Compare commits
8 commits
experiment
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c0635da6cc | ||
|
|
a86d718aa1 | ||
|
|
55ae83dc32 | ||
|
|
44b9daa00d | ||
|
|
1cf27fbd52 | ||
|
|
b0a5edaebb | ||
|
|
b67fae4df3 | ||
|
|
54b1eb2ad5 |
505 changed files with 31982 additions and 5821 deletions
|
|
@ -10,5 +10,5 @@
|
|||
"streak_tage": 1,
|
||||
"letzter_aktiver_tag": "2026-09-12",
|
||||
"historie": [],
|
||||
"zuletzt_aktualisiert": "2026-09-12T13:29:30.562924"
|
||||
"zuletzt_aktualisiert": "2026-09-15T15:18:26.060048"
|
||||
}
|
||||
|
|
@ -114,12 +114,12 @@
|
|||
}
|
||||
},
|
||||
|
||||
async register(name, email, password, role = 'solo', rememberMe = false) {
|
||||
async register(name, email, password, role = 'solo', rememberMe = false, profession = '') {
|
||||
try {
|
||||
const res = await fetch('/api/auth/register', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ name, email, password, role })
|
||||
body: JSON.stringify({ name, email, password, role, profession })
|
||||
});
|
||||
const data = await res.json().catch(() => ({}));
|
||||
if (!res.ok) {
|
||||
|
|
@ -333,7 +333,7 @@
|
|||
const loggedIn = this.isLoggedIn();
|
||||
const user = this.getUser();
|
||||
|
||||
// Topbar Elemente in index.html
|
||||
// Topbar Elemente in index.html & dashboard.html
|
||||
const userSpan = document.getElementById('nav-auth-user');
|
||||
const loginBtn = document.getElementById('nav-btn-login');
|
||||
const registerBtn = document.getElementById('nav-btn-register');
|
||||
|
|
@ -341,6 +341,24 @@
|
|||
const teacherLink = document.getElementById('nav-teacher-link');
|
||||
const heroPrimaryCta = document.getElementById('hero-primary-cta');
|
||||
|
||||
// Modern User Avatar Dropdown Elemente
|
||||
const userDropdown = document.getElementById('portal-user-dropdown');
|
||||
const userAvatar = document.getElementById('portal-user-avatar');
|
||||
const userNameEl = document.getElementById('portal-user-name');
|
||||
const ddFullName = document.getElementById('portal-dropdown-user-fullname');
|
||||
const ddEmail = document.getElementById('portal-dropdown-user-email');
|
||||
const ddBadge = document.getElementById('portal-dropdown-user-badge');
|
||||
const ddTeacher = document.getElementById('dropdown-link-teacher');
|
||||
const ddAdmin = document.getElementById('dropdown-link-admin');
|
||||
const ddMgmtTitle = document.getElementById('dropdown-mgmt-title');
|
||||
const ddMgmtDivider = document.getElementById('dropdown-teacher-divider');
|
||||
|
||||
// Mobile Drawer User Header
|
||||
const mobUserBox = document.getElementById('mobile-drawer-user-info');
|
||||
const mobAvatar = document.getElementById('mobile-drawer-avatar');
|
||||
const mobUsername = document.getElementById('mobile-drawer-username');
|
||||
const mobRole = document.getElementById('mobile-drawer-role');
|
||||
|
||||
// Dashboard Links auf index.html / impressum / datenschutz
|
||||
const navDashLink = document.getElementById('nav-dashboard-link');
|
||||
const mobDashLink = document.getElementById('mobile-drawer-dash-link');
|
||||
|
|
@ -349,40 +367,75 @@
|
|||
|
||||
if (guestBtn) guestBtn.style.display = 'none'; // Gast-Button permanent ausblenden
|
||||
|
||||
function computeInitials(str) {
|
||||
if (!str) return 'IT';
|
||||
const parts = str.trim().split(/\s+/);
|
||||
if (parts.length >= 2) {
|
||||
return (parts[0][0] + parts[1][0]).toUpperCase();
|
||||
}
|
||||
return str.substring(0, 2).toUpperCase();
|
||||
}
|
||||
|
||||
const roleLabels = {
|
||||
'admin': 'Administrator',
|
||||
'teacher': 'Dozent / Ausbilder',
|
||||
'student': 'Auszubildender',
|
||||
'solo': 'Professional'
|
||||
};
|
||||
|
||||
if (loggedIn && user) {
|
||||
document.body.classList.add('logged-in');
|
||||
const displayName = user.name || user.email.split('@')[0];
|
||||
const initials = computeInitials(displayName);
|
||||
const roleLabel = roleLabels[user.role] || 'Teilnehmer';
|
||||
const profStr = user.profession ? ` • ${user.profession}` : '';
|
||||
|
||||
// 1. User Dropdown befüllen
|
||||
if (userDropdown) {
|
||||
userDropdown.style.display = 'inline-block';
|
||||
}
|
||||
if (userAvatar) userAvatar.innerText = initials;
|
||||
if (userNameEl) userNameEl.innerText = displayName;
|
||||
if (ddFullName) ddFullName.innerText = displayName;
|
||||
if (ddEmail) ddEmail.innerText = user.email || '';
|
||||
if (ddBadge) {
|
||||
ddBadge.innerText = `${roleLabel}${profStr}`;
|
||||
}
|
||||
|
||||
// Mobile Drawer User Header
|
||||
if (mobUserBox) mobUserBox.style.display = 'flex';
|
||||
if (mobAvatar) mobAvatar.innerText = initials;
|
||||
if (mobUsername) mobUsername.innerText = displayName;
|
||||
if (mobRole) mobRole.innerText = `${roleLabel}${profStr}`;
|
||||
|
||||
// Dozent / Admin im Dropdown
|
||||
const hasTeacher = (user.role === 'teacher' || user.role === 'admin');
|
||||
const hasAdmin = (user.role === 'admin');
|
||||
if (ddTeacher) ddTeacher.style.display = hasTeacher ? 'flex' : 'none';
|
||||
if (ddAdmin) ddAdmin.style.display = hasAdmin ? 'flex' : 'none';
|
||||
if (ddMgmtTitle) ddMgmtTitle.style.display = hasTeacher ? 'block' : 'none';
|
||||
if (ddMgmtDivider) ddMgmtDivider.style.display = hasTeacher ? 'block' : 'none';
|
||||
|
||||
const mobMgmtTitle = document.getElementById('mobile-drawer-mgmt-title');
|
||||
const mobMgmtDivider = document.getElementById('mobile-drawer-mgmt-divider');
|
||||
if (mobMgmtTitle) mobMgmtTitle.style.display = hasTeacher ? 'block' : 'none';
|
||||
if (mobMgmtDivider) mobMgmtDivider.style.display = hasTeacher ? 'block' : 'none';
|
||||
|
||||
if (userSpan) {
|
||||
userSpan.style.display = 'inline-flex';
|
||||
userSpan.innerHTML = `👤 ${escapeHtml(user.name || user.email)}`;
|
||||
userSpan.innerHTML = `${escapeHtml(displayName)}`;
|
||||
userSpan.style.cursor = 'pointer';
|
||||
userSpan.title = 'Zum Kurs-Dashboard wechseln';
|
||||
userSpan.onclick = () => { window.location.href = 'dashboard.html'; };
|
||||
}
|
||||
if (loginBtn) {
|
||||
loginBtn.style.display = 'inline-flex';
|
||||
loginBtn.innerText = 'Abmelden';
|
||||
loginBtn.style.background = 'rgba(239, 68, 68, 0.15)';
|
||||
loginBtn.style.color = '#f87171';
|
||||
loginBtn.style.borderColor = 'rgba(239, 68, 68, 0.3)';
|
||||
loginBtn.removeAttribute('onclick');
|
||||
loginBtn.onclick = () => {
|
||||
if (confirm('Möchtest du dich wirklich abmelden?')) {
|
||||
this.logout();
|
||||
}
|
||||
};
|
||||
loginBtn.style.display = 'none'; // Bei aktivem User-Dropdown wird der separate Login-Btn ausgeblendet
|
||||
}
|
||||
if (registerBtn) {
|
||||
registerBtn.style.display = 'inline-flex';
|
||||
registerBtn.innerHTML = '<span class="btn-text-desktop">💻 Zum Kurs-Dashboard</span><span class="btn-text-mobile">Dashboard</span>';
|
||||
registerBtn.style.background = '#10b981';
|
||||
registerBtn.removeAttribute('onclick');
|
||||
registerBtn.onclick = (e) => {
|
||||
e.preventDefault();
|
||||
window.location.href = 'dashboard.html';
|
||||
};
|
||||
registerBtn.style.display = 'none'; // Ebenso
|
||||
}
|
||||
if (heroPrimaryCta) {
|
||||
heroPrimaryCta.innerHTML = '<span>▶ Zum Dashboard & Kurse wählen →</span>';
|
||||
heroPrimaryCta.innerHTML = '<span>Zum Dashboard & Kurse wählen →</span>';
|
||||
heroPrimaryCta.removeAttribute('onclick');
|
||||
heroPrimaryCta.onclick = (e) => {
|
||||
e.preventDefault();
|
||||
|
|
@ -392,7 +445,7 @@
|
|||
|
||||
// Alle allgemeinen Landing-Page Action Buttons anpassen
|
||||
document.querySelectorAll('.landing-cta-btn').forEach(btn => {
|
||||
btn.innerHTML = '<span>▶ Zum Dashboard & Kurse wählen →</span>';
|
||||
btn.innerHTML = '<span>Zum Dashboard & Kurse wählen →</span>';
|
||||
btn.style.background = '#10b981';
|
||||
btn.removeAttribute('onclick');
|
||||
btn.onclick = (e) => {
|
||||
|
|
@ -408,10 +461,10 @@
|
|||
if (landingBanner) {
|
||||
landingBanner.style.display = 'flex';
|
||||
if (landingUserName) {
|
||||
landingUserName.innerText = user.name || user.email.split('@')[0];
|
||||
landingUserName.innerText = displayName;
|
||||
}
|
||||
}
|
||||
if (navDashLink) navDashLink.style.display = 'none'; // Keine doppelte Schaltfläche in Desktop-Leiste
|
||||
if (navDashLink) navDashLink.style.display = 'none';
|
||||
if (mobDashLink) mobDashLink.style.display = 'flex';
|
||||
const mobLogoutBtn = document.getElementById('mobile-drawer-logout-btn');
|
||||
if (mobLogoutBtn) mobLogoutBtn.style.display = 'flex';
|
||||
|
|
@ -419,13 +472,16 @@
|
|||
if (mobDrawerAuthActions) mobDrawerAuthActions.style.display = 'none';
|
||||
} else {
|
||||
document.body.classList.remove('logged-in');
|
||||
if (userDropdown) userDropdown.style.display = 'none';
|
||||
if (mobUserBox) mobUserBox.style.display = 'none';
|
||||
|
||||
if (userSpan) {
|
||||
userSpan.style.display = 'none';
|
||||
userSpan.onclick = null;
|
||||
}
|
||||
if (loginBtn) {
|
||||
loginBtn.style.display = 'inline-flex';
|
||||
loginBtn.innerText = '🔑 Anmelden';
|
||||
loginBtn.innerText = 'Anmelden';
|
||||
loginBtn.style.background = 'rgba(255,255,255,0.12)';
|
||||
loginBtn.style.color = 'white';
|
||||
loginBtn.style.borderColor = 'rgba(255,255,255,0.2)';
|
||||
|
|
@ -433,7 +489,7 @@
|
|||
}
|
||||
if (registerBtn) {
|
||||
registerBtn.style.display = 'inline-flex';
|
||||
registerBtn.innerHTML = '<span class="btn-text-desktop">✨ Kostenlos registrieren</span><span class="btn-text-mobile">Starten</span>';
|
||||
registerBtn.innerHTML = '<span class="btn-text-desktop">Kostenlos registrieren</span><span class="btn-text-mobile">Starten</span>';
|
||||
registerBtn.style.background = '#0284c7';
|
||||
registerBtn.onclick = () => window.openAuthModal('register', { allowClose: true });
|
||||
}
|
||||
|
|
@ -443,7 +499,7 @@
|
|||
const mobLogoutBtn = document.getElementById('mobile-drawer-logout-btn');
|
||||
if (mobLogoutBtn) mobLogoutBtn.style.display = 'none';
|
||||
if (heroPrimaryCta) {
|
||||
heroPrimaryCta.innerHTML = '<span>🚀 Jetzt kostenlos registrieren</span>';
|
||||
heroPrimaryCta.innerHTML = '<span>Jetzt kostenlos registrieren →</span>';
|
||||
heroPrimaryCta.onclick = (e) => {
|
||||
e.preventDefault();
|
||||
window.openAuthModal('register', { allowClose: true });
|
||||
|
|
@ -452,7 +508,7 @@
|
|||
|
||||
// Alle allgemeinen Landing-Page Action Buttons für Besucher
|
||||
document.querySelectorAll('.landing-cta-btn').forEach(btn => {
|
||||
btn.innerHTML = '<span>✨ Jetzt kostenlos registrieren & Kurse wählen →</span>';
|
||||
btn.innerHTML = '<span>Jetzt kostenlos registrieren & Kurse wählen →</span>';
|
||||
btn.style.background = '#0284c7';
|
||||
btn.onclick = (e) => {
|
||||
e.preventDefault();
|
||||
|
|
@ -489,19 +545,19 @@
|
|||
// Aktualisiere ggf. vorhandene Curriculum-Aktions-Buttons
|
||||
document.querySelectorAll('.portal-card-action-btn').forEach(btn => {
|
||||
if (loggedIn) {
|
||||
btn.innerHTML = '<span>▶ Im Lernbereich öffnen</span> <span>→</span>';
|
||||
btn.innerHTML = '<span>Im Lernbereich öffnen</span> <span>→</span>';
|
||||
btn.classList.remove('locked');
|
||||
btn.style.background = '#10b981';
|
||||
btn.onclick = () => { window.location.href = 'dashboard.html'; };
|
||||
} else {
|
||||
btn.innerHTML = '<span>✨ Lehrplan freischalten & starten</span>';
|
||||
btn.innerHTML = '<span>Lehrplan ansehen</span>';
|
||||
btn.classList.add('locked');
|
||||
btn.style.background = '#0284c7';
|
||||
btn.onclick = (e) => {
|
||||
e.preventDefault();
|
||||
window.openAuthModal('register', {
|
||||
allowClose: true,
|
||||
title: '🔒 Lehrplan freischalten',
|
||||
title: 'Lehrplan freischalten',
|
||||
message: 'Erstelle einen kostenlosen Account in unter 30 Sekunden, um sofort vollen Zugriff auf alle Kurse und Module im Lernbereich zu erhalten.'
|
||||
});
|
||||
};
|
||||
|
|
@ -534,10 +590,10 @@
|
|||
` : ''}
|
||||
|
||||
<div style="display: flex; align-items: center; gap: 10px; margin-bottom: 14px;">
|
||||
<div style="width: 36px; height: 36px; border-radius: 8px; background: rgba(56, 189, 248, 0.15); border: 1px solid rgba(56, 189, 248, 0.4); display: flex; align-items: center; justify-content: center; font-size: 1.2rem;">🎓</div>
|
||||
<div style="width: 36px; height: 36px; border-radius: 8px; background: #eff6ff; border: 1px solid #bfdbfe; display: flex; align-items: center; justify-content: center; font-size: 1.05rem; color: #1d4ed8; font-weight: 800;">IT</div>
|
||||
<div>
|
||||
<div style="font-size: 1.15rem; font-weight: 800; color: #0f172a;" id="auth-modal-title">
|
||||
${escapeHtml(options.title || (initialMode === 'register' && allowReg ? '✨ Kostenlos registrieren' : '🔑 Anmelden'))}
|
||||
${escapeHtml(options.title || (initialMode === 'register' && allowReg ? 'Kostenlos registrieren' : 'Anmelden'))}
|
||||
</div>
|
||||
<div style="font-size: 0.72rem; color: #64748b; font-weight: 700; text-transform: uppercase; letter-spacing: 0.6px;">IT-PRAXISPORTAL AUTHENTICATION</div>
|
||||
</div>
|
||||
|
|
@ -576,12 +632,27 @@
|
|||
<div id="auth-field-role" style="display: none;">
|
||||
<label style="display: block; font-size: 0.8rem; font-weight: 700; color: #0f172a; margin-bottom: 4px;">Deine Rolle / Lernziel:</label>
|
||||
<select id="auth-select-role" style="width: 100%; padding: 9px 12px; border: 1px solid #cbd5e1; border-radius: 8px; font-size: 0.88rem; outline: none; background: white; box-sizing: border-box;">
|
||||
<option value="solo">🎓 Solo-Selbstlerner & Quereinsteiger</option>
|
||||
<option value="student">💼 Auszubildender Fachinformatiker (FIAE / FISI)</option>
|
||||
<option value="teacher">👨🏫 Lehrkraft / Dozent / Ausbilder</option>
|
||||
<option value="solo">Solo-Selbstlerner & Quereinsteiger</option>
|
||||
<option value="student">Auszubildender IT-Beruf</option>
|
||||
<option value="teacher">Lehrkraft / Dozent / Ausbilder</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div id="auth-field-profession" style="display: none;">
|
||||
<label style="display: block; font-size: 0.8rem; font-weight: 700; color: #0f172a; margin-bottom: 4px;">Dein IT-Ausbildungsberuf (optional):</label>
|
||||
<select id="auth-select-profession" style="width: 100%; padding: 9px 12px; border: 1px solid #cbd5e1; border-radius: 8px; font-size: 0.88rem; outline: none; background: white; box-sizing: border-box;">
|
||||
<option value="">Keine Angabe / Quereinsteiger</option>
|
||||
<option value="FISI">Fachinformatiker Systemintegration (FISI)</option>
|
||||
<option value="FIAE">Fachinformatiker Anwendungsentwicklung (FIAE)</option>
|
||||
<option value="FIDP">Fachinformatiker Daten- & Prozessanalyse (FIDP)</option>
|
||||
<option value="FIDV">Fachinformatiker Digitale Vernetzung (FIDV)</option>
|
||||
<option value="ITSE">IT-System-Elektroniker/in (ITSE)</option>
|
||||
<option value="ITSM">Kaufleute für IT-System-Management (ITSM)</option>
|
||||
<option value="KDM">Kaufleute für Digitalisierungsmanagement (KDM)</option>
|
||||
</select>
|
||||
<span style="display: block; font-size: 0.74rem; color: #64748b; margin-top: 3px;">Schaltet deinen spezifischen IHK AP2-Prüfungskurs auf dem Dashboard frei.</span>
|
||||
</div>
|
||||
|
||||
<!-- Session-Einstellung (Standard: nicht angehakt für Login-Zwang nach Schließen des Browsers) -->
|
||||
<div style="display: flex; align-items: center; gap: 8px; margin-top: 2px;">
|
||||
<input type="checkbox" id="auth-remember-me" style="width: 16px; height: 16px; cursor: pointer;">
|
||||
|
|
@ -606,7 +677,7 @@
|
|||
</form>
|
||||
|
||||
<div style="margin-top: 14px; text-align: center; font-size: 0.76rem; color: #94a3b8;">
|
||||
🔒 100% DSGVO-konform • Deutsche Server • Keine Tracking-Cookies
|
||||
100% DSGVO-konform • Serverstandort Deutschland • Keine Tracking-Cookies
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
|
@ -618,6 +689,7 @@
|
|||
const tabReg = document.getElementById('auth-tab-register');
|
||||
const nameField = document.getElementById('auth-field-name');
|
||||
const roleField = document.getElementById('auth-field-role');
|
||||
const profField = document.getElementById('auth-field-profession');
|
||||
const submitBtn = document.getElementById('auth-submit-btn');
|
||||
const errorBox = document.getElementById('auth-error-box');
|
||||
const form = document.getElementById('auth-form');
|
||||
|
|
@ -634,8 +706,9 @@
|
|||
tabLog.style.color = '#64748b';
|
||||
tabLog.style.boxShadow = 'none';
|
||||
|
||||
nameField.style.display = 'block';
|
||||
roleField.style.display = 'block';
|
||||
if (nameField) nameField.style.display = 'block';
|
||||
if (roleField) roleField.style.display = 'block';
|
||||
if (profField) profField.style.display = 'block';
|
||||
submitBtn.innerText = 'Jetzt kostenlos registrieren →';
|
||||
submitBtn.style.background = '#0284c7';
|
||||
} else {
|
||||
|
|
@ -648,8 +721,9 @@
|
|||
tabReg.style.boxShadow = 'none';
|
||||
}
|
||||
|
||||
nameField.style.display = 'none';
|
||||
roleField.style.display = 'none';
|
||||
if (nameField) nameField.style.display = 'none';
|
||||
if (roleField) roleField.style.display = 'none';
|
||||
if (profField) profField.style.display = 'none';
|
||||
submitBtn.innerText = 'Anmelden →';
|
||||
submitBtn.style.background = '#059669';
|
||||
}
|
||||
|
|
@ -678,6 +752,7 @@
|
|||
const password = document.getElementById('auth-input-password').value;
|
||||
const name = document.getElementById('auth-input-name')?.value.trim() || email.split('@')[0];
|
||||
const role = document.getElementById('auth-select-role')?.value || 'solo';
|
||||
const profession = document.getElementById('auth-select-profession')?.value || '';
|
||||
const rememberMe = document.getElementById('auth-remember-me')?.checked || false;
|
||||
|
||||
submitBtn.disabled = true;
|
||||
|
|
@ -685,7 +760,7 @@
|
|||
|
||||
let result;
|
||||
if (isRegister) {
|
||||
result = await window.AUTH.register(name, email, password, role, rememberMe);
|
||||
result = await window.AUTH.register(name, email, password, role, rememberMe, profession);
|
||||
} else {
|
||||
result = await window.AUTH.login(email, password, rememberMe);
|
||||
}
|
||||
|
|
@ -725,6 +800,50 @@
|
|||
}
|
||||
}
|
||||
|
||||
// Globales User Avatar Dropdown Handling
|
||||
window.toggleUserDropdown = function (event) {
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
const menu = document.getElementById('portal-dropdown-menu');
|
||||
const btn = document.getElementById('portal-user-btn');
|
||||
if (!menu) return;
|
||||
const isShown = menu.classList.contains('show');
|
||||
if (isShown) {
|
||||
menu.classList.remove('show');
|
||||
if (btn) btn.setAttribute('aria-expanded', 'false');
|
||||
} else {
|
||||
menu.classList.add('show');
|
||||
if (btn) btn.setAttribute('aria-expanded', 'true');
|
||||
}
|
||||
};
|
||||
|
||||
// Klick außerhalb schließt das Dropdown
|
||||
document.addEventListener('click', (e) => {
|
||||
const dd = document.getElementById('portal-user-dropdown');
|
||||
const menu = document.getElementById('portal-dropdown-menu');
|
||||
const btn = document.getElementById('portal-user-btn');
|
||||
if (menu && menu.classList.contains('show')) {
|
||||
if (!dd || !dd.contains(e.target)) {
|
||||
menu.classList.remove('show');
|
||||
if (btn) btn.setAttribute('aria-expanded', 'false');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Escape-Taste schließt das Dropdown
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape') {
|
||||
const menu = document.getElementById('portal-dropdown-menu');
|
||||
const btn = document.getElementById('portal-user-btn');
|
||||
if (menu && menu.classList.contains('show')) {
|
||||
menu.classList.remove('show');
|
||||
if (btn) btn.setAttribute('aria-expanded', 'false');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', initAuth);
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -691,97 +691,91 @@ window.COURSES_MANIFEST = {
|
|||
},
|
||||
"ihk_ap1": {
|
||||
"id": "ihk_ap1",
|
||||
"title": "IHK AP1 Crashkurs: IT-Arbeitsplatz & Programmierung",
|
||||
"title": "IHK AP1 Prüfungstraining: Einrichten eines IT-gestützten Arbeitsplatzes",
|
||||
"language": "ihk_ap1",
|
||||
"icon": "📘",
|
||||
"runner": "none",
|
||||
"description": "Lückenlose Vorbereitung auf die IHK-Abschlussprüfung Teil 1. Alle Lernfelder (LF 1 bis LF 8) mit interaktiven Übungen, 150-Fragen-Pool & 90-Minuten Probeprüfung.",
|
||||
"description": "Offizieller Vorbereitungskurs auf die IHK-Abschlussprüfung Teil 1 (Neuordnung nach ZPA-Katalog 2. Auflage). Alle Lernfelder (LF 1 bis LF 6), BPMN 2.0, EPK, PrintTop-Belegrechnung, Netzplantechnik & 171-Fragen-Pool.",
|
||||
"tracks": [
|
||||
{
|
||||
"id": "track_1_it_systeme",
|
||||
"title": "🌱 Lehrpfad 1: Basissysteme & Sicherheit (LF 1–4)",
|
||||
"description": "Unternehmen & Rolle im Betrieb, Hardware & Ergonomie, Subnetting & Netzwerke, Schutzbedarfsanalyse & DSGVO.",
|
||||
"title": "🌱 Lehrpfad 1: Basissysteme, Hardware & Netzwerke (LF 1–4)",
|
||||
"description": "Unternehmen & BGB-Verträge, Hardware, Ergonomie & IEC-Binärpräfixe, Subnetting & Netzwerke, Schutzbedarfsanalyse & DSGVO.",
|
||||
"certificateKey": "ihk_ap1_systeme",
|
||||
"chapters": [
|
||||
{
|
||||
"folder": "01_lf1_unternehmen_und_arbeitsplatz",
|
||||
"title": "LF 1: Unternehmen & Rolle im Betrieb (Organisation, Verträge, ITIL)",
|
||||
"title": "LF 1: Unternehmen & Rolle im Betrieb (Organisation, Verträge, ITIL 4 & KI-Support)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
},
|
||||
{
|
||||
"folder": "02_lf2_arbeitsplaetze_ausstatten",
|
||||
"title": "LF 2: Arbeitsplätze ausstatten (Hardware, Ergonomie, USV, NWA)",
|
||||
"title": "LF 2: Arbeitsplätze ausstatten (Displays, ArbStättV, USV & IEC-Binärpräfixe)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
},
|
||||
{
|
||||
"folder": "03_lf3_clients_in_netzwerke_einbinden",
|
||||
"title": "LF 3: Clients in Netzwerke einbinden (Subnetting, IPv6, LWL)",
|
||||
"title": "LF 3: Clients in Netzwerke einbinden (IPv4/IPv6, Subnetting, LWL & Übertragungszeiten)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
},
|
||||
{
|
||||
"folder": "04_lf4_schutzbedarfsanalyse_und_cybersicherheit",
|
||||
"title": "LF 4: Schutzbedarfsanalyse & Cyber-Sicherheit (CIA, Backup, TOMs)",
|
||||
"title": "LF 4: Schutzbedarfsanalyse & Cyber-Sicherheit (CIA, Backup, TOM & KI-Datenschutz)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "track_2_programmierung_und_datenbanken",
|
||||
"title": "💻 Lehrpfad 2: Software, CPS & Schnittstellen (LF 5–8)",
|
||||
"description": "Algorithmen & SQL-Datenbanken, Cyber-physische Systeme, Instandhaltung & Fehlerdiagnose, REST-Schnittstellen.",
|
||||
"id": "track_2_prozesse_software_itsm",
|
||||
"title": "⚡ Lehrpfad 2: Prozessmodellierung, Software-Design & ITSM (LF 1, 5, 6)",
|
||||
"description": "BPMN 2.0 Gateways & EPK, UML-Diagramme & Pseudocode (kein PAP/Struktogramm mehr!), IT-Servicemanagement & SLAs.",
|
||||
"certificateKey": "ihk_ap1_programmierung",
|
||||
"chapters": [
|
||||
{
|
||||
"folder": "05_lf5_software_zur_datenverwaltung",
|
||||
"title": "LF 5: Software zur Datenverwaltung anpassen (Trace, UML, SQL)",
|
||||
"folder": "05_prozessmodellierung_bpmn_und_epk",
|
||||
"title": "BPMN & EPK: Geschäftsprozessmodellierung nach ZPA-Standard (LF 1/6)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
},
|
||||
{
|
||||
"folder": "06_lf6_cyber_physische_systeme_iot",
|
||||
"title": "LF 6: Das Cyber-Physische System ergänzen (Sensoren, Aktoren, MQTT)",
|
||||
"folder": "06_lf5_software_und_anwendungsentwicklung",
|
||||
"title": "LF 5: Software- & Anwendungsentwicklung (UML, Pseudocode & Trace-Tabellen)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
},
|
||||
{
|
||||
"folder": "07_lf7_instandhaltung_und_fehlerdiagnose",
|
||||
"title": "LF 7: Cyber-Physische Systeme instand halten (Diagnose, ESD, Messen)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
},
|
||||
{
|
||||
"folder": "08_lf8_datenfluesse_und_schnittstellen",
|
||||
"title": "LF 8: Datenflüsse optimieren & Schnittstellen anpassen (REST, JSON, JWT)",
|
||||
"folder": "07_lf6_serviceanfragen_und_itsm",
|
||||
"title": "LF 6: Serviceanfragen bearbeiten & ITSM (Tickettriage, SLAs & Eskalation)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "track_3_praxis_drills",
|
||||
"title": "⚡ Lehrpfad 3: Interaktive Praxis-Drills & Labore",
|
||||
"description": "Trace-Tabellen-Trainer mit zeilenweiser Prüfung, Relationales SQL-Labor & Subnetting-Drill Master.",
|
||||
"id": "track_3_kaufmaennisches_und_datenmodelle",
|
||||
"title": "📊 Lehrpfad 3: Kaufmännische Steuerung, Netzplantechnik & Datenmodelle (LF 1, 2, 5)",
|
||||
"description": "PrintTop GmbH Rechnungsprüfung, Verzugszinsen § 288 BGB, DIN 69900 Netzplantechnik und relationale ER-Modelle (ohne SQL).",
|
||||
"certificateKey": "ihk_ap1_prozesse",
|
||||
"chapters": [
|
||||
{
|
||||
"folder": "09_trace_tabellen_trainer",
|
||||
"title": "Drill 1: Interaktiver Trace-Tabellen-Trainer (LF 5)",
|
||||
"folder": "08_belegsaetze_und_kaufmaennische_rechnung",
|
||||
"title": "LF 1/2: Belegsätze & Kaufmännische Rechnung (PrintTop GmbH, Skonto, Rabatt & Verzug)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
},
|
||||
{
|
||||
"folder": "10_sql_datenbank_labor",
|
||||
"title": "Drill 2: Relationale Datenbanken & Live-SQL Labor (LF 5)",
|
||||
"folder": "09_netzplantechnik_und_projektmanagement",
|
||||
"title": "LF 1: Netzplantechnik & Projektcontrolling (FAZ/FEZ/SAZ/SEZ, Puffer & Kritischer Pfad)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
},
|
||||
{
|
||||
"folder": "11_subnetting_drill_master",
|
||||
"title": "Drill 3: Endlos Subnetting-Drill Generator (LF 3)",
|
||||
"folder": "10_relationales_datenmodell_und_erm",
|
||||
"title": "LF 5: Relationales Datenmodell & ERM (Chen/Krähenfuß, Normalisierung 1NF-3NF)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
}
|
||||
|
|
@ -789,13 +783,19 @@ window.COURSES_MANIFEST = {
|
|||
},
|
||||
{
|
||||
"id": "track_4_probepruefung",
|
||||
"title": "🏆 Lehrpfad 4: Offizielle AP1 Probeprüfung & Notenrechner",
|
||||
"description": "90-Minuten Realtime Prüfungsmodus mit Countdown, 100 Punkten, Zufallsgenerator aus 150 Fragen & Erwartungshorizont.",
|
||||
"title": "🏆 Lehrpfad 4: Interaktive Praxis-Drills & IHK-Probeprüfung",
|
||||
"description": "Endlos-Subnetting-Drill Generator & authentische 90-Minuten-Prüfungssimulation mit 100-Punkte-Schlüssel.",
|
||||
"certificateKey": "ihk_ap1_master",
|
||||
"chapters": [
|
||||
{
|
||||
"folder": "11_subnetting_drill_master",
|
||||
"title": "Drill: Subnetting- & Präfix-Trainer mit Sofort-Validierung (LF 3)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
},
|
||||
{
|
||||
"folder": "12_ap1_probepruefung_simulation",
|
||||
"title": "Probeprüfung AP1: 90 Min. Realtime Simulation (100 Pkt.)",
|
||||
"title": "Simulation: IHK AP1 Probeprüfung (90 Min. / 100 Punkte nach ZPA 2. Auflage)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
}
|
||||
|
|
@ -809,89 +809,107 @@ window.COURSES_MANIFEST = {
|
|||
"language": "ihk_ap2_fisi",
|
||||
"icon": "📕",
|
||||
"runner": "none",
|
||||
"description": "Vollständige Prüfungsvorbereitung für FISI (LF 9–12, WiSo, 40h-Projekt). VLANs, OSPF, Active Directory, RAID-Simulator, Cloud & 3 vollständige Probeprüfungen.",
|
||||
"description": "Vollständige Prüfungsvorbereitung für FISI (LF 9–12, WiSo, 40h-Projekt). 12 Module nach ZPA Nord-West 2. Auflage: VLANs, VRT-Symbole, Active Directory, RAID 0-10, Backup 3-2-1-1-0, Enterprise SQL & Vollsimulation.",
|
||||
"tracks": [
|
||||
{
|
||||
"id": "track_1_netzwerke_und_server",
|
||||
"title": "🌱 Lehrpfad 1: Enterprise Netzwerke & Serverdienste (LF 9 & 10)",
|
||||
"description": "VLANs (802.1Q), LACP, Spanning Tree, OSPF, BGP, Active Directory, Gruppenrichtlinien, PKI & Ansible.",
|
||||
"description": "VLANs (802.1Q), LACP, Spanning Tree, OSPF, BGP, VRT-Symbole, Strukturierte Verkabelung, Active Directory & Kerberos.",
|
||||
"certificateKey": "ihk_ap2_netzwerke",
|
||||
"chapters": [
|
||||
{
|
||||
"folder": "01_lf9_netzwerke_und_dienste",
|
||||
"title": "LF 9: Netzwerke & Dienste bereitstellen (VLAN, Routing, Firewalls)",
|
||||
"folder": "01_lf9_netzwerkinfrastruktur_und_routing",
|
||||
"title": "LF 9: Enterprise Routing & Switching (VLAN, STP, LACP, OSPF)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
},
|
||||
{
|
||||
"folder": "02_lf10_serverdienste_und_automation",
|
||||
"title": "LF 10: Serverdienste & Automation bereitstellen (AD DS, DNS, Ansible)",
|
||||
"folder": "02_vrt_netzwerkplaene_und_infrastruktur",
|
||||
"title": "LF 9: Normierte Netzwerkpläne (VRT-Symbole) & Verkabelung",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
},
|
||||
{
|
||||
"folder": "03_lf10_serverdienste_und_identitaeten",
|
||||
"title": "LF 10: Serverdienste, Active Directory & Identitäten (AD DS, GPO, Kerberos)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "track_2_storage_und_cloud",
|
||||
"title": "🛡️ Lehrpfad 2: Storage, Hochverfügbarkeit & Cloud (LF 11 & 12)",
|
||||
"description": "RAID 0/1/5/6/10, SAN/NAS, HA-Cluster, Quorum, STONITH, ZFS, Hypervisoren, Docker, Kubernetes & Prometheus.",
|
||||
"certificateKey": "ihk_ap2_sicherheit",
|
||||
"id": "track_2_storage_und_automation",
|
||||
"title": "⚙️ Lehrpfad 2: Skripting, Storage & Hochverfügbarkeit (LF 10 & 11)",
|
||||
"description": "PowerShell, Bash, Ansible, RAID 0/1/5/6/10 (exklusiv AP2!), SAN/NAS, iSCSI, 3-2-1-1-0 Backup & HA-Cluster.",
|
||||
"certificateKey": "ihk_ap2_storage_automation",
|
||||
"chapters": [
|
||||
{
|
||||
"folder": "03_lf11_speicher_und_hochverfuegbarkeit",
|
||||
"title": "LF 11: Speicher- & Hochverfügbarkeitssysteme (RAID, SAN, Cluster)",
|
||||
"folder": "04_systemautomation_powershell_bash_python",
|
||||
"title": "LF 10: Skriptprogrammierung & Systemautomation (PowerShell, Bash, Ansible)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
},
|
||||
{
|
||||
"folder": "04_lf12_virtualisierung_und_cloud",
|
||||
"title": "LF 12: Virtualisierung, Cloud & Automation (Docker, K8s, IaC)",
|
||||
"folder": "05_lf11_speicher_und_hochverfuegbarkeit",
|
||||
"title": "LF 11: Enterprise Storage, RAID & SAN-Architekturen (RAID 0-10, iSCSI, ZFS)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
},
|
||||
{
|
||||
"folder": "06_backup_dr_und_hochverfuegbarkeit",
|
||||
"title": "LF 11: Backup, Disaster Recovery & Hochverfügbarkeit (3-2-1-1-0, RTO/RPO)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "track_3_wiso_und_projekt",
|
||||
"title": "☁️ Lehrpfad 3: WiSo & 40h-Betriebliche Projektarbeit",
|
||||
"description": "Wirtschafts- & Sozialkunde (Arbeitsrecht, KSchG, Sozialversicherung) & Leitfaden zur 40h-Projektarbeit samt Fachgespräch.",
|
||||
"certificateKey": "ihk_ap2_wiso_projekt",
|
||||
"id": "track_3_datenbanken_und_cloud",
|
||||
"title": "☁️ Lehrpfad 3: Enterprise SQL, Cloud & WiSo Arbeitsrecht (LF 10, 11 & 12)",
|
||||
"description": "ZPA-SQL (DDL, DCL, Multi-Table JOINs, DATEDIFF), Hypervisoren, Docker, Kubernetes, Zero Trust & WiSo Arbeitsrecht.",
|
||||
"certificateKey": "ihk_ap2_cloud_sql",
|
||||
"chapters": [
|
||||
{
|
||||
"folder": "05_wiso_arbeitsrecht_und_wirtschaft",
|
||||
"title": "WiSo: Wirtschafts- & Sozialkunde Prüfungs-Fit (BGB, KSchG, BetrVG)",
|
||||
"folder": "07_enterprise_sql_und_datenbank_admin",
|
||||
"title": "LF 10/11: Enterprise SQL & Datenbank-Administration (DDL, DCL, Joins, DATEDIFF)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
},
|
||||
{
|
||||
"folder": "06_betriebliche_projektarbeit_guide",
|
||||
"title": "Projekt: Betriebliche Projektarbeit (40h) & Fachgespräch-Guide",
|
||||
"folder": "08_lf12_virtualisierung_und_cloud",
|
||||
"title": "LF 12: Cloud-Architekturen, Virtualisierung & Security (Docker, K8s, Zero Trust)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
},
|
||||
{
|
||||
"folder": "09_wiso_arbeitsrecht_und_wirtschaft",
|
||||
"title": "WiSo 1: Arbeitsrecht, Tarifrecht & Gehaltsabrechnung (BBiG, KSchG, ArbZG)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "track_4_probepruefungen",
|
||||
"title": "🏆 Lehrpfad 4: Offizielle AP2 Probeprüfungen & Simulationen",
|
||||
"description": "Die 3 schriftlichen IHK-Prüfungsteile unter Realbedingungen (Teil 1: 90 Min., Teil 2: 90 Min., WiSo: 60 Min.) mit 150-Fragen-Zufallsgenerator.",
|
||||
"id": "track_4_betrieb_projekt_und_pruefung",
|
||||
"title": "🏆 Lehrpfad 4: Organisation, 40h-Projektarbeit & Prüfungssimulation",
|
||||
"description": "Unternehmensformen, BetrVG, PUE-Wert, Green IT, Leitfaden zur 40h-Projektarbeit samt Fachgespräch & Vollsimulation AP2.",
|
||||
"certificateKey": "ihk_ap2_fisi_master",
|
||||
"chapters": [
|
||||
{
|
||||
"folder": "07_ap2_teil_1_probepruefung",
|
||||
"title": "Probeprüfung AP2 Teil 1: Planen & Umsetzen (90 Min. / 100 Pkt.)",
|
||||
"folder": "10_wiso_betriebsorganisation_und_umwelt",
|
||||
"title": "WiSo 2: Unternehmensformen, Umweltschutz (PUE) & Organisation (BetrVG)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
},
|
||||
{
|
||||
"folder": "08_ap2_teil_2_probepruefung",
|
||||
"title": "Probeprüfung AP2 Teil 2: Administration & Netzanalyse (90 Min. / 100 Pkt.)",
|
||||
"folder": "11_betriebliche_projektarbeit_guide",
|
||||
"title": "Projekt: Betriebliche Projektarbeit (40h) & Fachgespräch (Guide)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
},
|
||||
{
|
||||
"folder": "09_ap2_wiso_probepruefung",
|
||||
"title": "Probeprüfung AP2 WiSo: Wirtschafts- & Sozialkunde (60 Min. / 100 Pkt.)",
|
||||
"folder": "12_ap2_gesamtpruefung_simulation",
|
||||
"title": "Abschluss: AP2 Gesamtprüfungssimulation (PB 1, PB 2 & WiSo)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
}
|
||||
|
|
@ -2254,5 +2272,689 @@ window.COURSES_MANIFEST = {
|
|||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"ihk_ap2_fiae": {
|
||||
"id": "ihk_ap2_fiae",
|
||||
"title": "IHK AP2 Crashkurs: Fachinformatiker Anwendungsentwicklung",
|
||||
"language": "ihk_ap2_fiae",
|
||||
"icon": "💻",
|
||||
"runner": "none",
|
||||
"description": "Vollständige Prüfungsvorbereitung für FIAE (LF 9–12, WiSo, 80h-Projekt). 12 Module nach ZPA Nord-West 2. Auflage: Software-Architektur (MVC, Hexagonal, Clean), GoF-Entwurfsmuster, UML 2.5, Algorithmen (O-Notation), OOP & SOLID, Enterprise SQL & ORM, REST/GraphQL, Testing & Vollsimulation.",
|
||||
"tracks": [
|
||||
{
|
||||
"id": "track_1_architektur_und_design",
|
||||
"title": "🌱 Lehrpfad 1: Software-Architektur, Modellierung & Anforderungsanalyse (LF 9 & 10)",
|
||||
"description": "Clean & Hexagonal Architecture, GoF Patterns (Singleton, Factory, Strategy, Observer), UML 2.5 (Klassendiagramm, Sequenzdiagramm, Aktivitätsdiagramm, State Machine), User Stories, Lasten-/Pflichtenheft & Function-Point-Methode.",
|
||||
"certificateKey": "ihk_ap2_fiae_architektur",
|
||||
"chapters": [
|
||||
{
|
||||
"folder": "01_software_architektur_und_design_patterns",
|
||||
"title": "LF 9: Software-Architektur & Design Patterns (MVC, Clean, GoF-Muster)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
},
|
||||
{
|
||||
"folder": "02_uml_modellierung_und_softwareentwurf",
|
||||
"title": "LF 9: Normierte UML 2.5 Modellierung (Klassen-, Sequenz- & Aktivitätsdiagramme)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
},
|
||||
{
|
||||
"folder": "03_anforderungsanalyse_und_aufwandsschaetzung",
|
||||
"title": "LF 10: Anforderungsanalyse, Agilität & Aufwandsschätzung (Function Points, COCOMO)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "track_2_algorithmen_und_oop",
|
||||
"title": "⚙️ Lehrpfad 2: Algorithmen, Datenstrukturen & Objektorientierung (LF 10 & 11)",
|
||||
"description": "O-Notation (Zeit- & Speicherkomplexität), Sortier- & Suchalgorithmen, Bäume, Graphen, Hashmaps, OOP-Konzepte & SOLID-Prinzipien.",
|
||||
"certificateKey": "ihk_ap2_fiae_algorithmen",
|
||||
"chapters": [
|
||||
{
|
||||
"folder": "04_algorithmen_und_komplexitaet_o_notation",
|
||||
"title": "LF 10: Algorithmen, Komplexität & O-Notation (Sortieren, Suchen, Rekursion)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
},
|
||||
{
|
||||
"folder": "05_datenstrukturen_und_speichermanagement",
|
||||
"title": "LF 10: Fortgeschrittene Datenstrukturen & Memory Management (Trees, HashTables, Caching)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
},
|
||||
{
|
||||
"folder": "06_objektorientierte_programmierung_solid",
|
||||
"title": "LF 11: Objektorientierte Programmierung & SOLID-Prinzipien (Clean Code)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "track_3_daten_apis_und_testing",
|
||||
"title": "☁️ Lehrpfad 3: Enterprise SQL, API-Design & Quality Assurance (LF 11 & 12)",
|
||||
"description": "ZPA-SQL (DDL, DML, DCL, Normalisierung, Transaktionen/ACID), RESTful APIs (HTTP Verbs, Statuscodes, OpenAPI), GraphQL, TDD, Unit-/Integrations-Tests & CI/CD.",
|
||||
"certificateKey": "ihk_ap2_fiae_daten_testing",
|
||||
"chapters": [
|
||||
{
|
||||
"folder": "07_enterprise_sql_und_datenbank_modellierung",
|
||||
"title": "LF 11: Enterprise SQL & Datenbank-Design (ER-Modell, Normalisierung, ACID, Transaktionen)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
},
|
||||
{
|
||||
"folder": "08_api_design_rest_json_und_graphql",
|
||||
"title": "LF 11: Modernes API-Design, REST, JSON & Web-Security (OAuth2, JWT, CORS)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
},
|
||||
{
|
||||
"folder": "09_software_testing_und_ci_cd_pipelines",
|
||||
"title": "LF 12: Software-Testing, Teststufen & CI/CD (TDD, Mocking, Testüberdeckung)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "track_4_wiso_projekt_und_pruefung",
|
||||
"title": "🏆 Lehrpfad 4: WiSo, 80h-Projektarbeit & AP2-Prüfungssimulation",
|
||||
"description": "Software-Lizenzmodelle (GPL, MIT, Apache, SaaS), Arbeits- & Urheberrecht, Leitfaden für die 80-stündige Projektarbeit samt Dokumentation & Fachgespräch sowie Vollprüfung.",
|
||||
"certificateKey": "ihk_ap2_fiae_master",
|
||||
"chapters": [
|
||||
{
|
||||
"folder": "10_wiso_arbeitsrecht_und_lizenzmodelle",
|
||||
"title": "WiSo: IT-Arbeitsrecht, Urheberrecht, Lizenzmodelle (Open Source vs. Commercial) & DSGVO",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
},
|
||||
{
|
||||
"folder": "11_betriebliche_projektarbeit_guide_80h",
|
||||
"title": "Projekt: Betriebliche Projektarbeit (80h) & Fachgespräch (Guide nach § 15 BBiG)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
},
|
||||
{
|
||||
"folder": "12_ap2_fiae_gesamtpruefung_simulation",
|
||||
"title": "Abschluss: AP2 FIAE Gesamtprüfungssimulation (PB 1, PB 2 & WiSo)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"ihk_ap2_fidp": {
|
||||
"id": "ihk_ap2_fidp",
|
||||
"title": "IHK AP2 Crashkurs: Fachinformatiker Daten- und Prozessanalyse",
|
||||
"language": "ihk_ap2_fidp",
|
||||
"icon": "📊",
|
||||
"runner": "none",
|
||||
"description": "Vollständige Prüfungsvorbereitung für FIDP (LF 9–12, WiSo, 40h-Projekt). 12 Module nach ZPA Nord-West 2. Auflage: Geschäftsprozessanalyse, BPMN 2.0 (ISO 19510), ISO 25012 Datenqualität, ETL/CDC, Star-/Snowflake-Schema, Big Data & Kafka, Deskriptive/Induktive Statistik, Machine Learning Evaluation (Confusion Matrix, ROC), BI-Dashboard-Design, Data Governance & Vollsimulation.",
|
||||
"tracks": [
|
||||
{
|
||||
"id": "track_1_prozessanalyse_und_qualitaet",
|
||||
"title": "🌱 Lehrpfad 1: Prozessanalyse, BPMN 2.0 & Datenqualität (LF 9 & 10)",
|
||||
"description": "Ist-Analyse, Durchlaufzeiten (DLZ), Prozess-Effizienz, BPMN 2.0 (Pools, Lanes, Gateways, Boundary Events), Datenformate (JSON, Parquet) & ISO/IEC 25012 Datenqualitätsmerkmale.",
|
||||
"certificateKey": "ihk_ap2_fidp_prozesse",
|
||||
"chapters": [
|
||||
{
|
||||
"folder": "01_prozessanalyse_und_prozessoptimierung",
|
||||
"title": "LF 9: Geschäftsprozessanalyse & Optimierungsmethoden (KPIs, Kaizen, Lean)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
},
|
||||
{
|
||||
"folder": "02_bpmn20_und_prozessmodellierung_vertiefung",
|
||||
"title": "LF 9: Normierte BPMN 2.0 Prozessmodellierung (Gateways, Intermediate Events)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
},
|
||||
{
|
||||
"folder": "03_datenquellen_und_datenqualitaet",
|
||||
"title": "LF 10: Datenquellen, Formate & Datenqualität (ISO 25012, Parquet)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "track_2_etl_und_warehousing",
|
||||
"title": "⚙️ Lehrpfad 2: Data Engineering, ETL & Data Warehousing (LF 10 & 11)",
|
||||
"description": "ETL vs. ELT, Change Data Capture (CDC), Idempotenz, DWH-3-Schichten-Architektur, Star- vs. Snowflake-Schema, Slowly Changing Dimensions (SCD 1-3) & Big Data Streaming (Kafka, Lakehouse).",
|
||||
"certificateKey": "ihk_ap2_fidp_dwh",
|
||||
"chapters": [
|
||||
{
|
||||
"folder": "04_etl_prozesse_und_datenpipelines",
|
||||
"title": "LF 10: ETL/ELT-Prozesse & Moderne Datenpipelines (CDC, Airflow, Idempotenz)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
},
|
||||
{
|
||||
"folder": "05_data_warehouse_und_datenmodellierung",
|
||||
"title": "LF 10: Data Warehouse Architekturen & Star-/Snowflake-Schema (SCD 1-3)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
},
|
||||
{
|
||||
"folder": "06_big_data_architekturen_und_streaming",
|
||||
"title": "LF 11: Big Data, Streaming & Data Lakehouse (5 V's, Kafka, Lambda/Kappa)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "track_3_statistik_ml_und_bi",
|
||||
"title": "☁️ Lehrpfad 3: Statistik, Machine Learning & BI-Reporting (LF 11 & 12)",
|
||||
"description": "Deskriptive & induktive Statistik (Mittelwert, Median, Hypothesentest, p-Wert), Machine Learning Evaluation (Confusion Matrix, Precision, Recall, F1-Score) & Dashboard-Design nach Tufte & IBCS.",
|
||||
"certificateKey": "ihk_ap2_fidp_analytics",
|
||||
"chapters": [
|
||||
{
|
||||
"folder": "07_deskriptive_und_induktive_statistik",
|
||||
"title": "LF 11: Deskriptive & Induktive Statistik (Lage-/Streumaße, Hypothesentests)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
},
|
||||
{
|
||||
"folder": "08_machine_learning_und_kuenstliche_intelligenz",
|
||||
"title": "LF 11: Machine Learning & Modell-Evaluation (Confusion Matrix, F1-Score)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
},
|
||||
{
|
||||
"folder": "09_datenvisualisierung_und_bi_reporting",
|
||||
"title": "LF 12: Datenvisualisierung & BI-Dashboard-Design (IBCS, Tufte, Power BI)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "track_4_governance_projekt_und_pruefung",
|
||||
"title": "🏆 Lehrpfad 4: Governance, 40h-Projektarbeit & AP2-Prüfungssimulation",
|
||||
"description": "DSGVO (Art. 5/6/9), Anonymisierung vs. Pseudonymisierung, Data Lineage, Leitfaden für die 40h-Projektarbeit samt Nutzwertanalyse & Vollprüfungssimulation.",
|
||||
"certificateKey": "ihk_ap2_fidp_master",
|
||||
"chapters": [
|
||||
{
|
||||
"folder": "10_datenschutz_compliance_und_wiso",
|
||||
"title": "WiSo & LF 12: Data Governance, DSGVO & IT-Recht (Anonymisierung, Lineage)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
},
|
||||
{
|
||||
"folder": "11_betriebliche_projektarbeit_guide_40h",
|
||||
"title": "Projekt: Betriebliche Projektarbeit (40h) & Fachgespräch (Guide)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
},
|
||||
{
|
||||
"folder": "12_ap2_fidp_gesamtpruefung_simulation",
|
||||
"title": "Abschluss: AP2 FIDP Gesamtprüfungssimulation (PB 1, PB 2 & WiSo)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"ihk_ap2_fidv": {
|
||||
"id": "ihk_ap2_fidv",
|
||||
"title": "IHK AP2 Crashkurs: Fachinformatiker Digitale Vernetzung",
|
||||
"language": "ihk_ap2_fidv",
|
||||
"icon": "🏭",
|
||||
"runner": "none",
|
||||
"description": "Vollständige Prüfungsvorbereitung für FIDV (LF 9–12, WiSo, 40h-Projekt). 12 Module nach ZPA Nord-West 2. Auflage: Cyber-Physische Systeme (CPS), RAMI 4.0, IO-Link & Sensorik, Edge Computing (IPCs, K3s), Profinet IO (RT/IRT), TSN (802.1Qbv), OPC UA & MQTT, Private 5G-Campusnetze (Band n78), Industrial Security IEC 62443, Purdue-Modell & IDMZ, PRP/HSR Zero-Failover & Vollsimulation.",
|
||||
"tracks": [
|
||||
{
|
||||
"id": "track_1_cps_und_edge",
|
||||
"title": "🌱 Lehrpfad 1: CPS, Sensorik & Edge Computing (LF 9 & 10)",
|
||||
"description": "Cyber-Physische Systeme (CPS), RAMI 4.0, Verwaltungsschale (AAS), 4-20 mA Stromschleifen, IO-Link (IEC 61131-9), Nyquist-Shannon & Industrie-PCs / Edge-Gateways.",
|
||||
"certificateKey": "ihk_ap2_fidv_cps",
|
||||
"chapters": [
|
||||
{
|
||||
"folder": "01_industrie40_und_cyber_physische_systeme",
|
||||
"title": "LF 9: Industrie 4.0 & Cyber-Physische Systeme (RAMI 4.0, AAS, Digital Twin)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
},
|
||||
{
|
||||
"folder": "02_sensorik_aktorik_und_signalverarbeitung",
|
||||
"title": "LF 9: Industrielle Sensorik, Aktorik & Signalwandlung (IO-Link, 4-20 mA)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
},
|
||||
{
|
||||
"folder": "03_edge_computing_und_iot_gateways",
|
||||
"title": "LF 10: Edge Computing, Industrie-PCs & IoT-Gateways (Docker, K3s, Filterung)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "track_2_busse_und_protokolle",
|
||||
"title": "⚙️ Lehrpfad 2: Industrielle Kommunikation & IoT-Protokolle (LF 10 & 11)",
|
||||
"description": "Modbus RTU/TCP, Profinet IO (RT/IRT), TSN (IEEE 802.1Qbv), OPC UA (Client/Server, Nodes), MQTT (QoS 0/1/2, LWT, Retain) & Private 5G-Campusnetze (Band n78 3,7-3,8 GHz).",
|
||||
"certificateKey": "ihk_ap2_fidv_busse",
|
||||
"chapters": [
|
||||
{
|
||||
"folder": "04_industrielle_kommunikation_und_feldbusse",
|
||||
"title": "LF 10: Feldbusse & Industrial Ethernet (Profinet IO, Modbus, TSN)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
},
|
||||
{
|
||||
"folder": "05_iot_protokolle_mqtt_coap_und_opc_ua",
|
||||
"title": "LF 11: Industrielle IoT-Protokolle (OPC UA, MQTT, CoAP)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
},
|
||||
{
|
||||
"folder": "06_drahtlose_vernetzung_und_5g_campusnetze",
|
||||
"title": "LF 11: Drahtlose Vernetzung & Private 5G-Campusnetze (URLLC, n78, LoRaWAN)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "track_3_ot_security_und_resilienz",
|
||||
"title": "☁️ Lehrpfad 3: Industrial Security & OT-Netzwerkresilienz (LF 11 & 12)",
|
||||
"description": "IEC 62443 (Zonen & Conduits, Security Levels 1-4), Purdue-Modell (Level 0-5), Industrial DMZ (IDMZ 3.5), Deep Packet Inspection (DPI), PRP/HSR Zero-Failover & passives OT-Monitoring.",
|
||||
"certificateKey": "ihk_ap2_fidv_security",
|
||||
"chapters": [
|
||||
{
|
||||
"folder": "07_industrial_security_iec_62443",
|
||||
"title": "LF 11: Industrial Security nach IEC 62443 (Zonen & Conduits, SL 1-4)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
},
|
||||
{
|
||||
"folder": "08_netzwerksegmentierung_und_ot_firewalls",
|
||||
"title": "LF 11: Purdue-Referenzmodell & OT-Netzwerksegmentierung (IDMZ, DPI)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
},
|
||||
{
|
||||
"folder": "09_ot_monitoring_anomalieerkennung_und_resilienz",
|
||||
"title": "LF 12: OT-Netzwerkresilienz, PRP/HSR & Passives Monitoring (TAP vs. SPAN)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "track_4_safety_projekt_und_pruefung",
|
||||
"title": "🏆 Lehrpfad 4: Maschinensicherheit, 40h-Projektarbeit & AP2-Prüfungssimulation",
|
||||
"description": "Safety vs. Security, Performance Level (PL) & SIL, CE-Kennzeichnung (wesentliche Veränderung), Leitfaden für die 40h-Projektarbeit samt Retrofit & Vollprüfungssimulation.",
|
||||
"certificateKey": "ihk_ap2_fidv_master",
|
||||
"chapters": [
|
||||
{
|
||||
"folder": "10_betriebsorganisation_sicherheit_und_wiso",
|
||||
"title": "WiSo & LF 12: Maschinensicherheit (Safety vs. Security, SIL/PL) & Recht",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
},
|
||||
{
|
||||
"folder": "11_betriebliche_projektarbeit_guide_40h",
|
||||
"title": "Projekt: Betriebliche Projektarbeit (40h) & Fachgespräch (Guide)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
},
|
||||
{
|
||||
"folder": "12_ap2_fidv_gesamtpruefung_simulation",
|
||||
"title": "Abschluss: AP2 FIDV Gesamtprüfungssimulation (PB 1, PB 2 & WiSo)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"ihk_ap2_itse": {
|
||||
"id": "ihk_ap2_itse",
|
||||
"title": "IHK AP2 Crashkurs: IT-System-Elektroniker/in",
|
||||
"language": "ihk_ap2_itse",
|
||||
"icon": "⚡",
|
||||
"runner": "none",
|
||||
"description": "Vollständige Prüfungsvorbereitung für ITSE (LF 9–12, WiSo, 40h-Projekt). 12 Module nach ZPA Nord-West 2. Auflage: DIN VDE 0100 Schutzmaßnahmen, Netzformen (TN/TT/IT), USV-Dimensionierung & VFI, Überspannungsschutz Typ 1-3, Strukturierte Verkabelung (EN 50173), LWL Singlemode/Multimode, Fusionsspleißen & OTDR-Messtechnik, DGUV V3 Prüfungen (VDE 0701/0702), Fehlersuche, EMV & Vollsimulation.",
|
||||
"tracks": [
|
||||
{
|
||||
"id": "track_1_elektrotechnik_und_usv",
|
||||
"title": "🌱 Lehrpfad 1: Elektrotechnik, Netzformen & USV-Anlagen (LF 9 & 10)",
|
||||
"description": "DIN VDE 0100 Schutzmaßnahmen, Netzformen (TN-C, TN-S, TT, IT), RCD-Dimensionierung, Leistungsberechnung (P, S, cos phi), USV-Klassen (VFD, VI, VFI) & Blitzschutzzonen (LPZ 0-3).",
|
||||
"certificateKey": "ihk_ap2_itse_strom",
|
||||
"chapters": [
|
||||
{
|
||||
"folder": "01_elektrotechnik_und_schutzmassnahmen_vde0100",
|
||||
"title": "LF 9: Elektrotechnische Grundlagen & Schutzmaßnahmen (DIN VDE 0100)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
},
|
||||
{
|
||||
"folder": "02_stromversorgung_und_usv_anlagen",
|
||||
"title": "LF 10: Stromversorgungs- & USV-Anlagen Projektierung (VFI, Scheinleistung)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
},
|
||||
{
|
||||
"folder": "03_blitzschutz_ueberspannung_und_erdung",
|
||||
"title": "LF 10: Blitzschutz, Überspannungsschutz & Erdungskonzepte (LPZ, Typ 1-3)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "track_2_leitungsnetze_und_lwl",
|
||||
"title": "⚙️ Lehrpfad 2: Leitungsnetze, Glasfaser & Messtechnik (LF 10 & 11)",
|
||||
"description": "Strukturierte Verkabelung nach DIN EN 50173, Kupfer Cat.6A/7, LWL Singlemode (OS2) vs. Multimode (OM3-5), Fusionsspleißen, OTDR-Kurvenanalyse & optisches Dämpfungsbudget.",
|
||||
"certificateKey": "ihk_ap2_itse_lwl",
|
||||
"chapters": [
|
||||
{
|
||||
"folder": "04_strukturierte_verkabelung_kupfer",
|
||||
"title": "LF 11: Strukturierte Verkabelung & Kupfer-Messtechnik (Cat.6A/7, NEXT)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
},
|
||||
{
|
||||
"folder": "05_lichtwellenleiter_und_spleisstechnik",
|
||||
"title": "LF 11: Lichtwellenleiter (LWL) & Fusionsspleißen (OS2 vs. OM3-5)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
},
|
||||
{
|
||||
"folder": "06_optische_messtechnik_und_otdr",
|
||||
"title": "LF 11: Optische Messtechnik & Dämpfungsbudget (OTDR, Fresnel-Reflexion)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "track_3_dguv_instandhaltung_und_funk",
|
||||
"title": "☁️ Lehrpfad 3: DGUV V3 Prüfungen, Instandhaltung & Funk (LF 9 & 12)",
|
||||
"description": "Wiederkehrende Prüfungen elektrischer Geräte nach DGUV Vorschrift 3 (DIN EN 50678 / VDE 0701), systematische Fehlersuche in Netzen & Funkübertragung / Antennentechnik (EIRP, EMV).",
|
||||
"certificateKey": "ihk_ap2_itse_instandhaltung",
|
||||
"chapters": [
|
||||
{
|
||||
"folder": "07_dguv_v3_pruefungen_vde0701",
|
||||
"title": "LF 9: Geräteprüfung nach DGUV Vorschrift 3 (VDE 0701/0702, R_PE, R_ISO)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
},
|
||||
{
|
||||
"folder": "08_fehlersuche_in_it_und_tk_systemen",
|
||||
"title": "LF 9: Systematische Fehlersuche in IT- & TK-Infrastrukturen (TDR, OSI)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
},
|
||||
{
|
||||
"folder": "09_funktechnik_antennen_und_emv",
|
||||
"title": "LF 12: Funkübertragung, Antennentechnik & EMV (EIRP, Richtfunk)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "track_4_arbeitssicherheit_projekt_und_pruefung",
|
||||
"title": "🏆 Lehrpfad 4: Sicherheit, 40h-Projektarbeit & AP2-Prüfungssimulation",
|
||||
"description": "Die 5 Sicherheitsregeln der Elektrotechnik, ElektroG/BattG, Leitfaden für die 40h-Projektarbeit samt Montage/VDE-Prüfung & Vollprüfungssimulation.",
|
||||
"certificateKey": "ihk_ap2_itse_master",
|
||||
"chapters": [
|
||||
{
|
||||
"folder": "10_arbeitssicherheit_umwelt_und_wiso",
|
||||
"title": "WiSo: Arbeitssicherheit (5 Sicherheitsregeln) & ElektroG (BattG, WEEE)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
},
|
||||
{
|
||||
"folder": "11_betriebliche_projektarbeit_guide_40h",
|
||||
"title": "Projekt: Betriebliche Projektarbeit (40h) & Fachgespräch (Guide ITSE)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
},
|
||||
{
|
||||
"folder": "12_ap2_itse_gesamtpruefung_simulation",
|
||||
"title": "Abschluss: AP2 ITSE Gesamtprüfungssimulation (PB 1, PB 2 & WiSo)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"ihk_ap2_itsm": {
|
||||
"id": "ihk_ap2_itsm",
|
||||
"title": "IHK AP2 Crashkurs: Kaufleute für IT-System-Management",
|
||||
"language": "ihk_ap2_itsm",
|
||||
"icon": "💼",
|
||||
"runner": "none",
|
||||
"description": "Vollständige Prüfungsvorbereitung für ITSM (LF 9–12, WiSo, 40h-Projekt). 12 Module nach ZPA Nord-West 2. Auflage: IT-Marktanalyse, SWOT & 7P Marketing-Mix, TCO/ROI, B2B-Vertrieb & Buying Center, Einwandbehandlung & Harvard-Konzept, CRM, Handelskalkulation (LEP bis BVP), IT-Vertragsrecht (Werk-/Dienstvertrag, EVB-IT), Service Level Agreements (SLAs, 99,9%), ITIL v4 & Vollsimulation.",
|
||||
"tracks": [
|
||||
{
|
||||
"id": "track_1_maerkte_marketing_tco",
|
||||
"title": "🌱 Lehrpfad 1: IT-Märkte, Marketing-Mix & Wirtschaftlichkeit (LF 9 & 10)",
|
||||
"description": "Marktforschung, SWOT-Analyse, BCG-Matrix, 7P-Dienstleistungsmarketing, Total Cost of Ownership (TCO), ROI & statische Amortisationsrechnung.",
|
||||
"certificateKey": "ihk_ap2_itsm_marketing",
|
||||
"chapters": [
|
||||
{
|
||||
"folder": "01_it_marktanalyse_wettbewerb_und_swot",
|
||||
"title": "LF 9: IT-Marktanalyse, Wettbewerb & SWOT-Analyse (BCG-Matrix)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
},
|
||||
{
|
||||
"folder": "02_marketing_mix_fuer_it_dienstleistungen",
|
||||
"title": "LF 10: Der Marketing-Mix für IT-Dienstleistungen (7P, Lead Nurturing)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
},
|
||||
{
|
||||
"folder": "03_investitions_und_wirtschaftlichkeitsrechnung",
|
||||
"title": "LF 9: Investitions- & Wirtschaftlichkeitsrechnung (TCO, ROI, Amortisation)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "track_2_vertrieb_crm_akquise",
|
||||
"title": "⚙️ Lehrpfad 2: B2B-Vertrieb, CRM & Kundenakquise (LF 10 & 11)",
|
||||
"description": "B2B-Vertriebszyklus, Buying Center Rollen, BANT-Qualifizierung, Fragetechniken, Einwandbehandlung (Bumerang), Harvard-Verhandlung & CRM-Kundenbindung.",
|
||||
"certificateKey": "ihk_ap2_itsm_vertrieb",
|
||||
"chapters": [
|
||||
{
|
||||
"folder": "04_b2b_vertriebsprozess_und_lead_management",
|
||||
"title": "LF 10: Der B2B-Vertriebsprozess & Buying Center Analyse (BANT)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
},
|
||||
{
|
||||
"folder": "05_kundenakquise_einwandbehandlung_und_verhandlung",
|
||||
"title": "LF 11: Kundenakquise, Einwandbehandlung & Verhandlungsführung (Harvard)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
},
|
||||
{
|
||||
"folder": "06_crm_systeme_und_kundenbindung",
|
||||
"title": "LF 11: CRM-Systeme, Customer Journey & Kundenbindung (CLV, Churn)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "track_3_kalkulation_recht_slas",
|
||||
"title": "☁️ Lehrpfad 3: Kalkulation, IT-Recht & Service Level Agreements (LF 11 & 12)",
|
||||
"description": "Kaufmännische Handelskalkulation (LEP bis BVP), Deckungsbeitrag, Break-Even-Point, BGB-Vertragstypen (Werk vs. Dienst), EVB-IT, SLAs (99,9%) & ITIL v4.",
|
||||
"certificateKey": "ihk_ap2_itsm_kalkulation",
|
||||
"chapters": [
|
||||
{
|
||||
"folder": "07_handelskalkulation_und_deckungsbeitrag",
|
||||
"title": "LF 11: Handelskalkulation & Deckungsbeitragsrechnung (Vorwärtskalkulation)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
},
|
||||
{
|
||||
"folder": "08_it_vertragsrecht_und_evb_it",
|
||||
"title": "LF 12: IT-Vertragsrecht, BGB-Vertragstypen & EVB-IT (Werk- vs. Dienstvertrag)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
},
|
||||
{
|
||||
"folder": "09_service_level_agreements_und_itil",
|
||||
"title": "LF 12: Service Level Agreements (SLAs) & ITIL Service Management",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "track_4_controlling_projekt_und_pruefung",
|
||||
"title": "🏆 Lehrpfad 4: Controlling, 40h-Projektarbeit & AP2-Prüfungssimulation",
|
||||
"description": "Finanzierungsarten (Leasing, Factoring), Liquidität, Leitfaden für die 40h-Projektarbeit samt Vermarktungskonzept & Vollprüfungssimulation.",
|
||||
"certificateKey": "ihk_ap2_itsm_master",
|
||||
"chapters": [
|
||||
{
|
||||
"folder": "10_it_controlling_finanzierung_und_wiso",
|
||||
"title": "WiSo: Finanzierungsformen (Leasing vs. Kauf), Bilanz & WiSo Arbeitsrecht",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
},
|
||||
{
|
||||
"folder": "11_betriebliche_projektarbeit_guide_40h",
|
||||
"title": "Projekt: Betriebliche Projektarbeit (40h) & Fachgespräch (Guide ITSM)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
},
|
||||
{
|
||||
"folder": "12_ap2_itsm_gesamtpruefung_simulation",
|
||||
"title": "Abschluss: AP2 ITSM Gesamtprüfungssimulation (PB 1, PB 2 & WiSo)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"ihk_ap2_kdm": {
|
||||
"id": "ihk_ap2_kdm",
|
||||
"title": "IHK AP2 Crashkurs: Kaufleute für Digitalisierungsmanagement",
|
||||
"language": "ihk_ap2_kdm",
|
||||
"icon": "📈",
|
||||
"runner": "none",
|
||||
"description": "Vollständige Prüfungsvorbereitung für KDM (LF 9–12, WiSo, 40h-Projekt). 12 Module nach ZPA Nord-West 2. Auflage: Digitale Geschäftsmodelle, Business Model Canvas (BMC), Plattformökonomie & Netzwerkeffekte, Datenmonetarisierung, BPMN 2.0 Prozessdigitalisierung, ERP-Modulverzahnung, GoBD-DMS & E-Invoicing (ZUGFeRD/XRechnung), Change Management (Kotter/Lewin), Controlling & KPIs, Datenschutz-Folgenabschätzung (DSFA Art. 35 DSGVO), EU AI Act & Vollsimulation.",
|
||||
"tracks": [
|
||||
{
|
||||
"id": "track_1_geschaeftsmodelle_und_plattformen",
|
||||
"title": "🌱 Lehrpfad 1: Digitale Geschäftsmodelle & Plattformökonomie (LF 9 & 11)",
|
||||
"description": "Business Model Canvas (BMC), Subscription Economy, direkte & indirekte Netzwerkeffekte, Metcalfe'sches Gesetz, Hühnchen-Ei-Problem & Datenmonetarisierung.",
|
||||
"certificateKey": "ihk_ap2_kdm_models",
|
||||
"chapters": [
|
||||
{
|
||||
"folder": "01_digitale_geschaeftsmodelle_und_bmc",
|
||||
"title": "LF 9: Analyse & Transformation digitaler Geschäftsmodelle (BMC)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
},
|
||||
{
|
||||
"folder": "02_plattformoekonomie_und_netzwerkeffekte",
|
||||
"title": "LF 11: Plattformökonomie, Netzwerkeffekte & Disruption (Metcalfe)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
},
|
||||
{
|
||||
"folder": "03_datenwerterschoepfung_und_datenmonetarisierung",
|
||||
"title": "LF 9: Datenökonomie & Monetarisierung betrieblicher Daten (DIKW)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "track_2_prozesse_erp_change",
|
||||
"title": "⚙️ Lehrpfad 2: Prozessdigitalisierung, ERP & Workflows (LF 9 & 10)",
|
||||
"description": "Geschäftsprozess-Digitalisierung in BPMN 2.0, Medienbrüche, RPA vs. API, ERP-Architekturen, GoBD-konformes DMS/ECM & E-Invoicing (ZUGFeRD/XRechnung nach EN 16931).",
|
||||
"certificateKey": "ihk_ap2_kdm_processes",
|
||||
"chapters": [
|
||||
{
|
||||
"folder": "04_geschaeftsprozess_digitalisierung_bpmn",
|
||||
"title": "LF 10: Geschäftsprozess-Digitalisierung in BPMN 2.0 (RPA, Medienbrüche)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
},
|
||||
{
|
||||
"folder": "05_erp_und_dokumentenmanagement_dms",
|
||||
"title": "LF 10: ERP-Systeme, GoBD-DMS & E-Invoicing (ZUGFeRD/XRechnung)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
},
|
||||
{
|
||||
"folder": "06_change_management_und_agile_methoden",
|
||||
"title": "LF 10: Change Management & Agiles Transformationsmanagement (Kotter/Lewin)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "track_3_controlling_dsfa_compliance",
|
||||
"title": "☁️ Lehrpfad 3: Business Analytics, Controlling & Compliance (LF 11 & 12)",
|
||||
"description": "Datengetriebenes Controlling, Straight-Through-Processing (STP), Cost per Transaction, Datenschutz-Folgenabschätzung (DSFA Art. 35), k-Anonymität & EU AI Act.",
|
||||
"certificateKey": "ihk_ap2_kdm_controlling",
|
||||
"chapters": [
|
||||
{
|
||||
"folder": "07_datengetriebenes_controlling_und_kpis",
|
||||
"title": "LF 11: Datengetriebenes Controlling & KPI-Dashboards (STP-Rate, IBCS)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
},
|
||||
{
|
||||
"folder": "08_datenschutz_folgenabschaetzung_und_ai_act",
|
||||
"title": "LF 12: Datenschutz-Folgenabschätzung (DSFA) & EU AI Act (Risikoklassen)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
},
|
||||
{
|
||||
"folder": "09_it_sicherheitsmanagement_und_risiko",
|
||||
"title": "LF 12: IT-Sicherheitsmanagement & Risikobeurteilung (BSI, R = E * S)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "track_4_nachhaltigkeit_projekt_und_pruefung",
|
||||
"title": "🏆 Lehrpfad 4: Nachhaltigkeit, 40h-Projektarbeit & AP2-Prüfungssimulation",
|
||||
"description": "Green IT, PUE-Wert, Mitbestimmung des Betriebsrats (§ 87 BetrVG), Leitfaden für die 40h-Projektarbeit zur Prozessdigitalisierung & Vollprüfungssimulation.",
|
||||
"certificateKey": "ihk_ap2_kdm_master",
|
||||
"chapters": [
|
||||
{
|
||||
"folder": "10_nachhaltigkeit_green_it_und_wiso",
|
||||
"title": "WiSo: Nachhaltigkeit (Green IT), Ethik & Mitbestimmung (§ 87 BetrVG)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
},
|
||||
{
|
||||
"folder": "11_betriebliche_projektarbeit_guide_40h",
|
||||
"title": "Projekt: Betriebliche Projektarbeit (40h) & Fachgespräch (Guide KDM)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
},
|
||||
{
|
||||
"folder": "12_ap2_kdm_gesamtpruefung_simulation",
|
||||
"title": "Abschluss: AP2 KDM Gesamtprüfungssimulation (PB 1, PB 2 & WiSo)",
|
||||
"taskFile": "README.md",
|
||||
"testFile": "index.html"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -6,33 +6,33 @@
|
|||
|
||||
(function () {
|
||||
const DEFAULT_LEVELS = [
|
||||
{ level: 1, min_xp: 0, max_xp: 150, titel: "Code-Küken 🐣", rang: "Bronze I" },
|
||||
{ level: 2, min_xp: 151, max_xp: 400, titel: "Code-Detektiv 🔍", rang: "Bronze II" },
|
||||
{ level: 3, min_xp: 401, max_xp: 800, titel: "Logik-Bastler ⚙️", rang: "Silber I" },
|
||||
{ level: 4, min_xp: 801, max_xp: 1300, titel: "Klassen-Baumeister 🏗️", rang: "Silber II" },
|
||||
{ level: 5, min_xp: 1301, max_xp: 2000, titel: "Git-Stratege 🌿", rang: "Gold I" },
|
||||
{ level: 6, min_xp: 2001, max_xp: 2900, titel: "TDD-Qualitätsprüfer 🧪", rang: "Gold II" },
|
||||
{ level: 7, min_xp: 2901, max_xp: 4000, titel: "Vererbungs-Meister 🧬", rang: "Platin I" },
|
||||
{ level: 8, min_xp: 4001, max_xp: 5300, titel: "Exception-Wächter 🛡️", rang: "Platin II" },
|
||||
{ level: 9, min_xp: 5301, max_xp: 6800, titel: "GUI-Entwickler 🖥️", rang: "Diamant" },
|
||||
{ level: 10, min_xp: 6801, max_xp: 99999, titel: "Software-Architekt 🏆", rang: "Großmeister" }
|
||||
{ level: 1, min_xp: 0, max_xp: 150, titel: "Junior IT-Trainee I: Syntax & Basistypen", rang: "Stufe 1" },
|
||||
{ level: 2, min_xp: 151, max_xp: 400, titel: "Junior IT-Trainee II: Kontrollfluss & Logik", rang: "Stufe 2" },
|
||||
{ level: 3, min_xp: 401, max_xp: 800, titel: "IT-Specialist: Funktionen & Datenstrukturen", rang: "Stufe 3" },
|
||||
{ level: 4, min_xp: 801, max_xp: 1300, titel: "Software Developer: OOP-Modellierung", rang: "Stufe 4" },
|
||||
{ level: 5, min_xp: 1301, max_xp: 2000, titel: "DevOps Engineer: Versionskontrolle & Tooling", rang: "Stufe 5" },
|
||||
{ level: 6, min_xp: 2001, max_xp: 2900, titel: "Quality Engineer: Test-Driven Development (TDD)", rang: "Stufe 6" },
|
||||
{ level: 7, min_xp: 2901, max_xp: 4000, titel: "Advanced Engineer: Entwurfsmuster & Polymorphie", rang: "Stufe 7" },
|
||||
{ level: 8, min_xp: 4001, max_xp: 5300, titel: "Systems Engineer: Security & Error Handling", rang: "Stufe 8" },
|
||||
{ level: 9, min_xp: 5301, max_xp: 6800, titel: "Application Engineer: Schnittstellen & UI", rang: "Stufe 9" },
|
||||
{ level: 10, min_xp: 6801, max_xp: 99999, titel: "Certified IT Systems Professional (IHK AP2)", rang: "Master-Stufe" }
|
||||
];
|
||||
|
||||
const DEFAULT_BADGES = {
|
||||
rechenkonegin: { icon: "🧮", name: "Rechenkönig", desc: "Grundlagen G01 & G02 abgeschlossen (Zahlen, Operatoren & Typen gemeistert)." },
|
||||
dialog_profi: { icon: "💬", name: "Dialog-Profi", desc: "Grundlagen G03 abgeschlossen (Interaktive Ein-/Ausgabe mit f-Strings)." },
|
||||
weichenstellerin: { icon: "🚦", name: "Weichensteller", desc: "Grundlagen G04 abgeschlossen (Bedingungslogik & Schaltjahre gemeistert)." },
|
||||
schleifen_dompteurin: { icon: "🎡", name: "Schleifen-Dompteur", desc: "Grundlagen G05 abgeschlossen (While- & For-Schleifen gebändigt)." },
|
||||
funktions_zauberin: { icon: "🪄", name: "Funktions-Zauberer", desc: "Grundlagen G06 abgeschlossen (Eigene Funktionen & Module gebaut)." },
|
||||
listen_archivarin: { icon: "📋", name: "Listen-Archivar", desc: "Grundlagen G07 & G08 abgeschlossen (Sequenzen & Strings analysiert)." },
|
||||
daten_strategin: { icon: "🗃️", name: "Daten-Stratege", desc: "Grundlagen G09 & G10 abgeschlossen (Dicts, Sets & Comprehensions gemeistert)." },
|
||||
bug_jaegerin: { icon: "🔍", name: "Bug-Jäger", desc: "Kapitel 00 gelöst (Alle Fehler-Bugs im Warm-up aufgespürt)." },
|
||||
erste_architektin: { icon: "🏗️", name: "Erster Architekt", desc: "Kapitel 01 bis 03 gelöst (Erste OOP-Klassen, Konstruktoren & Methoden)." },
|
||||
zeit_reisende: { icon: "🌿", name: "Git-Zeitreisender", desc: "Kapitel 04c Git-Versionskontrolle verstanden & Spielstände gesichert." },
|
||||
tamagotchi_mama: { icon: "🥚", name: "Tamagotchi-Hüter", desc: "Kapitel 06 Mini-Projekt abgeschlossen (Ein lebendiges Haustier gebaut)." },
|
||||
qualitaets_garantin: { icon: "🧪", name: "TDD-Qualitätsgarant", desc: "Kapitel 09 abgeschlossen (Eigene professionelle Unit Tests geschrieben)." },
|
||||
oop_grossmeisterin: { icon: "🧬", name: "Vererbungs-Koryphäe", desc: "Kapitel 10 bis 12 abgeschlossen (Vererbung, Polymorphie & Exceptions)." },
|
||||
software_architektin: { icon: "🏆", name: "Meister-Entwickler", desc: "Kapitel 16 Master-Projekt abgeschlossen & vollwertige Desktop-App gebaut!" }
|
||||
rechenkonegin: { icon: "🧮", name: "Zertifikat: Numerische Typen & Arithmetik", desc: "Grundlagen G01 & G02 abgeschlossen (Zahlen, Operatoren & Typen gemeistert)." },
|
||||
dialog_profi: { icon: "💬", name: "Zertifikat: Interaktive I/O & String-Interpolation", desc: "Grundlagen G03 abgeschlossen (Interaktive Ein-/Ausgabe mit f-Strings)." },
|
||||
weichenstellerin: { icon: "🚦", name: "Zertifikat: Verzweigungslogik & Boolesche Algebra", desc: "Grundlagen G04 abgeschlossen (Bedingungslogik & Kontrollfluss gemeistert)." },
|
||||
schleifen_dompteurin: { icon: "🎡", name: "Zertifikat: Iterationslogik & Sequenzverarbeitung", desc: "Grundlagen G05 abgeschlossen (While- & For-Schleifen gemeistert)." },
|
||||
funktions_zauberin: { icon: "📐", name: "Zertifikat: Modulare Funktionsarchitektur", desc: "Grundlagen G06 abgeschlossen (Modulare Funktionen & Parameterübergabe)." },
|
||||
listen_archivarin: { icon: "📋", name: "Zertifikat: Datenstrukturen: Listen & Slicing", desc: "Grundlagen G07 & G08 abgeschlossen (Sequenzen, Listen & Slicing analysiert)." },
|
||||
daten_strategin: { icon: "🗃️", name: "Zertifikat: Assoziative Arrays: Dicts & Sets", desc: "Grundlagen G09 & G10 abgeschlossen (Dicts, Sets & Hash-Lookup O(1))." },
|
||||
bug_jaegerin: { icon: "🔍", name: "Zertifikat: Systematisches Code-Debugging", desc: "Warm-up abgeschlossen (Fehleranalyse & Traceback-Debugging)." },
|
||||
erste_architektin: { icon: "🏗️", name: "Zertifikat: OOP-Grundlagen: Klassen & Kapselung", desc: "OOP 01 bis 03 gelöst (Klassendefinition, Konstruktoren & Instanzmethoden)." },
|
||||
zeit_reisende: { icon: "🌿", name: "Zertifikat: String-Repräsentation & Dunder-Methoden", desc: "OOP 04 gelöst (Spezielle Methoden __str__, __repr__ implementiert)." },
|
||||
tamagotchi_mama: { icon: "📦", name: "Zertifikat: State-Machine & Domänenmodellierung", desc: "OOP Mini-Projekt abgeschlossen (Zustandsverwaltung & Objektinteraktion)." },
|
||||
qualitaets_garantin: { icon: "🧪", name: "Zertifikat: Automatisierte Testsuites & TDD", desc: "OOP 09 abgeschlossen (Eigene professionelle Unit Tests mit Assertions)." },
|
||||
oop_grossmeisterin: { icon: "🧬", name: "Zertifikat: Fortgeschrittene OOP & Robustes Error-Handling", desc: "OOP 10 bis 12 abgeschlossen (Vererbung, Polymorphie & Exceptions)." },
|
||||
software_architektin: { icon: "🏆", name: "Zertifikat: Master-Projekt Software Engineering", desc: "Master-Projekt abgeschlossen & vollwertige Anwendungsarchitektur gebaut!" }
|
||||
};
|
||||
|
||||
function getGamificationData() {
|
||||
|
|
@ -47,8 +47,8 @@
|
|||
spieler_name: "Python-Entwickler",
|
||||
xp: 0,
|
||||
level: 1,
|
||||
titel: "Code-Küken 🐣",
|
||||
rang: "Bronze I",
|
||||
titel: "Junior IT-Trainee I: Syntax & Basistypen",
|
||||
rang: "Stufe 1",
|
||||
geloeste_kapitel: [],
|
||||
bestandene_tests: 0,
|
||||
freigeschaltete_badges: [],
|
||||
|
|
@ -112,7 +112,8 @@
|
|||
const hud = document.getElementById("gamification-hud");
|
||||
if (!hud) return;
|
||||
|
||||
const avatarIcon = currentLevelObj.titel.split(" ").pop() || "🐣";
|
||||
const avatarIcons = ["💻", "🔍", "⚙️", "🏗️", "🌿", "🧪", "🧬", "🛡️", "🖥️", "🏆"];
|
||||
const avatarIcon = avatarIcons[data.level - 1] || "💻";
|
||||
const solvedCount = (data.geloeste_kapitel || []).length;
|
||||
const totalChapters = 27;
|
||||
|
||||
|
|
|
|||
|
|
@ -11,13 +11,13 @@ window.GAMIFICATION_DATA = {
|
|||
"streak_tage": 1,
|
||||
"letzter_aktiver_tag": "2026-09-12",
|
||||
"historie": [],
|
||||
"zuletzt_aktualisiert": "2026-09-12T13:29:30.562924"
|
||||
"zuletzt_aktualisiert": "2026-09-15T15:18:26.060048"
|
||||
};
|
||||
window.GAMIFICATION_BADGES_KATALOG = {
|
||||
"rechenkonegin": {
|
||||
"id": "rechenkonegin",
|
||||
"icon": "🧮",
|
||||
"name": "Rechenkönigin",
|
||||
"name": "Zertifikat: Numerische Typen & Arithmetik",
|
||||
"desc": "Grundlagen 01 & 02 abgeschlossen (Zahlen, Operatoren & Typen gemeistert).",
|
||||
"xp_bonus": 100,
|
||||
"kriterium": [
|
||||
|
|
@ -28,7 +28,7 @@ window.GAMIFICATION_BADGES_KATALOG = {
|
|||
"dialog_profi": {
|
||||
"id": "dialog_profi",
|
||||
"icon": "💬",
|
||||
"name": "Dialog-Profi",
|
||||
"name": "Zertifikat: Interaktive I/O & String-Interpolation",
|
||||
"desc": "Grundlagen 03 abgeschlossen (Interaktive Ein-/Ausgabe mit f-Strings).",
|
||||
"xp_bonus": 75,
|
||||
"kriterium": [
|
||||
|
|
@ -38,8 +38,8 @@ window.GAMIFICATION_BADGES_KATALOG = {
|
|||
"weichenstellerin": {
|
||||
"id": "weichenstellerin",
|
||||
"icon": "🚦",
|
||||
"name": "Weichenstellerin",
|
||||
"desc": "Grundlagen 04 abgeschlossen (Bedingungslogik & Schaltjahre gemeistert).",
|
||||
"name": "Zertifikat: Verzweigungslogik & Boolesche Algebra",
|
||||
"desc": "Grundlagen 04 abgeschlossen (Bedingungslogik & Kontrollfluss gemeistert).",
|
||||
"xp_bonus": 75,
|
||||
"kriterium": [
|
||||
"lehrpfad_1_grundlagen/04_verzweigungen_und_bedingungen"
|
||||
|
|
@ -48,8 +48,8 @@ window.GAMIFICATION_BADGES_KATALOG = {
|
|||
"schleifen_dompteurin": {
|
||||
"id": "schleifen_dompteurin",
|
||||
"icon": "🎡",
|
||||
"name": "Schleifen-Dompteurin",
|
||||
"desc": "Grundlagen 05 abgeschlossen (While- & For-Schleifen gebändigt).",
|
||||
"name": "Zertifikat: Iterationslogik & Sequenzverarbeitung",
|
||||
"desc": "Grundlagen 05 abgeschlossen (While- & For-Schleifen gemeistert).",
|
||||
"xp_bonus": 100,
|
||||
"kriterium": [
|
||||
"lehrpfad_1_grundlagen/05_schleifen_und_wiederholungen"
|
||||
|
|
@ -57,9 +57,9 @@ window.GAMIFICATION_BADGES_KATALOG = {
|
|||
},
|
||||
"funktions_zauberin": {
|
||||
"id": "funktions_zauberin",
|
||||
"icon": "🪄",
|
||||
"name": "Funktions-Zauberin",
|
||||
"desc": "Grundlagen 06 abgeschlossen (Eigene Funktionen & Module gebaut).",
|
||||
"icon": "📐",
|
||||
"name": "Zertifikat: Modulare Funktionsarchitektur",
|
||||
"desc": "Grundlagen 06 abgeschlossen (Modulare Funktionen & Parameterübergabe).",
|
||||
"xp_bonus": 100,
|
||||
"kriterium": [
|
||||
"lehrpfad_1_grundlagen/06_funktionen_und_module"
|
||||
|
|
@ -68,8 +68,8 @@ window.GAMIFICATION_BADGES_KATALOG = {
|
|||
"listen_archivarin": {
|
||||
"id": "listen_archivarin",
|
||||
"icon": "📋",
|
||||
"name": "Listen-Archivarin",
|
||||
"desc": "Grundlagen 07 & 08 abgeschlossen (Sequenzen & Strings analysiert).",
|
||||
"name": "Zertifikat: Datenstrukturen: Listen & Slicing",
|
||||
"desc": "Grundlagen 07 & 08 abgeschlossen (Sequenzen, Listen & Slicing analysiert).",
|
||||
"xp_bonus": 120,
|
||||
"kriterium": [
|
||||
"lehrpfad_1_grundlagen/07_listen_und_sequenzen",
|
||||
|
|
@ -79,8 +79,8 @@ window.GAMIFICATION_BADGES_KATALOG = {
|
|||
"daten_strategin": {
|
||||
"id": "daten_strategin",
|
||||
"icon": "🗃️",
|
||||
"name": "Daten-Strategin",
|
||||
"desc": "Grundlagen 09 & 10 abgeschlossen (Dicts, Sets & Comprehensions gemeistert).",
|
||||
"name": "Zertifikat: Assoziative Arrays: Dicts & Sets",
|
||||
"desc": "Grundlagen 09 & 10 abgeschlossen (Dictionaries, Sets & Hash-Lookup O(1)).",
|
||||
"xp_bonus": 150,
|
||||
"kriterium": [
|
||||
"lehrpfad_1_grundlagen/09_dictionaries_und_sets",
|
||||
|
|
@ -90,8 +90,8 @@ window.GAMIFICATION_BADGES_KATALOG = {
|
|||
"bug_jaegerin": {
|
||||
"id": "bug_jaegerin",
|
||||
"icon": "🔍",
|
||||
"name": "Bug-Jägerin",
|
||||
"desc": "Kapitel 00 gelöst (Alle Fehler-Bugs im Warm-up aufgespürt).",
|
||||
"name": "Zertifikat: Systematisches Code-Debugging",
|
||||
"desc": "Warm-up abgeschlossen (Fehleranalyse & Traceback-Debugging).",
|
||||
"xp_bonus": 100,
|
||||
"kriterium": [
|
||||
"lehrpfad_2_oop_einstieg/00_fehlersuche_und_grundlagen"
|
||||
|
|
@ -100,8 +100,8 @@ window.GAMIFICATION_BADGES_KATALOG = {
|
|||
"erste_architektin": {
|
||||
"id": "erste_architektin",
|
||||
"icon": "🏗️",
|
||||
"name": "Erste Architektin",
|
||||
"desc": "Kapitel 01 bis 03 gelöst (Erste OOP-Klassen, Konstruktoren & Methoden).",
|
||||
"name": "Zertifikat: OOP-Grundlagen: Klassen & Kapselung",
|
||||
"desc": "OOP 01 bis 03 gelöst (Klassendefinition, Konstruktoren & Instanzmethoden).",
|
||||
"xp_bonus": 200,
|
||||
"kriterium": [
|
||||
"lehrpfad_2_oop_einstieg/01_einstieg_klassen",
|
||||
|
|
@ -111,9 +111,9 @@ window.GAMIFICATION_BADGES_KATALOG = {
|
|||
},
|
||||
"tamagotchi_mama": {
|
||||
"id": "tamagotchi_mama",
|
||||
"icon": "🥚",
|
||||
"name": "Tamagotchi-Mama",
|
||||
"desc": "Kapitel 06 Mini-Projekt abgeschlossen (Ein lebendiges Haustier gebaut).",
|
||||
"icon": "📦",
|
||||
"name": "Zertifikat: State-Machine & Domänenmodellierung",
|
||||
"desc": "OOP Mini-Projekt abgeschlossen (Zustandsverwaltung & Objektinteraktion).",
|
||||
"xp_bonus": 250,
|
||||
"kriterium": [
|
||||
"lehrpfad_2_oop_einstieg/06_abschlussprojekt_tamagotchi"
|
||||
|
|
@ -122,8 +122,8 @@ window.GAMIFICATION_BADGES_KATALOG = {
|
|||
"zeit_reisende": {
|
||||
"id": "zeit_reisende",
|
||||
"icon": "🌿",
|
||||
"name": "Git-Zeitreisende",
|
||||
"desc": "Kapitel 04c Git-Versionskontrolle verstanden & Spielstände gesichert.",
|
||||
"name": "Zertifikat: String-Repräsentation & Dunder-Methoden",
|
||||
"desc": "OOP 04 gelöst (Spezielle Methoden __str__, __repr__ implementiert).",
|
||||
"xp_bonus": 100,
|
||||
"kriterium": [
|
||||
"lehrpfad_2_oop_einstieg/04_str_und_darstellung"
|
||||
|
|
@ -132,8 +132,8 @@ window.GAMIFICATION_BADGES_KATALOG = {
|
|||
"qualitaets_garantin": {
|
||||
"id": "qualitaets_garantin",
|
||||
"icon": "🧪",
|
||||
"name": "TDD-Qualitätsgarantin",
|
||||
"desc": "Kapitel 09 abgeschlossen (Eigene professionelle Unit Tests geschrieben).",
|
||||
"name": "Zertifikat: Automatisierte Testsuites & TDD",
|
||||
"desc": "OOP 09 abgeschlossen (Eigene professionelle Unit Tests mit Assertions).",
|
||||
"xp_bonus": 200,
|
||||
"kriterium": [
|
||||
"lehrpfad_3_fortgeschrittenes_oop/09_eigene_unit_tests_schreiben"
|
||||
|
|
@ -142,8 +142,8 @@ window.GAMIFICATION_BADGES_KATALOG = {
|
|||
"oop_grossmeisterin": {
|
||||
"id": "oop_grossmeisterin",
|
||||
"icon": "🧬",
|
||||
"name": "Vererbungs-Koryphäe",
|
||||
"desc": "Kapitel 10 bis 12 abgeschlossen (Vererbung, Polymorphie & Exceptions).",
|
||||
"name": "Zertifikat: Fortgeschrittene OOP & Robustes Error-Handling",
|
||||
"desc": "OOP 10 bis 12 abgeschlossen (Vererbung, Polymorphie & Exceptions).",
|
||||
"xp_bonus": 300,
|
||||
"kriterium": [
|
||||
"lehrpfad_3_fortgeschrittenes_oop/10_vererbung_und_super",
|
||||
|
|
@ -154,8 +154,8 @@ window.GAMIFICATION_BADGES_KATALOG = {
|
|||
"software_architektin": {
|
||||
"id": "software_architektin",
|
||||
"icon": "🏆",
|
||||
"name": "Meister-Entwicklerin",
|
||||
"desc": "Kapitel 16 Master-Projekt abgeschlossen & vollwertige Desktop-App gebaut!",
|
||||
"name": "Zertifikat: Master-Projekt Software Engineering",
|
||||
"desc": "Master-Projekt abgeschlossen & vollwertige Anwendungsarchitektur gebaut!",
|
||||
"xp_bonus": 500,
|
||||
"kriterium": [
|
||||
"lehrpfad_3_fortgeschrittenes_oop/16_master_abschlussprojekt"
|
||||
|
|
@ -167,70 +167,70 @@ window.GAMIFICATION_LEVEL_STUFEN = [
|
|||
"level": 1,
|
||||
"min_xp": 0,
|
||||
"max_xp": 150,
|
||||
"titel": "Code-Küken 🐣",
|
||||
"rang": "Bronze I"
|
||||
"titel": "Junior IT-Trainee I: Syntax & Basistypen",
|
||||
"rang": "Stufe 1"
|
||||
},
|
||||
{
|
||||
"level": 2,
|
||||
"min_xp": 151,
|
||||
"max_xp": 400,
|
||||
"titel": "Code-Detektivin 🔍",
|
||||
"rang": "Bronze II"
|
||||
"titel": "Junior IT-Trainee II: Kontrollfluss & Logik",
|
||||
"rang": "Stufe 2"
|
||||
},
|
||||
{
|
||||
"level": 3,
|
||||
"min_xp": 401,
|
||||
"max_xp": 800,
|
||||
"titel": "Logik-Bastlerin ⚙️",
|
||||
"rang": "Silber I"
|
||||
"titel": "IT-Specialist: Funktionen & Datenstrukturen",
|
||||
"rang": "Stufe 3"
|
||||
},
|
||||
{
|
||||
"level": 4,
|
||||
"min_xp": 801,
|
||||
"max_xp": 1300,
|
||||
"titel": "Klassen-Baumeisterin 🏗️",
|
||||
"rang": "Silber II"
|
||||
"titel": "Software Developer: OOP-Modellierung",
|
||||
"rang": "Stufe 4"
|
||||
},
|
||||
{
|
||||
"level": 5,
|
||||
"min_xp": 1301,
|
||||
"max_xp": 2000,
|
||||
"titel": "Git-Strategin 🌿",
|
||||
"rang": "Gold I"
|
||||
"titel": "DevOps Engineer: Versionskontrolle & Tooling",
|
||||
"rang": "Stufe 5"
|
||||
},
|
||||
{
|
||||
"level": 6,
|
||||
"min_xp": 2001,
|
||||
"max_xp": 2900,
|
||||
"titel": "TDD-Qualitätsprüferin 🧪",
|
||||
"rang": "Gold II"
|
||||
"titel": "Quality Engineer: Test-Driven Development (TDD)",
|
||||
"rang": "Stufe 6"
|
||||
},
|
||||
{
|
||||
"level": 7,
|
||||
"min_xp": 2901,
|
||||
"max_xp": 4000,
|
||||
"titel": "Vererbungs-Meisterin 🧬",
|
||||
"rang": "Platin I"
|
||||
"titel": "Advanced Engineer: Entwurfsmuster & Polymorphie",
|
||||
"rang": "Stufe 7"
|
||||
},
|
||||
{
|
||||
"level": 8,
|
||||
"min_xp": 4001,
|
||||
"max_xp": 5300,
|
||||
"titel": "Exception-Wächterin 🛡️",
|
||||
"rang": "Platin II"
|
||||
"titel": "Systems Engineer: Security & Error Handling",
|
||||
"rang": "Stufe 8"
|
||||
},
|
||||
{
|
||||
"level": 9,
|
||||
"min_xp": 5301,
|
||||
"max_xp": 6800,
|
||||
"titel": "GUI-Entwicklerin 🖥️",
|
||||
"rang": "Diamant"
|
||||
"titel": "Application Engineer: Schnittstellen & UI",
|
||||
"rang": "Stufe 9"
|
||||
},
|
||||
{
|
||||
"level": 10,
|
||||
"min_xp": 6801,
|
||||
"max_xp": 99999,
|
||||
"titel": "Software-Architektin 🏆",
|
||||
"rang": "Großmeisterin"
|
||||
"titel": "Certified IT Systems Professional (IHK AP2)",
|
||||
"rang": "Master-Stufe"
|
||||
}
|
||||
];
|
||||
|
|
|
|||
298
assets/ihk_tools/beleg_kalkulator.js
Normal file
298
assets/ihk_tools/beleg_kalkulator.js
Normal file
|
|
@ -0,0 +1,298 @@
|
|||
/**
|
||||
* 🧾 IHK BELEG- & KALKULATIONS-TRAINER (PrintTop GmbH) 🧾
|
||||
* ====================================================
|
||||
* Interaktiver Trainer für kaufmännische Berechnungen nach ZPA 2. Auflage:
|
||||
* 1. Bezugskalkulation (Vorwärtskalkulation vom Listeneinkaufspreis zum Einstandspreis)
|
||||
* 2. Rechnungsprüfung & Skontoabzug bei Zahlung innerhalb Frist
|
||||
* 3. Verzugszinsberechnung nach § 288 BGB & 40 € Mahnpauschale (§ 288 Abs. 5 BGB)
|
||||
*/
|
||||
|
||||
class BelegKalkulator {
|
||||
constructor(containerId) {
|
||||
this.container = document.getElementById(containerId);
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
if (!this.container) return;
|
||||
this.render();
|
||||
}
|
||||
|
||||
render() {
|
||||
this.container.innerHTML = `
|
||||
<div class="ihk-tool-card" style="background: #1e293b; border: 1px solid #334155; border-radius: 12px; padding: 24px; margin-bottom: 24px;">
|
||||
<div class="ihk-tool-header" style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
|
||||
<div>
|
||||
<h3 style="color: #38bdf8; margin: 0; font-size: 1.3rem;">🧾 Interaktiver Beleg- & Kalkulationsrechner</h3>
|
||||
<p style="color: #94a3b8; font-size: 0.9rem; margin: 4px 0 0 0;">PrintTop GmbH • Original ZPA-Aufgabenmuster</p>
|
||||
</div>
|
||||
<div>
|
||||
<span class="ihk-badge ihk-badge-success">Kaufmännische IT-Praxis</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modus-Umschalter -->
|
||||
<div style="display: flex; gap: 10px; margin-bottom: 20px; border-bottom: 1px solid #334155; padding-bottom: 12px;">
|
||||
<button id="tab-bezug" class="ihk-btn ihk-btn-primary" style="padding: 6px 14px; font-size: 0.85rem;">📦 Bezugskalkulation (Einstandspreis)</button>
|
||||
<button id="tab-verzug" class="ihk-btn ihk-btn-secondary" style="padding: 6px 14px; font-size: 0.85rem;">⚖️ Verzugszinsen § 288 BGB</button>
|
||||
</div>
|
||||
|
||||
<!-- Ansicht 1: Bezugskalkulation -->
|
||||
<div id="view-bezug">
|
||||
<div style="background: #0f172a; padding: 16px; border-radius: 8px; margin-bottom: 18px; border: 1px solid #334155;">
|
||||
<strong style="color: #f8fafc;">Übung:</strong> Die PrintTop GmbH bestellt 10 Hochleistungsserver beim Großhändler.
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 12px; margin-top: 10px; font-size: 0.9rem; color: #cbd5e1;">
|
||||
<div>Listeneinkaufspreis (netto): <strong id="lbl-lep">10.000,00 €</strong></div>
|
||||
<div>Lieferrabatt: <strong id="lbl-rabatt">15 %</strong></div>
|
||||
<div>Lieferskonto: <strong id="lbl-skonto">2 %</strong></div>
|
||||
<div>Bezugskosten (Fracht/Zoll): <strong id="lbl-bezug">350,00 €</strong></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class="ihk-table" style="width: 100%; border-collapse: collapse; font-size: 0.9rem;">
|
||||
<thead>
|
||||
<tr style="background: #0f172a; color: #38bdf8; text-align: left;">
|
||||
<th style="padding: 10px; border: 1px solid #334155;">Kalkulationsschritt</th>
|
||||
<th style="padding: 10px; border: 1px solid #334155; width: 140px;">Prozentsatz</th>
|
||||
<th style="padding: 10px; border: 1px solid #334155; width: 180px;">Ihre Eingabe (€)</th>
|
||||
<th style="padding: 10px; border: 1px solid #334155; width: 180px;">Prüfung</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="padding: 8px; border: 1px solid #334155;">Listeneinkaufspreis (LEP)</td>
|
||||
<td style="padding: 8px; border: 1px solid #334155;">100 %</td>
|
||||
<td style="padding: 8px; border: 1px solid #334155;"><input type="text" id="val-lep" class="ihk-input" value="10000.00" disabled style="background:#1e293b;"></td>
|
||||
<td style="padding: 8px; border: 1px solid #334155; color: #4ade80;">✓ Gegeben</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 8px; border: 1px solid #334155;">- Lieferrabatt</td>
|
||||
<td style="padding: 8px; border: 1px solid #334155;">15 %</td>
|
||||
<td style="padding: 8px; border: 1px solid #334155;"><input type="number" step="0.01" id="val-rabatt-eur" class="ihk-input" placeholder="z. B. 1500.00"></td>
|
||||
<td id="status-rabatt-eur" style="padding: 8px; border: 1px solid #334155;">-</td>
|
||||
</tr>
|
||||
<tr style="background: rgba(56, 189, 248, 0.05);">
|
||||
<td style="padding: 8px; border: 1px solid #334155; font-weight: bold;">= Zieleinkaufspreis (ZEP)</td>
|
||||
<td style="padding: 8px; border: 1px solid #334155;">85 %</td>
|
||||
<td style="padding: 8px; border: 1px solid #334155;"><input type="number" step="0.01" id="val-zep" class="ihk-input" placeholder="ZEP in €"></td>
|
||||
<td id="status-zep" style="padding: 8px; border: 1px solid #334155;">-</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 8px; border: 1px solid #334155;">- Lieferskonto</td>
|
||||
<td style="padding: 8px; border: 1px solid #334155;">2 % vom ZEP</td>
|
||||
<td style="padding: 8px; border: 1px solid #334155;"><input type="number" step="0.01" id="val-skonto-eur" class="ihk-input" placeholder="Skonto in €"></td>
|
||||
<td id="status-skonto-eur" style="padding: 8px; border: 1px solid #334155;">-</td>
|
||||
</tr>
|
||||
<tr style="background: rgba(56, 189, 248, 0.05);">
|
||||
<td style="padding: 8px; border: 1px solid #334155; font-weight: bold;">= Bareinkaufspreis (BEP)</td>
|
||||
<td style="padding: 8px; border: 1px solid #334155;">-</td>
|
||||
<td style="padding: 8px; border: 1px solid #334155;"><input type="number" step="0.01" id="val-bep" class="ihk-input" placeholder="BEP in €"></td>
|
||||
<td id="status-bep" style="padding: 8px; border: 1px solid #334155;">-</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 8px; border: 1px solid #334155;">+ Bezugskosten (Fracht, Verpackung)</td>
|
||||
<td style="padding: 8px; border: 1px solid #334155;">-</td>
|
||||
<td style="padding: 8px; border: 1px solid #334155;"><input type="number" step="0.01" id="val-bezug-eur" class="ihk-input" value="350.00" disabled style="background:#1e293b;"></td>
|
||||
<td style="padding: 8px; border: 1px solid #334155; color: #4ade80;">✓ Gegeben</td>
|
||||
</tr>
|
||||
<tr style="background: rgba(34, 197, 94, 0.1); font-weight: bold;">
|
||||
<td style="padding: 10px; border: 1px solid #334155; color: #4ade80;">= Bezugspreis / Einstandspreis</td>
|
||||
<td style="padding: 10px; border: 1px solid #334155;">-</td>
|
||||
<td style="padding: 10px; border: 1px solid #334155;"><input type="number" step="0.01" id="val-einstand" class="ihk-input" placeholder="Einstandspreis €" style="border-color: #22c55e;"></td>
|
||||
<td id="status-einstand" style="padding: 10px; border: 1px solid #334155;">-</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div style="display: flex; gap: 10px; margin-top: 16px;">
|
||||
<button class="ihk-btn ihk-btn-success" id="btn-check-bezug">✓ Eingaben prüfen</button>
|
||||
<button class="ihk-btn ihk-btn-secondary" id="btn-solve-bezug">💡 Lösung aufdecken</button>
|
||||
<button class="ihk-btn ihk-btn-secondary" id="btn-new-bezug">🎲 Neue Zufallszahlen</button>
|
||||
</div>
|
||||
<div id="box-bezug-res" class="ihk-result-box" style="margin-top: 16px;"></div>
|
||||
</div>
|
||||
|
||||
<!-- Ansicht 2: Verzugszinsen § 288 BGB -->
|
||||
<div id="view-verzug" style="display: none;">
|
||||
<div style="background: #0f172a; padding: 16px; border-radius: 8px; margin-bottom: 18px; border: 1px solid #334155;">
|
||||
<strong style="color: #f8fafc;">Gesetzliche Grundlagen nach BGB (§ 288):</strong>
|
||||
<p style="color: #cbd5e1; font-size: 0.9rem; margin: 8px 0 0 0; line-height: 1.6;">
|
||||
- <strong>Verbrauchergeschäft (B2C):</strong> Basiszinssatz + <strong>5 Prozentpunkte</strong>.<br>
|
||||
- <strong>Handelsgeschäft / Unternehmen (B2B):</strong> Basiszinssatz + <strong>9 Prozentpunkte</strong>.<br>
|
||||
- <strong>Mahnpauschale bei B2B (§ 288 Abs. 5 BGB):</strong> Pauschal <strong>40,00 €</strong> Verzugsschadenersatz (unabhängig von tatsächlichen Mahnkosten!).<br>
|
||||
- <strong>Zinsformel (Deutsche Zinsmethode 30/360):</strong> <code>Z = (Forderungsbetrag * Zinssatz * Verzugstage) / (100 * 360)</code>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 16px; margin-bottom: 20px;">
|
||||
<div class="ihk-input-group">
|
||||
<label>Rechnungsbetrag (Brutto):</label>
|
||||
<input type="number" id="vz-betrag" class="ihk-input" value="11900.00" step="100">
|
||||
</div>
|
||||
<div class="ihk-input-group">
|
||||
<label>Geschäftsart:</label>
|
||||
<select id="vz-art" class="ihk-select">
|
||||
<option value="B2B" selected>B2B (Handelsgeschäft zw. Firmen: +9 %-Pkt.)</option>
|
||||
<option value="B2C">B2C (Verbrauchergeschäft: +5 %-Pkt.)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="ihk-input-group">
|
||||
<label>Aktueller Basiszinssatz (§ 247 BGB):</label>
|
||||
<input type="number" id="vz-basiszins" class="ihk-input" value="3.62" step="0.01">
|
||||
</div>
|
||||
<div class="ihk-input-group">
|
||||
<label>Tage im Verzug:</label>
|
||||
<input type="number" id="vz-tage" class="ihk-input" value="45" min="1" max="360">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; gap: 10px; margin-bottom: 16px;">
|
||||
<button class="ihk-btn ihk-btn-success" id="btn-calc-verzug">🧮 Verzugszinsen & Gesamtforderung berechnen</button>
|
||||
</div>
|
||||
|
||||
<div id="vz-result-card" style="background: #0f172a; border: 1px solid #334155; border-radius: 8px; padding: 18px; display: none;">
|
||||
<h4 style="color: #38bdf8; margin: 0 0 12px 0;">Ergebnis der Zinsberechnung:</h4>
|
||||
<div id="vz-result-content" style="color: #cbd5e1; font-size: 0.92rem; line-height: 1.8;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
this.attachEvents();
|
||||
}
|
||||
|
||||
attachEvents() {
|
||||
const tabBezug = document.getElementById("tab-bezug");
|
||||
const tabVerzug = document.getElementById("tab-verzug");
|
||||
const viewBezug = document.getElementById("view-bezug");
|
||||
const viewVerzug = document.getElementById("view-verzug");
|
||||
|
||||
tabBezug.addEventListener("click", () => {
|
||||
tabBezug.className = "ihk-btn ihk-btn-primary";
|
||||
tabVerzug.className = "ihk-btn ihk-btn-secondary";
|
||||
viewBezug.style.display = "block";
|
||||
viewVerzug.style.display = "none";
|
||||
});
|
||||
|
||||
tabVerzug.addEventListener("click", () => {
|
||||
tabVerzug.className = "ihk-btn ihk-btn-primary";
|
||||
tabBezug.className = "ihk-btn ihk-btn-secondary";
|
||||
viewVerzug.style.display = "block";
|
||||
viewBezug.style.display = "none";
|
||||
});
|
||||
|
||||
document.getElementById("btn-check-bezug").addEventListener("click", () => this.checkBezug());
|
||||
document.getElementById("btn-solve-bezug").addEventListener("click", () => this.solveBezug());
|
||||
document.getElementById("btn-new-bezug").addEventListener("click", () => this.newBezugScenario());
|
||||
document.getElementById("btn-calc-verzug").addEventListener("click", () => this.calculateVerzug());
|
||||
}
|
||||
|
||||
getBezugValues() {
|
||||
const lep = 10000;
|
||||
const rabattP = 0.15;
|
||||
const skontoP = 0.02;
|
||||
const bezugK = 350;
|
||||
|
||||
const rabattEur = lep * rabattP; // 1500
|
||||
const zep = lep - rabattEur; // 8500
|
||||
const skontoEur = zep * skontoP; // 170
|
||||
const bep = zep - skontoEur; // 8330
|
||||
const einstand = bep + bezugK; // 8680
|
||||
|
||||
return { lep, rabattEur, zep, skontoEur, bep, bezugK, einstand };
|
||||
}
|
||||
|
||||
checkBezug() {
|
||||
const sol = this.getBezugValues();
|
||||
const uRabatt = parseFloat(document.getElementById("val-rabatt-eur").value);
|
||||
const uZep = parseFloat(document.getElementById("val-zep").value);
|
||||
const uSkonto = parseFloat(document.getElementById("val-skonto-eur").value);
|
||||
const uBep = parseFloat(document.getElementById("val-bep").value);
|
||||
const uEinstand = parseFloat(document.getElementById("val-einstand").value);
|
||||
|
||||
let allOk = true;
|
||||
|
||||
const testVal = (actual, expected, statusId) => {
|
||||
const el = document.getElementById(statusId);
|
||||
if (Math.abs(actual - expected) < 0.05) {
|
||||
el.innerHTML = `<span style="color:#4ade80;">✓ ${expected.toFixed(2)} €</span>`;
|
||||
} else {
|
||||
el.innerHTML = `<span style="color:#ef4444;">✕ Soll: ${expected.toFixed(2)} €</span>`;
|
||||
allOk = false;
|
||||
}
|
||||
};
|
||||
|
||||
testVal(uRabatt, sol.rabattEur, "status-rabatt-eur");
|
||||
testVal(uZep, sol.zep, "status-zep");
|
||||
testVal(uSkonto, sol.skontoEur, "status-skonto-eur");
|
||||
testVal(uBep, sol.bep, "status-bep");
|
||||
testVal(uEinstand, sol.einstand, "status-einstand");
|
||||
|
||||
const box = document.getElementById("box-bezug-res");
|
||||
box.style.display = "block";
|
||||
if (allOk) {
|
||||
box.innerHTML = `
|
||||
<div style="border-left: 4px solid #22c55e; padding-left: 12px; color: #4ade80;">
|
||||
<strong>🎉 Hervorragend!</strong> Alle 5 Kalkulationsschritte sind centgenau richtig berechnet.<br>
|
||||
Der Bezugspreis beträgt exakt <strong>${sol.einstand.toFixed(2)} €</strong>.
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
box.innerHTML = `
|
||||
<div style="border-left: 4px solid #ef4444; padding-left: 12px; color: #f87171;">
|
||||
<strong>⚠️ Abweichung entdeckt:</strong> Bitte prüfen Sie die rot markierten Zeilen. Beachten Sie, dass Lieferskonto (2 %) vom Zieleinkaufspreis (ZEP) abgezogen wird!
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
solveBezug() {
|
||||
const sol = this.getBezugValues();
|
||||
document.getElementById("val-rabatt-eur").value = sol.rabattEur.toFixed(2);
|
||||
document.getElementById("val-zep").value = sol.zep.toFixed(2);
|
||||
document.getElementById("val-skonto-eur").value = sol.skontoEur.toFixed(2);
|
||||
document.getElementById("val-bep").value = sol.bep.toFixed(2);
|
||||
document.getElementById("val-einstand").value = sol.einstand.toFixed(2);
|
||||
this.checkBezug();
|
||||
}
|
||||
|
||||
newBezugScenario() {
|
||||
this.solveBezug();
|
||||
}
|
||||
|
||||
calculateVerzug() {
|
||||
const betrag = parseFloat(document.getElementById("vz-betrag").value) || 0;
|
||||
const art = document.getElementById("vz-art").value;
|
||||
const basiszins = parseFloat(document.getElementById("vz-basiszins").value) || 0;
|
||||
const tage = parseInt(document.getElementById("vz-tage").value, 10) || 0;
|
||||
|
||||
const aufschlag = art === "B2B" ? 9.0 : 5.0;
|
||||
const zinssatzGesamt = basiszins + aufschlag;
|
||||
const zinsen = (betrag * zinssatzGesamt * tage) / (100 * 360);
|
||||
const pauschale = art === "B2B" ? 40.0 : 0.0;
|
||||
const gesamtForderung = betrag + zinsen + pauschale;
|
||||
|
||||
const card = document.getElementById("vz-result-card");
|
||||
const content = document.getElementById("vz-result-content");
|
||||
card.style.display = "block";
|
||||
|
||||
content.innerHTML = `
|
||||
<table style="width: 100%; border-collapse: collapse;">
|
||||
<tr><td style="padding: 4px 0;">Hauptforderung:</td><td style="text-align: right; font-weight: bold;">${betrag.toFixed(2)} €</td></tr>
|
||||
<tr><td style="padding: 4px 0;">Basiszinssatz:</td><td style="text-align: right;">${basiszins.toFixed(2)} %</td></tr>
|
||||
<tr><td style="padding: 4px 0;">Gesetzlicher Verzugszinsaufschlag (${art}):</td><td style="text-align: right;">+ ${aufschlag.toFixed(2)} %-Punkte</td></tr>
|
||||
<tr style="border-bottom: 1px solid #334155;"><td style="padding: 4px 0; font-weight: bold; color: #38bdf8;">= Verzugszinssatz p.a.:</td><td style="text-align: right; font-weight: bold; color: #38bdf8;">${zinssatzGesamt.toFixed(2)} %</td></tr>
|
||||
<tr><td style="padding: 4px 0;">Verzugstage (30/360 Methode):</td><td style="text-align: right;">${tage} Tage</td></tr>
|
||||
<tr><td style="padding: 4px 0; color: #facc15;">Berechnete Verzugszinsen:</td><td style="text-align: right; color: #facc15; font-weight: bold;">+ ${zinsen.toFixed(2)} €</td></tr>
|
||||
${art === "B2B" ? `<tr><td style="padding: 4px 0; color: #fb923c;">Mahnpauschale (§ 288 Abs. 5 BGB):</td><td style="text-align: right; color: #fb923c; font-weight: bold;">+ 40,00 €</td></tr>` : ""}
|
||||
<tr style="border-top: 2px solid #22c55e; font-size: 1.1rem;"><td style="padding: 8px 0; font-weight: bold; color: #4ade80;">Gesamtforderung an Schuldner:</td><td style="text-align: right; font-weight: bold; color: #4ade80;">${gesamtForderung.toFixed(2)} €</td></tr>
|
||||
</table>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
if (document.getElementById("beleg-kalkulator-container")) {
|
||||
window.belegKalkulator = new BelegKalkulator("beleg-kalkulator-container");
|
||||
}
|
||||
});
|
||||
172
assets/ihk_tools/bpmn_trainer.js
Normal file
172
assets/ihk_tools/bpmn_trainer.js
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
/**
|
||||
* 🔄 IHK BPMN 2.0 & EPK INTERAKTIVER TRAINER 🔄
|
||||
* ============================================
|
||||
* Interaktiver Trainer zur Geschäftsprozessmodellierung:
|
||||
* 1. Gateway-Logik (XOR, AND, OR) und Pfad-Entscheidungen
|
||||
* 2. Pool & Lane Regeln (Sequenzfluss vs. Nachrichtenfluss)
|
||||
* 3. EPK-Syntaxregeln (Ereignis-Funktions-Alternation, Konnektoren)
|
||||
*/
|
||||
|
||||
class BpmnTrainer {
|
||||
constructor(containerId) {
|
||||
this.container = document.getElementById(containerId);
|
||||
this.currentTask = 0;
|
||||
this.tasks = [
|
||||
{
|
||||
title: "Aufgabe 1: BPMN 2.0 Gateway-Auswahl für IT-Support",
|
||||
scenario: "Ein Kunde meldet ein Softwareproblem im Ticketsystem. Der First-Level-Support analysiert die Anfrage. Wenn das Problem durch Standardwissen gelöst werden kann, wird sofort eine Lösung an den Kunden geschickt. Falls Spezialwissen erforderlich ist, wird das Ticket an das Entwicklerteam eskaliert. Es wird EXAKT EINER dieser beiden Pfade gewählt.",
|
||||
question: "Welches BPMN 2.0 Gateway muss für diese Verzweigung eingesetzt werden?",
|
||||
options: [
|
||||
{ text: "Exklusives Gateway (XOR) - Raute mit 'X' oder leer", correct: true, hint: "Richtig! XOR (Exclusive OR) schlägt genau einen Alternativpfad basierend auf Bedingungen ein." },
|
||||
{ text: "Paralleles Gateway (AND) - Raute mit '+'", correct: false, hint: "Falsch. AND würde BEIDE Pfade gleichzeitig auslösen." },
|
||||
{ text: "Inklusives Gateway (OR) - Raute mit 'O'", correct: false, hint: "Falsch. OR erlaubt eine beliebige Kombination (1 bis alle Pfade)." },
|
||||
{ text: "Ereignisbasiertes Gateway - Raute mit Doppelkreis und Fünfeck", correct: false, hint: "Falsch. Ein event-basiertes Gateway wartet auf externe Ereignisse, nicht auf Datenbedingungen." }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Aufgabe 2: Sequenzfluss vs. Nachrichtenfluss über Pool-Grenzen",
|
||||
scenario: "Ein Onlineshop (Pool 'Webshop AG') kommuniziert mit einem externen Logistikdienstleister (Pool 'LogiTrans GmbH'). Eine Bestellung wurde abgeschlossen und soll an LogiTrans übertragen werden.",
|
||||
question: "Welche Verbindungslinie ist zwischen zwei getrennten Pools (Blackbox oder Whitebox) nach BPMN 2.0-Standard zulässig?",
|
||||
options: [
|
||||
{ text: "Durchgezogene Linie mit gefülltem Pfeil (Sequenzfluss)", correct: false, hint: "Regelverstoß! Ein Sequenzfluss darf NIEMALS eine Pool-Grenze überschreiten." },
|
||||
{ text: "Gestrichelte Linie mit offenem Pfeil und Kreis am Ursprung (Nachrichtenfluss)", correct: true, hint: "Perfekt! Nachrichtenflüsse (Message Flow) sind das EINZIGE Mittel zur Kommunikation zwischen getrennten Pools." },
|
||||
{ text: "Gepunktete Linie ohne Pfeilspitze (Assoziation)", correct: false, hint: "Assoziationen verbinden lediglich Datenobjekte oder Textanmerkungen mit Elementen." },
|
||||
{ text: "Doppelte durchgezogene Linie", correct: false, hint: "Dieses Symbol existiert im BPMN 2.0 Standard nicht." }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Aufgabe 3: EPK Syntaxregeln (Ereignisgesteuerte Prozesskette)",
|
||||
scenario: "Ein Auszubildender zeichnet eine EPK für die Serverüberwachung. Nach dem Ereignis 'Server ausgefallen' setzt er direkt einen XOR-Konnektor, um zwischen zwei Folgeaktionen zu verzweigen.",
|
||||
question: "Ist diese Konstruktion nach den formalen Syntaxregeln einer EPK zulässig?",
|
||||
options: [
|
||||
{ text: "Nein! Ein Ereignis hat keine Entscheidungskraft. Eine XOR- oder ODER-Verzweigung darf nur nach einer FUNKTION erfolgen.", correct: true, hint: "Hervorragend! Ein Ereignis ist ein passiver Zustand ohne logische Intelligenz. Nur Funktionen treffen Entscheidungen, die danach mit XOR/ODER verzweigen dürfen." },
|
||||
{ text: "Ja, jedes Element darf beliebig mit Konnektoren verzweigen.", correct: false, hint: "Falsch. Die Konnektorenregeln der EPK verbieten XOR/ODER direkt nach einem einzelnen Ereignis." },
|
||||
{ text: "Nein, Konnektoren dürfen in EPKs grundsätzlich nur zur Zusammenführung, nie zur Verzweigung genutzt werden.", correct: false, hint: "Falsch. Konnektoren können sowohl verzweigen als auch zusammenführen." },
|
||||
{ text: "Ja, solange das Ereignis ein Sechseck ist.", correct: false, hint: "Falsch. Die geometrische Form ändert nichts an den logischen Verknüpfungsregeln." }
|
||||
]
|
||||
}
|
||||
];
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
if (!this.container) return;
|
||||
this.render();
|
||||
}
|
||||
|
||||
render() {
|
||||
const t = this.tasks[this.currentTask];
|
||||
this.container.innerHTML = `
|
||||
<div class="ihk-tool-card" style="background: #1e293b; border: 1px solid #334155; border-radius: 12px; padding: 24px; margin-bottom: 24px;">
|
||||
<div class="ihk-tool-header" style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
|
||||
<div>
|
||||
<h3 style="color: #38bdf8; margin: 0; font-size: 1.3rem;">🔄 BPMN 2.0 & EPK Modellierungs-Trainer</h3>
|
||||
<p style="color: #94a3b8; font-size: 0.9rem; margin: 4px 0 0 0;">Frage ${this.currentTask + 1} von ${this.tasks.length}</p>
|
||||
</div>
|
||||
<div style="display: flex; gap: 8px;">
|
||||
${this.tasks.map((_, i) => `
|
||||
<button class="ihk-btn ${i === this.currentTask ? 'ihk-btn-primary' : 'ihk-btn-secondary'}" style="padding: 4px 10px; font-size: 0.8rem;" onclick="window.bpmnTrainer.switchTask(${i})">
|
||||
${i + 1}
|
||||
</button>
|
||||
`).join("")}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="background: #0f172a; padding: 16px; border-radius: 8px; border: 1px solid #334155; margin-bottom: 20px;">
|
||||
<h4 style="color: #facc15; margin: 0 0 8px 0; font-size: 1.05rem;">${t.title}</h4>
|
||||
<p style="color: #cbd5e1; font-size: 0.92rem; line-height: 1.6; margin: 0 0 12px 0;">
|
||||
${t.scenario}
|
||||
</p>
|
||||
<strong style="color: #38bdf8; font-size: 0.95rem;">${t.question}</strong>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; flex-direction: column; gap: 10px; margin-bottom: 20px;">
|
||||
${t.options.map((opt, i) => `
|
||||
<div id="bpmn-opt-${i}" style="background: #0f172a; border: 1px solid #334155; border-radius: 8px; padding: 14px 16px; cursor: pointer; transition: all 0.2s;" onclick="window.bpmnTrainer.selectOption(${i})">
|
||||
<label style="cursor: pointer; display: flex; align-items: center; gap: 12px; color: #f8fafc; font-size: 0.92rem;">
|
||||
<input type="radio" name="bpmn-radio" value="${i}" style="transform: scale(1.2);">
|
||||
<span>${opt.text}</span>
|
||||
</label>
|
||||
</div>
|
||||
`).join("")}
|
||||
</div>
|
||||
|
||||
<div style="display: flex; gap: 10px;">
|
||||
<button class="ihk-btn ihk-btn-success" id="btn-check-bpmn">✓ Antwort prüfen</button>
|
||||
<button class="ihk-btn ihk-btn-secondary" id="btn-next-bpmn" ${this.currentTask === this.tasks.length - 1 ? 'disabled' : ''}>Nächste Aufgabe →</button>
|
||||
</div>
|
||||
|
||||
<div id="bpmn-feedback" class="ihk-result-box" style="margin-top: 16px;"></div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.getElementById("btn-check-bpmn").addEventListener("click", () => this.check());
|
||||
document.getElementById("btn-next-bpmn").addEventListener("click", () => {
|
||||
if (this.currentTask < this.tasks.length - 1) {
|
||||
this.switchTask(this.currentTask + 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
selectOption(index) {
|
||||
const radios = document.getElementsByName("bpmn-radio");
|
||||
if (radios[index]) radios[index].checked = true;
|
||||
}
|
||||
|
||||
switchTask(index) {
|
||||
this.currentTask = index;
|
||||
this.render();
|
||||
}
|
||||
|
||||
check() {
|
||||
const t = this.tasks[this.currentTask];
|
||||
const radios = document.getElementsByName("bpmn-radio");
|
||||
let selected = -1;
|
||||
for (let i = 0; i < radios.length; i++) {
|
||||
if (radios[i].checked) {
|
||||
selected = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const box = document.getElementById("bpmn-feedback");
|
||||
box.style.display = "block";
|
||||
|
||||
if (selected === -1) {
|
||||
box.innerHTML = `<span style="color: #facc15;">⚠️ Bitte wählen Sie eine Antwort aus.</span>`;
|
||||
return;
|
||||
}
|
||||
|
||||
const chosen = t.options[selected];
|
||||
t.options.forEach((opt, idx) => {
|
||||
const card = document.getElementById(`bpmn-opt-${idx}`);
|
||||
if (opt.correct) {
|
||||
card.style.borderColor = "#22c55e";
|
||||
card.style.background = "rgba(34, 197, 94, 0.15)";
|
||||
} else if (idx === selected) {
|
||||
card.style.borderColor = "#ef4444";
|
||||
card.style.background = "rgba(239, 68, 68, 0.15)";
|
||||
}
|
||||
});
|
||||
|
||||
if (chosen.correct) {
|
||||
box.innerHTML = `
|
||||
<div style="border-left: 4px solid #22c55e; padding-left: 12px; color: #4ade80;">
|
||||
<strong>🎉 Ausgezeichnet!</strong><br>${chosen.hint}
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
box.innerHTML = `
|
||||
<div style="border-left: 4px solid #ef4444; padding-left: 12px; color: #f87171;">
|
||||
<strong>❌ Nicht ganz:</strong><br>${chosen.hint}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
if (document.getElementById("bpmn-trainer-container")) {
|
||||
window.bpmnTrainer = new BpmnTrainer("bpmn-trainer-container");
|
||||
}
|
||||
});
|
||||
95
assets/ihk_tools/confusion_matrix_calc.js
Normal file
95
assets/ihk_tools/confusion_matrix_calc.js
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
/**
|
||||
* Interaktiver Konfusionsmatrix- & ML-Kennzahlen-Rechner für FIDP
|
||||
* IHK ZPA Nord-West AP2
|
||||
*/
|
||||
(function() {
|
||||
function renderConfusionMatrix(containerId) {
|
||||
const container = document.getElementById(containerId);
|
||||
if (!container) return;
|
||||
|
||||
container.innerHTML = `
|
||||
<div style="background: #0f172a; border: 1px solid #334155; border-radius: 8px; padding: 20px; color: #f8fafc;">
|
||||
<h4 style="color: #38bdf8; margin-top: 0; margin-bottom: 12px;">📊 Interaktiver Konfusionsmatrix-Rechner (FIDP)</h4>
|
||||
<p style="color: #94a3b8; font-size: 0.9rem; margin-bottom: 16px;">
|
||||
Geben Sie die Werte der 2x2-Konfusionsmatrix ein, um Accuracy, Precision, Recall und F1-Score nach IHK-Formeln live zu berechnen:
|
||||
</p>
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px; max-width: 480px; margin-bottom: 20px;">
|
||||
<div style="background: #1e293b; padding: 12px; border-radius: 6px; border-left: 4px solid #22c55e;">
|
||||
<label style="font-size: 0.85rem; color: #4ade80; display: block; font-weight: 600;">True Positive (TP)</label>
|
||||
<input type="number" id="cm_tp" value="80" min="0" class="ihk-input" style="width: 100%; margin-top: 6px;" />
|
||||
</div>
|
||||
<div style="background: #1e293b; padding: 12px; border-radius: 6px; border-left: 4px solid #f59e0b;">
|
||||
<label style="font-size: 0.85rem; color: #fcd34d; display: block; font-weight: 600;">False Positive (FP - Alpha-Fehler)</label>
|
||||
<input type="number" id="cm_fp" value="20" min="0" class="ihk-input" style="width: 100%; margin-top: 6px;" />
|
||||
</div>
|
||||
<div style="background: #1e293b; padding: 12px; border-radius: 6px; border-left: 4px solid #ef4444;">
|
||||
<label style="font-size: 0.85rem; color: #f87171; display: block; font-weight: 600;">False Negative (FN - Beta-Fehler)</label>
|
||||
<input type="number" id="cm_fn" value="10" min="0" class="ihk-input" style="width: 100%; margin-top: 6px;" />
|
||||
</div>
|
||||
<div style="background: #1e293b; padding: 12px; border-radius: 6px; border-left: 4px solid #3b82f6;">
|
||||
<label style="font-size: 0.85rem; color: #60a5fa; display: block; font-weight: 600;">True Negative (TN)</label>
|
||||
<input type="number" id="cm_tn" value="890" min="0" class="ihk-input" style="width: 100%; margin-top: 6px;" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; background: #1e293b; padding: 16px; border-radius: 6px;">
|
||||
<div>
|
||||
<div style="color: #94a3b8; font-size: 0.8rem;">Genauigkeit (Accuracy)</div>
|
||||
<div id="res_acc" style="font-size: 1.3rem; font-weight: bold; color: #38bdf8;">-- %</div>
|
||||
<div style="font-size: 0.75rem; color: #64748b;">(TP + TN) / Gesamt</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style="color: #94a3b8; font-size: 0.8rem;">Präzision (Precision)</div>
|
||||
<div id="res_prec" style="font-size: 1.3rem; font-weight: bold; color: #fcd34d;">-- %</div>
|
||||
<div style="font-size: 0.75rem; color: #64748b;">TP / (TP + FP)</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style="color: #94a3b8; font-size: 0.8rem;">Trefferquote (Recall)</div>
|
||||
<div id="res_rec" style="font-size: 1.3rem; font-weight: bold; color: #4ade80;">-- %</div>
|
||||
<div style="font-size: 0.75rem; color: #64748b;">TP / (TP + FN)</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style="color: #94a3b8; font-size: 0.8rem;">Harmonisches Mittel (F1)</div>
|
||||
<div id="res_f1" style="font-size: 1.3rem; font-weight: bold; color: #c084fc;">--</div>
|
||||
<div style="font-size: 0.75rem; color: #64748b;">2 * (P * R) / (P + R)</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
function update() {
|
||||
const tp = parseFloat(document.getElementById('cm_tp').value) || 0;
|
||||
const fp = parseFloat(document.getElementById('cm_fp').value) || 0;
|
||||
const fn = parseFloat(document.getElementById('cm_fn').value) || 0;
|
||||
const tn = parseFloat(document.getElementById('cm_tn').value) || 0;
|
||||
const total = tp + fp + fn + tn;
|
||||
|
||||
if (total === 0) return;
|
||||
|
||||
const acc = ((tp + tn) / total) * 100;
|
||||
const prec = (tp + fp > 0) ? (tp / (tp + fp)) * 100 : 0;
|
||||
const rec = (tp + fn > 0) ? (tp / (tp + fn)) * 100 : 0;
|
||||
const p = prec / 100;
|
||||
const r = rec / 100;
|
||||
const f1 = (p + r > 0) ? (2 * (p * r) / (p + r)) : 0;
|
||||
|
||||
document.getElementById('res_acc').textContent = acc.toFixed(2) + ' %';
|
||||
document.getElementById('res_prec').textContent = prec.toFixed(2) + ' %';
|
||||
document.getElementById('res_rec').textContent = rec.toFixed(2) + ' %';
|
||||
document.getElementById('res_f1').textContent = f1.toFixed(3);
|
||||
}
|
||||
|
||||
['cm_tp', 'cm_fp', 'cm_fn', 'cm_tn'].forEach(id => {
|
||||
document.getElementById(id).addEventListener('input', update);
|
||||
});
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
window.renderConfusionMatrix = renderConfusionMatrix;
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
if (document.getElementById('confusion-matrix-app')) {
|
||||
renderConfusionMatrix('confusion-matrix-app');
|
||||
}
|
||||
});
|
||||
})();
|
||||
472
assets/ihk_tools/diagram_studio.css
Normal file
472
assets/ihk_tools/diagram_studio.css
Normal file
|
|
@ -0,0 +1,472 @@
|
|||
/* 🎨 DIAGRAM STUDIO (ER, STRUKTOGRAMM, UML) STYLES */
|
||||
|
||||
.ds-container {
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.ds-tab-nav {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
border-bottom: 1px solid #1e293b;
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
|
||||
.ds-tab-btn {
|
||||
background: #1e293b;
|
||||
color: #cbd5e1;
|
||||
border: 1px solid #334155;
|
||||
border-radius: 8px;
|
||||
padding: 10px 18px;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.ds-tab-btn:hover {
|
||||
background: #334155;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.ds-tab-btn.active {
|
||||
background: #1d4ed8;
|
||||
color: #ffffff;
|
||||
border-color: #38bdf8;
|
||||
box-shadow: 0 4px 12px rgba(29, 78, 216, 0.3);
|
||||
}
|
||||
|
||||
.ds-editor-card {
|
||||
background: #0f172a;
|
||||
border: 1px solid #1e293b;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.ds-toolbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background: #1e293b;
|
||||
border-bottom: 1px solid #334155;
|
||||
padding: 10px 16px;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.ds-toolbar-left, .ds-toolbar-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.ds-btn-group {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.ds-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 7px 12px;
|
||||
border-radius: 6px;
|
||||
font-size: 0.82rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
transition: all 0.15s ease;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.ds-btn-primary { background: #0284c7; color: #ffffff; }
|
||||
.ds-btn-primary:hover { background: #0369a1; }
|
||||
|
||||
.ds-btn-secondary { background: #334155; color: #f8fafc; border: 1px solid #475569; }
|
||||
.ds-btn-secondary:hover { background: #475569; }
|
||||
|
||||
.ds-btn-success { background: #059669; color: #ffffff; }
|
||||
.ds-btn-success:hover { background: #047857; }
|
||||
|
||||
.ds-btn-danger { background: rgba(239, 68, 68, 0.15); color: #f87171; border: 1px solid rgba(239, 68, 68, 0.3); }
|
||||
.ds-btn-danger:hover { background: rgba(239, 68, 68, 0.3); }
|
||||
|
||||
.ds-select-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 0.82rem;
|
||||
font-weight: 600;
|
||||
color: #cbd5e1;
|
||||
}
|
||||
|
||||
.ds-select {
|
||||
background: #0f172a;
|
||||
border: 1px solid #475569;
|
||||
border-radius: 6px;
|
||||
color: #f8fafc;
|
||||
padding: 5px 8px;
|
||||
font-size: 0.82rem;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.ds-select:focus {
|
||||
border-color: #38bdf8;
|
||||
}
|
||||
|
||||
.ds-input, .ds-textarea {
|
||||
width: 100%;
|
||||
background: #0f172a;
|
||||
border: 1px solid #334155;
|
||||
border-radius: 6px;
|
||||
color: #f8fafc;
|
||||
padding: 8px 10px;
|
||||
font-size: 0.86rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.ds-input:focus, .ds-textarea:focus {
|
||||
border-color: #38bdf8;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.ds-workspace-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 320px;
|
||||
height: 640px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.ds-canvas-wrap {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
background: #090d16;
|
||||
border-right: 1px solid #1e293b;
|
||||
}
|
||||
|
||||
.ds-sidebar {
|
||||
background: #0f172a;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.ds-sidebar-header {
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid #1e293b;
|
||||
background: rgba(255, 255, 255, 0.02);
|
||||
}
|
||||
|
||||
.ds-sidebar-header h4 {
|
||||
margin: 0;
|
||||
font-size: 0.92rem;
|
||||
color: #38bdf8;
|
||||
}
|
||||
|
||||
.ds-sidebar-body {
|
||||
padding: 16px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.ds-hint {
|
||||
font-size: 0.84rem;
|
||||
color: #94a3b8;
|
||||
line-height: 1.5;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.ds-field-group {
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.ds-field-group label {
|
||||
display: block;
|
||||
font-size: 0.82rem;
|
||||
font-weight: 600;
|
||||
color: #cbd5e1;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.ds-attr-card {
|
||||
background: #090d16;
|
||||
border: 1px solid #334155;
|
||||
border-radius: 6px;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.ds-icon-btn {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: #f87171;
|
||||
cursor: pointer;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.ds-modal {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(15, 23, 42, 0.85);
|
||||
backdrop-filter: blur(6px);
|
||||
z-index: 1000;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.ds-modal-content {
|
||||
background: #0f172a;
|
||||
border: 1px solid #334155;
|
||||
border-radius: 12px;
|
||||
max-width: 760px;
|
||||
width: 90%;
|
||||
max-height: 85vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: #f8fafc;
|
||||
padding: 22px;
|
||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.ds-modal-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #334155;
|
||||
padding-bottom: 12px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.ds-modal-header h3 {
|
||||
margin: 0;
|
||||
font-size: 1.12rem;
|
||||
color: #38bdf8;
|
||||
}
|
||||
|
||||
.ds-modal-close {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: #cbd5e1;
|
||||
font-size: 1.3rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ds-code-block {
|
||||
background: #060911;
|
||||
padding: 16px;
|
||||
border-radius: 6px;
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
font-family: monospace;
|
||||
font-size: 0.85rem;
|
||||
color: #f1f5f9;
|
||||
border: 1px solid #1e293b;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.ds-modal-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
📐 STRUKTOGRAMM (DIN 66261) CSS
|
||||
========================================================================== */
|
||||
|
||||
.sg-render-wrapper {
|
||||
padding: 28px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
min-width: 600px;
|
||||
}
|
||||
|
||||
.sg-container {
|
||||
width: 100%;
|
||||
max-width: 720px;
|
||||
background: #090d16;
|
||||
border: 2px solid #38bdf8;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.sg-header-title {
|
||||
background: #1e293b;
|
||||
color: #cbd5e1;
|
||||
text-align: center;
|
||||
font-size: 0.76rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.8px;
|
||||
padding: 6px 12px;
|
||||
border-bottom: 2px solid #38bdf8;
|
||||
}
|
||||
|
||||
.sg-root-table {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.sg-block {
|
||||
cursor: pointer;
|
||||
transition: background 0.12s ease;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.sg-block.sg-selected {
|
||||
outline: 2px solid #fbbf24;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.sg-instruction {
|
||||
background: #1e293b;
|
||||
border-bottom: 1px solid #334155;
|
||||
padding: 10px 14px;
|
||||
}
|
||||
|
||||
.sg-instruction:hover {
|
||||
background: #253349;
|
||||
}
|
||||
|
||||
.sg-text {
|
||||
font-family: monospace;
|
||||
font-size: 0.88rem;
|
||||
color: #f8fafc;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.sg-if {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-bottom: 1px solid #334155;
|
||||
background: #1e293b;
|
||||
}
|
||||
|
||||
.sg-if-header {
|
||||
position: relative;
|
||||
height: 48px;
|
||||
background: #0f172a;
|
||||
border-bottom: 1px solid #334155;
|
||||
}
|
||||
|
||||
.sg-if-svg {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.sg-if-condition {
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
font-family: monospace;
|
||||
font-size: 0.85rem;
|
||||
font-weight: bold;
|
||||
color: #38bdf8;
|
||||
z-index: 2;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.sg-if-label-left {
|
||||
position: absolute;
|
||||
bottom: 4px;
|
||||
left: 12px;
|
||||
font-size: 0.72rem;
|
||||
font-weight: 800;
|
||||
color: #4ade80;
|
||||
z-index: 2;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.sg-if-label-right {
|
||||
position: absolute;
|
||||
bottom: 4px;
|
||||
right: 12px;
|
||||
font-size: 0.72rem;
|
||||
font-weight: 800;
|
||||
color: #f87171;
|
||||
z-index: 2;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.sg-if-branches {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.sg-if-then {
|
||||
flex: 1;
|
||||
border-right: 1px solid #334155;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.sg-if-else {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.sg-while, .sg-for {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-bottom: 1px solid #334155;
|
||||
background: #1e293b;
|
||||
}
|
||||
|
||||
.sg-while-header, .sg-for-header {
|
||||
background: #0f172a;
|
||||
padding: 8px 14px;
|
||||
border-bottom: 1px solid #334155;
|
||||
font-family: monospace;
|
||||
font-size: 0.85rem;
|
||||
font-weight: bold;
|
||||
color: #38bdf8;
|
||||
}
|
||||
|
||||
.sg-badge-loop {
|
||||
color: #fbbf24;
|
||||
font-size: 0.76rem;
|
||||
font-weight: 800;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.sg-while-body-wrap {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.sg-while-gutter {
|
||||
width: 28px;
|
||||
background: #0f172a;
|
||||
border-right: 1px solid #334155;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.sg-while-body {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.sg-empty-block {
|
||||
padding: 8px;
|
||||
font-size: 0.75rem;
|
||||
color: #64748b;
|
||||
text-align: center;
|
||||
font-style: italic;
|
||||
}
|
||||
63
assets/ihk_tools/diagram_studio.js
Normal file
63
assets/ihk_tools/diagram_studio.js
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
/**
|
||||
* 🎨 IT-PRAXISPORTAL – DIAGRAMM STUDIO MAIN CONTROLLER
|
||||
* Schaltet nahtlos zwischen ER-Modell, Struktogramm (DIN 66261) und UML um.
|
||||
*/
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const tabEr = document.getElementById('tab-btn-er');
|
||||
const tabSg = document.getElementById('tab-btn-struktogramm');
|
||||
const tabUml = document.getElementById('tab-btn-uml');
|
||||
|
||||
const panelEr = document.getElementById('panel-er');
|
||||
const panelSg = document.getElementById('panel-struktogramm');
|
||||
const panelUml = document.getElementById('panel-uml');
|
||||
|
||||
let erInstance = null;
|
||||
let sgInstance = null;
|
||||
let umlInstance = null;
|
||||
|
||||
function switchTab(activeTab) {
|
||||
[tabEr, tabSg, tabUml].forEach(btn => btn && btn.classList.remove('active'));
|
||||
[panelEr, panelSg, panelUml].forEach(p => p && (p.style.display = 'none'));
|
||||
|
||||
if (activeTab === 'er') {
|
||||
tabEr.classList.add('active');
|
||||
panelEr.style.display = 'block';
|
||||
if (!erInstance) {
|
||||
erInstance = new EREditor('panel-er');
|
||||
} else {
|
||||
erInstance.render();
|
||||
}
|
||||
} else if (activeTab === 'struktogramm') {
|
||||
tabSg.classList.add('active');
|
||||
panelSg.style.display = 'block';
|
||||
if (!sgInstance) {
|
||||
sgInstance = new StruktogrammEditor('panel-struktogramm');
|
||||
} else {
|
||||
sgInstance.render();
|
||||
}
|
||||
} else if (activeTab === 'uml') {
|
||||
tabUml.classList.add('active');
|
||||
panelUml.style.display = 'block';
|
||||
if (!umlInstance) {
|
||||
umlInstance = new UMLEditor('panel-uml');
|
||||
} else {
|
||||
umlInstance.render();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tabEr) tabEr.addEventListener('click', () => switchTab('er'));
|
||||
if (tabSg) tabSg.addEventListener('click', () => switchTab('struktogramm'));
|
||||
if (tabUml) tabUml.addEventListener('click', () => switchTab('uml'));
|
||||
|
||||
// Handle URL hash parameters (z.B. #struktogramm oder #uml oder #er)
|
||||
const hash = window.location.hash.replace('#', '').toLowerCase();
|
||||
if (hash === 'struktogramm' || hash === 'sg') {
|
||||
switchTab('struktogramm');
|
||||
} else if (hash === 'uml') {
|
||||
switchTab('uml');
|
||||
} else {
|
||||
switchTab('er');
|
||||
}
|
||||
});
|
||||
683
assets/ihk_tools/er_editor.js
Normal file
683
assets/ihk_tools/er_editor.js
Normal file
|
|
@ -0,0 +1,683 @@
|
|||
/**
|
||||
* 🗄️ IT-PRAXISPORTAL – INTERAKTIVER ER-MODELL EDITOR (ENTITY RELATIONSHIP)
|
||||
* Unterstützt Entitäten, Attribute (PK, FK, NN, UQ), Relationen (1:1, 1:n, n:m),
|
||||
* Notationen (Krähenfuß, Chen, Min-Max), automatische SQL-DDL-Generierung und SVG-Export.
|
||||
*/
|
||||
|
||||
class EREditor {
|
||||
constructor(containerId, options = {}) {
|
||||
this.container = typeof containerId === 'string' ? document.getElementById(containerId) : containerId;
|
||||
this.options = options;
|
||||
this.entities = [];
|
||||
this.relations = [];
|
||||
this.selectedEntityId = null;
|
||||
this.notation = 'crowsfoot'; // 'crowsfoot' | 'chen' | 'minmax'
|
||||
this.draggedEntity = null;
|
||||
this.dragOffset = { x: 0, y: 0 };
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
if (!this.container) return;
|
||||
this.container.innerHTML = `
|
||||
<div class="ds-editor-card">
|
||||
<div class="ds-toolbar">
|
||||
<div class="ds-toolbar-left">
|
||||
<button type="button" class="ds-btn ds-btn-primary" id="er-btn-add-entity">
|
||||
<span>➕</span> <span>Entität hinzufügen</span>
|
||||
</button>
|
||||
<button type="button" class="ds-btn ds-btn-secondary" id="er-btn-add-relation">
|
||||
<span>🔗</span> <span>Beziehung anlegen</span>
|
||||
</button>
|
||||
<div class="ds-select-wrap">
|
||||
<label>Notation:</label>
|
||||
<select id="er-select-notation" class="ds-select">
|
||||
<option value="crowsfoot">Krähenfuß (Crow's Foot)</option>
|
||||
<option value="chen">Chen-Notation (1, c, m, mc)</option>
|
||||
<option value="minmax">Min-Max-Notation (0,1 / 1,n)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="ds-select-wrap">
|
||||
<label>Vorlage:</label>
|
||||
<select id="er-select-template" class="ds-select">
|
||||
<option value="">-- IHK-Vorlage wählen --</option>
|
||||
<option value="onlineshop">IHK Online-Shop (Kunde, Bestellung, Artikel)</option>
|
||||
<option value="asset_mgmt">IT-Asset Management (Server, Standort, Ticket)</option>
|
||||
<option value="schulung">IHK Ausbildungsbetrieb (Azubi, Projekt, Ausbilder)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ds-toolbar-right">
|
||||
<button type="button" class="ds-btn ds-btn-success" id="er-btn-generate-sql">
|
||||
<span>💾</span> <span>SQL DDL</span>
|
||||
</button>
|
||||
<button type="button" class="ds-btn ds-btn-secondary" id="er-btn-export-svg">
|
||||
<span>🖼️</span> <span>SVG Export</span>
|
||||
</button>
|
||||
<button type="button" class="ds-btn ds-btn-danger" id="er-btn-clear" title="Alles leeren">
|
||||
<span>🗑️</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ds-workspace-grid">
|
||||
<div class="ds-canvas-wrap" id="er-canvas-container">
|
||||
<svg id="er-svg-canvas" width="100%" height="100%"></svg>
|
||||
</div>
|
||||
<div class="ds-sidebar" id="er-sidebar">
|
||||
<div class="ds-sidebar-header">
|
||||
<h4 id="er-sidebar-title">Eigenschaften</h4>
|
||||
</div>
|
||||
<div class="ds-sidebar-body" id="er-sidebar-content">
|
||||
<p class="ds-hint">Wähle eine Entität oder Beziehung im Diagramm aus, um Eigenschaften und Attribute zu bearbeiten.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SQL DDL Modal -->
|
||||
<div id="er-sql-modal" class="ds-modal" style="display:none;">
|
||||
<div class="ds-modal-content">
|
||||
<div class="ds-modal-header">
|
||||
<h3>Generiertes SQL DDL Skript (ANSI SQL:2016)</h3>
|
||||
<button type="button" class="ds-modal-close" id="er-sql-modal-close">×</button>
|
||||
</div>
|
||||
<pre id="er-sql-code-display" class="ds-code-block"></pre>
|
||||
<div class="ds-modal-footer">
|
||||
<button type="button" class="ds-btn ds-btn-primary" id="er-sql-copy-btn">In Zwischenablage kopieren</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
this.bindEvents();
|
||||
this.loadTemplate('onlineshop');
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
document.getElementById('er-btn-add-entity').addEventListener('click', () => this.addEntity());
|
||||
document.getElementById('er-btn-add-relation').addEventListener('click', () => this.promptAddRelation());
|
||||
document.getElementById('er-select-notation').addEventListener('change', (e) => {
|
||||
this.notation = e.target.value;
|
||||
this.render();
|
||||
});
|
||||
document.getElementById('er-select-template').addEventListener('change', (e) => {
|
||||
if (e.target.value) this.loadTemplate(e.target.value);
|
||||
});
|
||||
document.getElementById('er-btn-generate-sql').addEventListener('click', () => this.showSqlModal());
|
||||
document.getElementById('er-sql-modal-close').addEventListener('click', () => {
|
||||
document.getElementById('er-sql-modal').style.display = 'none';
|
||||
});
|
||||
document.getElementById('er-sql-copy-btn').addEventListener('click', () => {
|
||||
const code = document.getElementById('er-sql-code-display').innerText;
|
||||
navigator.clipboard.writeText(code).then(() => {
|
||||
alert('SQL-Skript erfolgreich kopiert!');
|
||||
});
|
||||
});
|
||||
document.getElementById('er-btn-export-svg').addEventListener('click', () => this.exportSvg());
|
||||
document.getElementById('er-btn-clear').addEventListener('click', () => {
|
||||
if (confirm('Möchtest du das aktuelle ER-Modell wirklich leeren?')) {
|
||||
this.entities = [];
|
||||
this.relations = [];
|
||||
this.selectedEntityId = null;
|
||||
this.render();
|
||||
this.renderSidebar();
|
||||
}
|
||||
});
|
||||
|
||||
const canvas = document.getElementById('er-canvas-container');
|
||||
canvas.addEventListener('mousedown', (e) => this.onMouseDown(e));
|
||||
window.addEventListener('mousemove', (e) => this.onMouseMove(e));
|
||||
window.addEventListener('mouseup', () => this.onMouseUp());
|
||||
}
|
||||
|
||||
addEntity(name = 'tbl_neue_tabelle', x = 60, y = 60) {
|
||||
const id = 'ent_' + Date.now() + '_' + Math.floor(Math.random() * 1000);
|
||||
const entity = {
|
||||
id: id,
|
||||
name: name,
|
||||
x: x,
|
||||
y: y,
|
||||
width: 230,
|
||||
attributes: [
|
||||
{ name: 'id', type: 'INT', pk: true, fk: false, nn: true, uq: true },
|
||||
{ name: 'bezeichnung', type: 'VARCHAR(100)', pk: false, fk: false, nn: true, uq: false }
|
||||
]
|
||||
};
|
||||
this.entities.push(entity);
|
||||
this.selectEntity(id);
|
||||
this.render();
|
||||
}
|
||||
|
||||
selectEntity(id) {
|
||||
this.selectedEntityId = id;
|
||||
this.render();
|
||||
this.renderSidebar();
|
||||
}
|
||||
|
||||
promptAddRelation() {
|
||||
if (this.entities.length < 2) {
|
||||
alert('Du benötigst mindestens 2 Entitäten, um eine Beziehung herzustellen.');
|
||||
return;
|
||||
}
|
||||
const fromId = this.selectedEntityId || this.entities[0].id;
|
||||
const otherEntities = this.entities.filter(e => e.id !== fromId);
|
||||
const toId = otherEntities[0].id;
|
||||
|
||||
const relId = 'rel_' + Date.now();
|
||||
this.relations.push({
|
||||
id: relId,
|
||||
name: 'hat',
|
||||
sourceEntityId: fromId,
|
||||
targetEntityId: toId,
|
||||
cardinalitySource: '1',
|
||||
cardinalityTarget: 'n'
|
||||
});
|
||||
this.render();
|
||||
this.renderSidebar();
|
||||
}
|
||||
|
||||
loadTemplate(tplName) {
|
||||
this.entities = [];
|
||||
this.relations = [];
|
||||
|
||||
if (tplName === 'onlineshop') {
|
||||
this.entities = [
|
||||
{
|
||||
id: 'ent_kunde',
|
||||
name: 'tbl_kunde',
|
||||
x: 50,
|
||||
y: 60,
|
||||
width: 230,
|
||||
attributes: [
|
||||
{ name: 'kunde_id', type: 'INT', pk: true, fk: false, nn: true, uq: true },
|
||||
{ name: 'nachname', type: 'VARCHAR(50)', pk: false, fk: false, nn: true, uq: false },
|
||||
{ name: 'vorname', type: 'VARCHAR(50)', pk: false, fk: false, nn: false, uq: false },
|
||||
{ name: 'email', type: 'VARCHAR(120)', pk: false, fk: false, nn: true, uq: true },
|
||||
{ name: 'registriert_am', type: 'DATE', pk: false, fk: false, nn: true, uq: false }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'ent_bestellung',
|
||||
name: 'tbl_bestellung',
|
||||
x: 370,
|
||||
y: 60,
|
||||
width: 230,
|
||||
attributes: [
|
||||
{ name: 'bestell_id', type: 'INT', pk: true, fk: false, nn: true, uq: true },
|
||||
{ name: 'kunde_id', type: 'INT', pk: false, fk: true, nn: true, uq: false },
|
||||
{ name: 'bestelldatum', type: 'DATETIME', pk: false, fk: false, nn: true, uq: false },
|
||||
{ name: 'status', type: 'VARCHAR(20)', pk: false, fk: false, nn: true, uq: false }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'ent_position',
|
||||
name: 'tbl_bestellposition',
|
||||
x: 370,
|
||||
y: 280,
|
||||
width: 230,
|
||||
attributes: [
|
||||
{ name: 'bestell_id', type: 'INT', pk: true, fk: true, nn: true, uq: false },
|
||||
{ name: 'artikel_id', type: 'INT', pk: true, fk: true, nn: true, uq: false },
|
||||
{ name: 'menge', type: 'INT', pk: false, fk: false, nn: true, uq: false },
|
||||
{ name: 'einzelpreis', type: 'DECIMAL(10,2)', pk: false, fk: false, nn: true, uq: false }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'ent_artikel',
|
||||
name: 'tbl_artikel',
|
||||
x: 50,
|
||||
y: 280,
|
||||
width: 230,
|
||||
attributes: [
|
||||
{ name: 'artikel_id', type: 'INT', pk: true, fk: false, nn: true, uq: true },
|
||||
{ name: 'artikelname', type: 'VARCHAR(100)', pk: false, fk: false, nn: true, uq: false },
|
||||
{ name: 'preis', type: 'DECIMAL(10,2)', pk: false, fk: false, nn: true, uq: false },
|
||||
{ name: 'lagerbestand', type: 'INT', pk: false, fk: false, nn: true, uq: false }
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
this.relations = [
|
||||
{
|
||||
id: 'rel_kunde_bestellung',
|
||||
name: 'gibt_auf',
|
||||
sourceEntityId: 'ent_kunde',
|
||||
targetEntityId: 'ent_bestellung',
|
||||
cardinalitySource: '1',
|
||||
cardinalityTarget: 'n'
|
||||
},
|
||||
{
|
||||
id: 'rel_bestellung_pos',
|
||||
name: 'besteht_aus',
|
||||
sourceEntityId: 'ent_bestellung',
|
||||
targetEntityId: 'ent_position',
|
||||
cardinalitySource: '1',
|
||||
cardinalityTarget: 'n'
|
||||
},
|
||||
{
|
||||
id: 'rel_artikel_pos',
|
||||
name: 'enthaelt',
|
||||
sourceEntityId: 'ent_artikel',
|
||||
targetEntityId: 'ent_position',
|
||||
cardinalitySource: '1',
|
||||
cardinalityTarget: 'n'
|
||||
}
|
||||
];
|
||||
} else if (tplName === 'asset_mgmt') {
|
||||
this.entities = [
|
||||
{
|
||||
id: 'ent_server',
|
||||
name: 'tbl_server',
|
||||
x: 60,
|
||||
y: 70,
|
||||
width: 230,
|
||||
attributes: [
|
||||
{ name: 'server_id', type: 'INT', pk: true, fk: false, nn: true, uq: true },
|
||||
{ name: 'hostname', type: 'VARCHAR(60)', pk: false, fk: false, nn: true, uq: true },
|
||||
{ name: 'ip_adresse', type: 'VARCHAR(45)', pk: false, fk: false, nn: true, uq: true },
|
||||
{ name: 'standort_id', type: 'INT', pk: false, fk: true, nn: true, uq: false }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'ent_standort',
|
||||
name: 'tbl_standort',
|
||||
x: 380,
|
||||
y: 70,
|
||||
width: 220,
|
||||
attributes: [
|
||||
{ name: 'standort_id', type: 'INT', pk: true, fk: false, nn: true, uq: true },
|
||||
{ name: 'bezeichnung', type: 'VARCHAR(50)', pk: false, fk: false, nn: true, uq: false },
|
||||
{ name: 'netzwerk_cidr', type: 'VARCHAR(20)', pk: false, fk: false, nn: true, uq: false }
|
||||
]
|
||||
}
|
||||
];
|
||||
this.relations = [
|
||||
{
|
||||
id: 'rel_server_standort',
|
||||
name: 'steht_in',
|
||||
sourceEntityId: 'ent_standort',
|
||||
targetEntityId: 'ent_server',
|
||||
cardinalitySource: '1',
|
||||
cardinalityTarget: 'n'
|
||||
}
|
||||
];
|
||||
} else if (tplName === 'schulung') {
|
||||
this.entities = [
|
||||
{
|
||||
id: 'ent_azubi',
|
||||
name: 'tbl_azubi',
|
||||
x: 60,
|
||||
y: 70,
|
||||
width: 230,
|
||||
attributes: [
|
||||
{ name: 'azubi_id', type: 'INT', pk: true, fk: false, nn: true, uq: true },
|
||||
{ name: 'name', type: 'VARCHAR(60)', pk: false, fk: false, nn: true, uq: false },
|
||||
{ name: 'ausbildungsberuf', type: 'VARCHAR(40)', pk: false, fk: false, nn: true, uq: false },
|
||||
{ name: 'ausbildungsjahr', type: 'INT', pk: false, fk: false, nn: true, uq: false }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'ent_projekt',
|
||||
name: 'tbl_projektarbeit',
|
||||
x: 390,
|
||||
y: 70,
|
||||
width: 230,
|
||||
attributes: [
|
||||
{ name: 'projekt_id', type: 'INT', pk: true, fk: false, nn: true, uq: true },
|
||||
{ name: 'titel', type: 'VARCHAR(150)', pk: false, fk: false, nn: true, uq: false },
|
||||
{ name: 'stundenumfang', type: 'INT', pk: false, fk: false, nn: true, uq: false },
|
||||
{ name: 'azubi_id', type: 'INT', pk: false, fk: true, nn: true, uq: true }
|
||||
]
|
||||
}
|
||||
];
|
||||
this.relations = [
|
||||
{
|
||||
id: 'rel_azubi_projekt',
|
||||
name: 'fuehrt_durch',
|
||||
sourceEntityId: 'ent_azubi',
|
||||
targetEntityId: 'ent_projekt',
|
||||
cardinalitySource: '1',
|
||||
cardinalityTarget: '1'
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
this.selectedEntityId = this.entities[0] ? this.entities[0].id : null;
|
||||
this.render();
|
||||
this.renderSidebar();
|
||||
}
|
||||
|
||||
onMouseDown(e) {
|
||||
const rect = document.getElementById('er-svg-canvas').getBoundingClientRect();
|
||||
const mouseX = e.clientX - rect.left;
|
||||
const mouseY = e.clientY - rect.top;
|
||||
|
||||
for (let i = this.entities.length - 1; i >= 0; i--) {
|
||||
const ent = this.entities[i];
|
||||
const headerHeight = 32;
|
||||
const height = headerHeight + ent.attributes.length * 24 + 10;
|
||||
if (mouseX >= ent.x && mouseX <= ent.x + ent.width && mouseY >= ent.y && mouseY <= ent.y + height) {
|
||||
this.draggedEntity = ent;
|
||||
this.dragOffset = { x: mouseX - ent.x, y: mouseY - ent.y };
|
||||
this.selectEntity(ent.id);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMouseMove(e) {
|
||||
if (!this.draggedEntity) return;
|
||||
const rect = document.getElementById('er-svg-canvas').getBoundingClientRect();
|
||||
const mouseX = e.clientX - rect.left;
|
||||
const mouseY = e.clientY - rect.top;
|
||||
|
||||
this.draggedEntity.x = Math.max(10, Math.min(1200, mouseX - this.dragOffset.x));
|
||||
this.draggedEntity.y = Math.max(10, Math.min(800, mouseY - this.dragOffset.y));
|
||||
this.render();
|
||||
}
|
||||
|
||||
onMouseUp() {
|
||||
this.draggedEntity = null;
|
||||
}
|
||||
|
||||
render() {
|
||||
const svg = document.getElementById('er-svg-canvas');
|
||||
if (!svg) return;
|
||||
|
||||
let html = `
|
||||
<defs>
|
||||
<pattern id="er-grid" width="20" height="20" patternUnits="userSpaceOnUse">
|
||||
<path d="M 20 0 L 0 0 0 20" fill="none" stroke="rgba(255,255,255,0.05)" stroke-width="1"/>
|
||||
</pattern>
|
||||
</defs>
|
||||
<rect width="100%" height="100%" fill="#090d16"/>
|
||||
<rect width="100%" height="100%" fill="url(#er-grid)"/>
|
||||
`;
|
||||
|
||||
// 1. Relations
|
||||
this.relations.forEach(rel => {
|
||||
const src = this.entities.find(e => e.id === rel.sourceEntityId);
|
||||
const tgt = this.entities.find(e => e.id === rel.targetEntityId);
|
||||
if (!src || !tgt) return;
|
||||
|
||||
const srcCenter = { x: src.x + src.width / 2, y: src.y + 45 };
|
||||
const tgtCenter = { x: tgt.x + tgt.width / 2, y: tgt.y + 45 };
|
||||
|
||||
html += `<line x1="${srcCenter.x}" y1="${srcCenter.y}" x2="${tgtCenter.x}" y2="${tgtCenter.y}" stroke="#38bdf8" stroke-width="2"/>`;
|
||||
|
||||
const midX = (srcCenter.x + tgtCenter.x) / 2;
|
||||
const midY = (srcCenter.y + tgtCenter.y) / 2;
|
||||
|
||||
const srcOffX = srcCenter.x + (tgtCenter.x - srcCenter.x) * 0.25;
|
||||
const srcOffY = srcCenter.y + (tgtCenter.y - srcCenter.y) * 0.25 - 10;
|
||||
const tgtOffX = tgtCenter.x - (tgtCenter.x - srcCenter.x) * 0.25;
|
||||
const tgtOffY = tgtCenter.y - (tgtCenter.y - srcCenter.y) * 0.25 - 10;
|
||||
|
||||
let cardSrcText = rel.cardinalitySource;
|
||||
let cardTgtText = rel.cardinalityTarget;
|
||||
|
||||
if (this.notation === 'chen') {
|
||||
cardSrcText = rel.cardinalitySource === '1' ? '1' : 'n';
|
||||
cardTgtText = rel.cardinalityTarget === '1' ? '1' : 'm';
|
||||
} else if (this.notation === 'minmax') {
|
||||
cardSrcText = rel.cardinalitySource === '1' ? '(0,1)' : '(1,n)';
|
||||
cardTgtText = rel.cardinalityTarget === '1' ? '(1,1)' : '(0,n)';
|
||||
}
|
||||
|
||||
html += `
|
||||
<g transform="translate(${midX}, ${midY})">
|
||||
<rect x="-36" y="-12" width="72" height="24" rx="12" fill="#1e293b" stroke="#38bdf8" stroke-width="1.5"/>
|
||||
<text x="0" y="4" fill="#f8fafc" font-size="10" font-weight="bold" text-anchor="middle" font-family="sans-serif">${rel.name}</text>
|
||||
</g>
|
||||
<rect x="${srcOffX - 14}" y="${srcOffY - 10}" width="28" height="20" rx="4" fill="#0f172a" stroke="#475569" stroke-width="1"/>
|
||||
<text x="${srcOffX}" y="${srcOffY + 4}" fill="#38bdf8" font-size="11" font-weight="bold" text-anchor="middle" font-family="monospace">${cardSrcText}</text>
|
||||
|
||||
<rect x="${tgtOffX - 14}" y="${tgtOffY - 10}" width="28" height="20" rx="4" fill="#0f172a" stroke="#475569" stroke-width="1"/>
|
||||
<text x="${tgtOffX}" y="${tgtOffY + 4}" fill="#38bdf8" font-size="11" font-weight="bold" text-anchor="middle" font-family="monospace">${cardTgtText}</text>
|
||||
`;
|
||||
});
|
||||
|
||||
// 2. Entities
|
||||
this.entities.forEach(ent => {
|
||||
const isSelected = ent.id === this.selectedEntityId;
|
||||
const headerHeight = 34;
|
||||
const attrHeight = 24;
|
||||
const totalHeight = headerHeight + ent.attributes.length * attrHeight + 8;
|
||||
|
||||
html += `
|
||||
<g class="er-entity-node" transform="translate(${ent.x}, ${ent.y})" style="cursor:move;">
|
||||
<rect x="0" y="0" width="${ent.width}" height="${totalHeight}" rx="8" fill="#1e293b" stroke="${isSelected ? '#38bdf8' : '#334155'}" stroke-width="${isSelected ? 2.5 : 1.5}" filter="drop-shadow(0 4px 10px rgba(0,0,0,0.35))"/>
|
||||
<rect x="0" y="0" width="${ent.width}" height="${headerHeight}" rx="8" fill="${isSelected ? '#1d4ed8' : '#0f172a'}"/>
|
||||
<rect x="0" y="${headerHeight - 2}" width="${ent.width}" height="2" fill="${isSelected ? '#38bdf8' : '#334155'}"/>
|
||||
<text x="12" y="22" fill="#ffffff" font-size="13" font-weight="bold" font-family="sans-serif">🗄️ ${ent.name}</text>
|
||||
`;
|
||||
|
||||
ent.attributes.forEach((attr, idx) => {
|
||||
const rowY = headerHeight + idx * attrHeight + 16;
|
||||
let keyBadge = '';
|
||||
let badgeColor = '#94a3b8';
|
||||
if (attr.pk) {
|
||||
keyBadge = 'PK';
|
||||
badgeColor = '#fbbf24';
|
||||
} else if (attr.fk) {
|
||||
keyBadge = 'FK';
|
||||
badgeColor = '#38bdf8';
|
||||
}
|
||||
|
||||
html += `
|
||||
<g transform="translate(10, ${rowY - 12})">
|
||||
${keyBadge ? `<rect x="0" y="0" width="22" height="15" rx="3" fill="rgba(255,255,255,0.08)" stroke="${badgeColor}" stroke-width="1"/><text x="11" y="11" fill="${badgeColor}" font-size="9" font-weight="bold" text-anchor="middle" font-family="monospace">${keyBadge}</text>` : ''}
|
||||
<text x="${keyBadge ? 28 : 6}" y="11" fill="#f8fafc" font-size="11" font-weight="${attr.pk ? 'bold' : 'normal'}" font-family="monospace">${attr.name}</text>
|
||||
<text x="${ent.width - 24}" y="11" fill="#94a3b8" font-size="10" text-anchor="end" font-family="monospace">${attr.type}</text>
|
||||
</g>
|
||||
`;
|
||||
});
|
||||
|
||||
html += `</g>`;
|
||||
});
|
||||
|
||||
svg.innerHTML = html;
|
||||
}
|
||||
|
||||
renderSidebar() {
|
||||
const sidebarTitle = document.getElementById('er-sidebar-title');
|
||||
const content = document.getElementById('er-sidebar-content');
|
||||
if (!content) return;
|
||||
|
||||
const ent = this.entities.find(e => e.id === this.selectedEntityId);
|
||||
if (!ent) {
|
||||
sidebarTitle.innerText = 'Eigenschaften';
|
||||
content.innerHTML = `<p class="ds-hint">Klicke auf eine Entität im Diagramm, um Eigenschaften und Attribute zu bearbeiten.</p>`;
|
||||
return;
|
||||
}
|
||||
|
||||
sidebarTitle.innerText = `Entität: ${ent.name}`;
|
||||
|
||||
let html = `
|
||||
<div class="ds-field-group">
|
||||
<label>Tabellenname:</label>
|
||||
<input type="text" id="er-edit-ent-name" class="ds-input" value="${ent.name}">
|
||||
</div>
|
||||
|
||||
<div style="display:flex; justify-content:space-between; align-items:center; margin:12px 0 8px 0;">
|
||||
<span style="font-size:0.8rem; font-weight:700; color:#38bdf8; text-transform:uppercase;">Attribute / Spalten:</span>
|
||||
<button type="button" class="ds-btn ds-btn-primary" id="er-btn-add-attr" style="padding:4px 8px; font-size:0.75rem;">+ Spalte</button>
|
||||
</div>
|
||||
|
||||
<div style="display:flex; flex-direction:column; gap:8px; max-height:280px; overflow-y:auto; padding-right:4px;">
|
||||
`;
|
||||
|
||||
ent.attributes.forEach((attr, idx) => {
|
||||
html += `
|
||||
<div class="ds-attr-card">
|
||||
<div style="display:flex; gap:6px; margin-bottom:6px;">
|
||||
<input type="text" data-idx="${idx}" class="ds-input er-attr-name" value="${attr.name}" style="padding:4px 6px; font-size:0.8rem; flex:1;" placeholder="Spaltenname">
|
||||
<select data-idx="${idx}" class="ds-select er-attr-type" style="padding:4px 6px; font-size:0.8rem; width:100px;">
|
||||
<option value="INT" ${attr.type === 'INT' ? 'selected' : ''}>INT</option>
|
||||
<option value="VARCHAR(50)" ${attr.type === 'VARCHAR(50)' ? 'selected' : ''}>VARCHAR(50)</option>
|
||||
<option value="VARCHAR(100)" ${attr.type === 'VARCHAR(100)' ? 'selected' : ''}>VARCHAR(100)</option>
|
||||
<option value="DATE" ${attr.type === 'DATE' ? 'selected' : ''}>DATE</option>
|
||||
<option value="DATETIME" ${attr.type === 'DATETIME' ? 'selected' : ''}>DATETIME</option>
|
||||
<option value="DECIMAL(10,2)" ${attr.type === 'DECIMAL(10,2)' ? 'selected' : ''}>DECIMAL</option>
|
||||
<option value="BOOLEAN" ${attr.type === 'BOOLEAN' ? 'selected' : ''}>BOOLEAN</option>
|
||||
</select>
|
||||
<button type="button" data-idx="${idx}" class="er-attr-del-btn ds-icon-btn" title="Löschen">×</button>
|
||||
</div>
|
||||
<div style="display:flex; gap:10px; font-size:0.75rem; color:#cbd5e1;">
|
||||
<label><input type="checkbox" data-idx="${idx}" class="er-attr-pk" ${attr.pk ? 'checked' : ''}> PK</label>
|
||||
<label><input type="checkbox" data-idx="${idx}" class="er-attr-fk" ${attr.fk ? 'checked' : ''}> FK</label>
|
||||
<label><input type="checkbox" data-idx="${idx}" class="er-attr-nn" ${attr.nn ? 'checked' : ''}> Not Null</label>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
html += `
|
||||
</div>
|
||||
<div style="margin-top:16px; border-top:1px solid #334155; padding-top:12px;">
|
||||
<button type="button" id="er-btn-delete-entity" class="ds-btn ds-btn-danger" style="width:100%; font-size:0.82rem;">
|
||||
🗑️ Diese Entität löschen
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
content.innerHTML = html;
|
||||
|
||||
document.getElementById('er-edit-ent-name').addEventListener('input', (e) => {
|
||||
ent.name = e.target.value;
|
||||
this.render();
|
||||
});
|
||||
|
||||
document.getElementById('er-btn-add-attr').addEventListener('click', () => {
|
||||
ent.attributes.push({ name: 'neues_feld', type: 'VARCHAR(50)', pk: false, fk: false, nn: false, uq: false });
|
||||
this.render();
|
||||
this.renderSidebar();
|
||||
});
|
||||
|
||||
content.querySelectorAll('.er-attr-name').forEach(inp => {
|
||||
inp.addEventListener('input', (e) => {
|
||||
const idx = parseInt(e.target.dataset.idx, 10);
|
||||
ent.attributes[idx].name = e.target.value;
|
||||
this.render();
|
||||
});
|
||||
});
|
||||
|
||||
content.querySelectorAll('.er-attr-type').forEach(sel => {
|
||||
sel.addEventListener('change', (e) => {
|
||||
const idx = parseInt(e.target.dataset.idx, 10);
|
||||
ent.attributes[idx].type = e.target.value;
|
||||
this.render();
|
||||
});
|
||||
});
|
||||
|
||||
content.querySelectorAll('.er-attr-pk').forEach(chk => {
|
||||
chk.addEventListener('change', (e) => {
|
||||
const idx = parseInt(e.target.dataset.idx, 10);
|
||||
ent.attributes[idx].pk = e.target.checked;
|
||||
this.render();
|
||||
});
|
||||
});
|
||||
|
||||
content.querySelectorAll('.er-attr-fk').forEach(chk => {
|
||||
chk.addEventListener('change', (e) => {
|
||||
const idx = parseInt(e.target.dataset.idx, 10);
|
||||
ent.attributes[idx].fk = e.target.checked;
|
||||
this.render();
|
||||
});
|
||||
});
|
||||
|
||||
content.querySelectorAll('.er-attr-nn').forEach(chk => {
|
||||
chk.addEventListener('change', (e) => {
|
||||
const idx = parseInt(e.target.dataset.idx, 10);
|
||||
ent.attributes[idx].nn = e.target.checked;
|
||||
this.render();
|
||||
});
|
||||
});
|
||||
|
||||
content.querySelectorAll('.er-attr-del-btn').forEach(btn => {
|
||||
btn.addEventListener('click', (e) => {
|
||||
const idx = parseInt(e.target.dataset.idx, 10);
|
||||
ent.attributes.splice(idx, 1);
|
||||
this.render();
|
||||
this.renderSidebar();
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById('er-btn-delete-entity').addEventListener('click', () => {
|
||||
if (confirm(`Entität "${ent.name}" wirklich löschen?`)) {
|
||||
this.entities = this.entities.filter(e => e.id !== ent.id);
|
||||
this.relations = this.relations.filter(r => r.sourceEntityId !== ent.id && r.targetEntityId !== ent.id);
|
||||
this.selectedEntityId = this.entities[0] ? this.entities[0].id : null;
|
||||
this.render();
|
||||
this.renderSidebar();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
generateSqlDDL() {
|
||||
let sql = `-- ========================================================\n`;
|
||||
sql += `-- IHK Prüfungsvorbereitung: Relationales Datenbankschema\n`;
|
||||
sql += `-- Generiert vom IT-Praxisportal ER-Modell Studio\n`;
|
||||
sql += `-- Standard: ANSI SQL:2016\n`;
|
||||
sql += `-- ========================================================\n\n`;
|
||||
|
||||
this.entities.forEach(ent => {
|
||||
sql += `CREATE TABLE ${ent.name} (\n`;
|
||||
const pks = ent.attributes.filter(a => a.pk).map(a => a.name);
|
||||
const lines = [];
|
||||
|
||||
ent.attributes.forEach(attr => {
|
||||
let line = ` ${attr.name} ${attr.type}`;
|
||||
if (attr.nn) line += ` NOT NULL`;
|
||||
if (attr.uq && !attr.pk) line += ` UNIQUE`;
|
||||
lines.push(line);
|
||||
});
|
||||
|
||||
if (pks.length > 0) {
|
||||
lines.push(` PRIMARY KEY (${pks.join(', ')})`);
|
||||
}
|
||||
|
||||
sql += lines.join(',\n');
|
||||
sql += `\n);\n\n`;
|
||||
});
|
||||
|
||||
if (this.relations.length > 0) {
|
||||
sql += `-- Fremdschlüssel-Beziehungen (Referentielle Integrität)\n`;
|
||||
this.relations.forEach(rel => {
|
||||
const src = this.entities.find(e => e.id === rel.sourceEntityId);
|
||||
const tgt = this.entities.find(e => e.id === rel.targetEntityId);
|
||||
if (src && tgt) {
|
||||
const srcPk = src.attributes.find(a => a.pk) || { name: 'id' };
|
||||
sql += `ALTER TABLE ${tgt.name} ADD CONSTRAINT fk_${tgt.name}_${src.name}\n`;
|
||||
sql += ` FOREIGN KEY (${src.name}_id) REFERENCES ${src.name}(${srcPk.name})\n`;
|
||||
sql += ` ON DELETE RESTRICT ON UPDATE CASCADE;\n\n`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return sql;
|
||||
}
|
||||
|
||||
showSqlModal() {
|
||||
const ddl = this.generateSqlDDL();
|
||||
document.getElementById('er-sql-code-display').innerText = ddl;
|
||||
document.getElementById('er-sql-modal').style.display = 'flex';
|
||||
}
|
||||
|
||||
exportSvg() {
|
||||
const svgEl = document.getElementById('er-svg-canvas');
|
||||
if (!svgEl) return;
|
||||
const serializer = new XMLSerializer();
|
||||
const source = serializer.serializeToString(svgEl);
|
||||
const blob = new Blob([source], { type: 'image/svg+xml;charset=utf-8' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = 'er_modell_ihk.svg';
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
}
|
||||
|
||||
window.EREditor = EREditor;
|
||||
|
|
@ -153,9 +153,12 @@ class IhkExamEngine {
|
|||
</button>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; align-items: center; gap: 10px;">
|
||||
<div style="display: flex; align-items: center; gap: 10px; flex-wrap: wrap;">
|
||||
<a href="../../../diagram_editor.html" target="_blank" class="ihk-btn" style="background: #0284c7; color: #ffffff !important; font-size: 0.82rem; padding: 6px 14px; text-decoration: none; display: inline-flex; align-items: center; gap: 6px;" title="Öffnet das interaktive IHK Diagramm-Studio in neuem Tab">
|
||||
<span>📐</span> <span>Diagramm-Studio (ER / SG / UML)</span>
|
||||
</a>
|
||||
<button id="btn-open-catalog" class="ihk-btn" style="background: #334155; color: #f8fafc; font-size: 0.82rem; padding: 6px 14px; border: 1px solid #475569;">
|
||||
📚 150-Fragen-Pool (Drill)
|
||||
📚 Fragenkatalog (234 Fragen)
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
|
@ -347,6 +350,16 @@ class IhkExamEngine {
|
|||
task.dataset.points = q.points;
|
||||
task.style.cssText = "background: #1e293b; padding: 24px; border-radius: 12px; border: 1px solid #334155; margin-bottom: 24px;";
|
||||
|
||||
let diagramLinkHtml = '';
|
||||
const topicLow = ((q.topic || '') + ' ' + (q.question || '')).toLowerCase();
|
||||
if (topicLow.includes('er-modell') || topicLow.includes('kardinalit') || topicLow.includes('normalis')) {
|
||||
diagramLinkHtml = `<div style="margin-bottom: 12px;"><a href="../../../diagram_editor.html#er" target="_blank" class="ihk-btn" style="padding: 4px 10px; font-size: 0.78rem; background: rgba(56, 189, 248, 0.15); color: #38bdf8 !important; border: 1px solid rgba(56, 189, 248, 0.3); text-decoration: none; border-radius: 4px; display: inline-flex; align-items: center; gap: 6px;"><span>🗄️</span> <span>Im ER-Modell Editor entwerfen / prüfen →</span></a></div>`;
|
||||
} else if (topicLow.includes('struktogramm') || topicLow.includes('nassi') || topicLow.includes('din 66261')) {
|
||||
diagramLinkHtml = `<div style="margin-bottom: 12px;"><a href="../../../diagram_editor.html#struktogramm" target="_blank" class="ihk-btn" style="padding: 4px 10px; font-size: 0.78rem; background: rgba(56, 189, 248, 0.15); color: #38bdf8 !important; border: 1px solid rgba(56, 189, 248, 0.3); text-decoration: none; border-radius: 4px; display: inline-flex; align-items: center; gap: 6px;"><span>📐</span> <span>Im Struktogramm-Editor entwerfen / prüfen →</span></a></div>`;
|
||||
} else if (topicLow.includes('uml') || topicLow.includes('klassendiagramm') || topicLow.includes('sequenzdiagramm') || topicLow.includes('use case')) {
|
||||
diagramLinkHtml = `<div style="margin-bottom: 12px;"><a href="../../../diagram_editor.html#uml" target="_blank" class="ihk-btn" style="padding: 4px 10px; font-size: 0.78rem; background: rgba(56, 189, 248, 0.15); color: #38bdf8 !important; border: 1px solid rgba(56, 189, 248, 0.3); text-decoration: none; border-radius: 4px; display: inline-flex; align-items: center; gap: 6px;"><span>🔷</span> <span>Im UML-Studio entwerfen / prüfen →</span></a></div>`;
|
||||
}
|
||||
|
||||
task.innerHTML = `
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 14px;">
|
||||
<h3 style="color: #38bdf8; margin: 0; font-size: 1.15rem;">
|
||||
|
|
@ -370,6 +383,7 @@ class IhkExamEngine {
|
|||
<p style="color: #e2e8f0; font-size: 0.95rem; line-height: 1.6; margin-top: 0;">
|
||||
${this.formatQuestionText(q.question)}
|
||||
</p>
|
||||
${diagramLinkHtml}
|
||||
<textarea class="ihk-input" rows="${q.points > 8 ? 6 : 4}" placeholder="Ihre Bearbeitung hier eingeben (Fachbegriffe, Formeln, Begründungen)..."></textarea>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
105
assets/ihk_tools/handelskalkulation_trainer.js
Normal file
105
assets/ihk_tools/handelskalkulation_trainer.js
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
/**
|
||||
* Interaktiver Handelskalkulations-Trainer für ITSM & KDM
|
||||
* IHK ZPA Nord-West AP2
|
||||
*/
|
||||
(function() {
|
||||
function renderHandelskalkulation(containerId) {
|
||||
const container = document.getElementById(containerId);
|
||||
if (!container) return;
|
||||
|
||||
container.innerHTML = `
|
||||
<div style="background: #0f172a; border: 1px solid #334155; border-radius: 8px; padding: 20px; color: #f8fafc;">
|
||||
<h4 style="color: #38bdf8; margin-top: 0; margin-bottom: 12px;">📊 Interaktiver Handelskalkulations-Trainer (ITSM / KDM)</h4>
|
||||
<p style="color: #94a3b8; font-size: 0.9rem; margin-bottom: 16px;">
|
||||
Geben Sie die kaufmännischen Eckdaten ein, um das Vorwärtskalkulations-Schema nach IHK-Standard durchzurechnen:
|
||||
</p>
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; margin-bottom: 20px;">
|
||||
<div style="background: #1e293b; padding: 10px; border-radius: 6px;">
|
||||
<label style="font-size: 0.8rem; color: #cbd5e1; display: block;">Listeneinkaufspreis (LEP €)</label>
|
||||
<input type="number" id="kalk_lep" value="3000" min="0" step="50" class="ihk-input" style="width: 100%; margin-top: 4px;" />
|
||||
</div>
|
||||
<div style="background: #1e293b; padding: 10px; border-radius: 6px;">
|
||||
<label style="font-size: 0.8rem; color: #cbd5e1; display: block;">Lieferantenrabatt (%)</label>
|
||||
<input type="number" id="kalk_rab" value="20" min="0" max="100" class="ihk-input" style="width: 100%; margin-top: 4px;" />
|
||||
</div>
|
||||
<div style="background: #1e293b; padding: 10px; border-radius: 6px;">
|
||||
<label style="font-size: 0.8rem; color: #cbd5e1; display: block;">Lieferantenskonto (%)</label>
|
||||
<input type="number" id="kalk_sko" value="2" min="0" max="100" class="ihk-input" style="width: 100%; margin-top: 4px;" />
|
||||
</div>
|
||||
<div style="background: #1e293b; padding: 10px; border-radius: 6px;">
|
||||
<label style="font-size: 0.8rem; color: #cbd5e1; display: block;">Bezugskosten (€)</label>
|
||||
<input type="number" id="kalk_bezug" value="50" min="0" step="5" class="ihk-input" style="width: 100%; margin-top: 4px;" />
|
||||
</div>
|
||||
<div style="background: #1e293b; padding: 10px; border-radius: 6px;">
|
||||
<label style="font-size: 0.8rem; color: #cbd5e1; display: block;">Handlungskosten HKZ (%)</label>
|
||||
<input type="number" id="kalk_hkz" value="15" min="0" class="ihk-input" style="width: 100%; margin-top: 4px;" />
|
||||
</div>
|
||||
<div style="background: #1e293b; padding: 10px; border-radius: 6px;">
|
||||
<label style="font-size: 0.8rem; color: #cbd5e1; display: block;">Gewinnzuschlag (%)</label>
|
||||
<input type="number" id="kalk_gewinn" value="25" min="0" class="ihk-input" style="width: 100%; margin-top: 4px;" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="background: #1e293b; border-radius: 6px; padding: 16px; font-family: monospace; font-size: 0.9rem; line-height: 1.8;">
|
||||
<div style="display: flex; justify-content: space-between;"><span>Listeneinkaufspreis:</span> <span id="out_lep">0,00 €</span></div>
|
||||
<div style="display: flex; justify-content: space-between; color: #f87171;"><span>- Lieferantenrabatt:</span> <span id="out_rab">0,00 €</span></div>
|
||||
<div style="display: flex; justify-content: space-between; border-top: 1px solid #475569;"><span>= Zieleinkaufspreis:</span> <span id="out_zep">0,00 €</span></div>
|
||||
<div style="display: flex; justify-content: space-between; color: #f87171;"><span>- Lieferantenskonto:</span> <span id="out_sko">0,00 €</span></div>
|
||||
<div style="display: flex; justify-content: space-between; border-top: 1px solid #475569;"><span>= Bareinkaufspreis:</span> <span id="out_bep">0,00 €</span></div>
|
||||
<div style="display: flex; justify-content: space-between; color: #38bdf8;"><span>+ Bezugskosten:</span> <span id="out_bezug">0,00 €</span></div>
|
||||
<div style="display: flex; justify-content: space-between; border-top: 1px solid #475569; font-weight: bold; color: #fcd34d;"><span>= Einstandspreis (Bezugspreis):</span> <span id="out_einstand">0,00 €</span></div>
|
||||
<div style="display: flex; justify-content: space-between; color: #38bdf8;"><span>+ Handlungskosten:</span> <span id="out_hkz">0,00 €</span></div>
|
||||
<div style="display: flex; justify-content: space-between; border-top: 1px solid #475569;"><span>= Selbstkostenpreis:</span> <span id="out_sk">0,00 €</span></div>
|
||||
<div style="display: flex; justify-content: space-between; color: #4ade80;"><span>+ Gewinnzuschlag:</span> <span id="out_gewinn">0,00 €</span></div>
|
||||
<div style="display: flex; justify-content: space-between; border-top: 2px solid #38bdf8; font-weight: bold; font-size: 1.05rem; color: #38bdf8; padding-top: 4px;"><span>= Barverkaufspreis (BVP netto):</span> <span id="out_bvp">0,00 €</span></div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
function update() {
|
||||
const lep = parseFloat(document.getElementById('kalk_lep').value) || 0;
|
||||
const rabP = parseFloat(document.getElementById('kalk_rab').value) || 0;
|
||||
const skoP = parseFloat(document.getElementById('kalk_sko').value) || 0;
|
||||
const bezug = parseFloat(document.getElementById('kalk_bezug').value) || 0;
|
||||
const hkzP = parseFloat(document.getElementById('kalk_hkz').value) || 0;
|
||||
const gewinnP = parseFloat(document.getElementById('kalk_gewinn').value) || 0;
|
||||
|
||||
const rab = lep * (rabP / 100);
|
||||
const zep = lep - rab;
|
||||
const sko = zep * (skoP / 100);
|
||||
const bep = zep - sko;
|
||||
const einstand = bep + bezug;
|
||||
const hkz = einstand * (hkzP / 100);
|
||||
const sk = einstand + hkz;
|
||||
const gewinn = sk * (gewinnP / 100);
|
||||
const bvp = sk + gewinn;
|
||||
|
||||
const fmt = (v) => v.toLocaleString('de-DE', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) + ' €';
|
||||
|
||||
document.getElementById('out_lep').textContent = fmt(lep);
|
||||
document.getElementById('out_rab').textContent = '- ' + fmt(rab);
|
||||
document.getElementById('out_zep').textContent = fmt(zep);
|
||||
document.getElementById('out_sko').textContent = '- ' + fmt(sko);
|
||||
document.getElementById('out_bep').textContent = fmt(bep);
|
||||
document.getElementById('out_bezug').textContent = '+ ' + fmt(bezug);
|
||||
document.getElementById('out_einstand').textContent = fmt(einstand);
|
||||
document.getElementById('out_hkz').textContent = '+ ' + fmt(hkz);
|
||||
document.getElementById('out_sk').textContent = fmt(sk);
|
||||
document.getElementById('out_gewinn').textContent = '+ ' + fmt(gewinn);
|
||||
document.getElementById('out_bvp').textContent = fmt(bvp);
|
||||
}
|
||||
|
||||
['kalk_lep', 'kalk_rab', 'kalk_sko', 'kalk_bezug', 'kalk_hkz', 'kalk_gewinn'].forEach(id => {
|
||||
document.getElementById(id).addEventListener('input', update);
|
||||
});
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
window.renderHandelskalkulation = renderHandelskalkulation;
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
if (document.getElementById('handelskalkulation-app')) {
|
||||
renderHandelskalkulation('handelskalkulation-app');
|
||||
}
|
||||
});
|
||||
})();
|
||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -112,18 +112,34 @@
|
|||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.ihk-btn-success {
|
||||
background: #16a34a;
|
||||
.ihk-btn-primary {
|
||||
background: #0284c7;
|
||||
color: white !important;
|
||||
text-decoration: none;
|
||||
}
|
||||
.ihk-btn-success:hover {
|
||||
background: #15803d;
|
||||
.ihk-btn-primary:hover {
|
||||
background: #0369a1;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.ihk-btn-secondary {
|
||||
background: #475569;
|
||||
color: white !important;
|
||||
text-decoration: none;
|
||||
}
|
||||
.ihk-btn-secondary:hover {
|
||||
background: #334155;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.ihk-btn-success {
|
||||
background: #16a34a;
|
||||
color: white !important;
|
||||
text-decoration: none;
|
||||
}
|
||||
.ihk-btn-success:hover {
|
||||
background: #15803d;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.ihk-result-box {
|
||||
|
|
|
|||
248
assets/ihk_tools/netzplan_trainer.js
Normal file
248
assets/ihk_tools/netzplan_trainer.js
Normal file
|
|
@ -0,0 +1,248 @@
|
|||
/**
|
||||
* 📅 IHK NETZPLAN-TRAINER (DIN 69900) 📅
|
||||
* =====================================
|
||||
* Interaktiver Trainer zur Berechnung von:
|
||||
* - Vorwärtsrechnung: FAZ (Frühester Anfangszeitpunkt) & FEZ (Frühester Endzeitpunkt)
|
||||
* - Rückwärtsrechnung: SAZ (Spätester Anfangszeitpunkt) & SEZ (Spätester Endzeitpunkt)
|
||||
* - Pufferzeiten: GP (Gesamtpuffer = SAZ - FAZ) & FP (Freier Puffer = min(FAZ_Nachfolger) - FEZ)
|
||||
* - Kritischer Pfad (Vorgänge mit GP = 0)
|
||||
*/
|
||||
|
||||
class NetzplanTrainer {
|
||||
constructor(containerId) {
|
||||
this.container = document.getElementById(containerId);
|
||||
this.scenarioIndex = 0;
|
||||
this.scenarios = [
|
||||
{
|
||||
title: "Szenario 1: Rollout eines neuen Firmennetzwerks (PrintTop GmbH)",
|
||||
nodes: [
|
||||
{ id: "A", name: "Anforderungsanalyse", d: 3, preds: [], faz: 0, fez: 3, saz: 0, sez: 3, gp: 0, fp: 0 },
|
||||
{ id: "B", name: "Hardware-Beschaffung", d: 5, preds: ["A"], faz: 3, fez: 8, saz: 3, sez: 8, gp: 0, fp: 0 },
|
||||
{ id: "C", name: "Kabelverlegung (Cat 7)", d: 4, preds: ["A"], faz: 3, fez: 7, saz: 4, sez: 8, gp: 1, fp: 1 },
|
||||
{ id: "D", name: "Switch-/Router-Konfiguration", d: 4, preds: ["B"], faz: 8, fez: 12, saz: 8, sez: 12, gp: 0, fp: 0 },
|
||||
{ id: "E", name: "Patchfeld & Dosen auflegen", d: 3, preds: ["C"], faz: 7, fez: 10, saz: 9, sez: 12, gp: 2, fp: 2 },
|
||||
{ id: "F", name: "End-to-End Funktionstest", d: 2, preds: ["D", "E"], faz: 12, fez: 14, saz: 12, sez: 14, gp: 0, fp: 0 },
|
||||
{ id: "G", name: "Übergabe & Abnahme", d: 1, preds: ["F"], faz: 14, fez: 15, saz: 14, sez: 15, gp: 0, fp: 0 }
|
||||
],
|
||||
criticalPath: ["A", "B", "D", "F", "G"]
|
||||
},
|
||||
{
|
||||
title: "Szenario 2: Migration auf Microsoft Active Directory",
|
||||
nodes: [
|
||||
{ id: "A", name: "Ist-Aufnahme AD DS", d: 2, preds: [], faz: 0, fez: 2, saz: 0, sez: 2, gp: 0, fp: 0 },
|
||||
{ id: "B", name: "OU-Struktur & GPO-Design", d: 4, preds: ["A"], faz: 2, fez: 6, saz: 2, sez: 6, gp: 0, fp: 0 },
|
||||
{ id: "C", name: "Bereitstellung DC-Hardware", d: 3, preds: ["A"], faz: 2, fez: 5, saz: 3, sez: 6, gp: 1, fp: 1 },
|
||||
{ id: "D", name: "Benutzerdaten CSV-Export", d: 2, preds: ["A"], faz: 2, fez: 4, saz: 6, sez: 8, gp: 4, fp: 4 },
|
||||
{ id: "E", name: "Installation Domain Controller", d: 2, preds: ["B", "C"], faz: 6, fez: 8, saz: 6, sez: 8, gp: 0, fp: 0 },
|
||||
{ id: "F", name: "PowerShell Massenimport", d: 3, preds: ["D", "E"], faz: 8, fez: 11, saz: 8, sez: 11, gp: 0, fp: 0 },
|
||||
{ id: "G", name: "Client-Domain-Join & Tests", d: 2, preds: ["F"], faz: 11, fez: 13, saz: 11, sez: 13, gp: 0, fp: 0 }
|
||||
],
|
||||
criticalPath: ["A", "B", "E", "F", "G"]
|
||||
}
|
||||
];
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
if (!this.container) return;
|
||||
this.render();
|
||||
}
|
||||
|
||||
render() {
|
||||
const sc = this.scenarios[this.scenarioIndex];
|
||||
this.container.innerHTML = `
|
||||
<div class="ihk-tool-card" style="background: #1e293b; border: 1px solid #334155; border-radius: 12px; padding: 24px;">
|
||||
<div class="ihk-tool-header" style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
|
||||
<div>
|
||||
<h3 style="color: #38bdf8; margin: 0; font-size: 1.3rem;">📊 DIN 69900 Netzplantechnik Trainer</h3>
|
||||
<p style="color: #94a3b8; font-size: 0.9rem; margin: 4px 0 0 0;">${sc.title}</p>
|
||||
</div>
|
||||
<div style="display: flex; gap: 8px;">
|
||||
<button class="ihk-btn ihk-btn-secondary" id="np-prev-scenario" style="padding: 6px 12px; font-size: 0.85rem;">← Szenario 1</button>
|
||||
<button class="ihk-btn ihk-btn-secondary" id="np-next-scenario" style="padding: 6px 12px; font-size: 0.85rem;">Szenario 2 →</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="background: #0f172a; border: 1px solid #334155; border-radius: 8px; padding: 16px; margin-bottom: 20px;">
|
||||
<p style="color: #cbd5e1; font-size: 0.9rem; margin: 0 0 10px 0;">
|
||||
<strong>Aufgabe:</strong> Berechnen Sie für jeden Vorgang die Zeitpunkte der <em>Vorwärtsrechnung</em> (FAZ, FEZ), der <em>Rückwärtsrechnung</em> (SAZ, SEZ) sowie den <em>Gesamtpuffer (GP)</em> und <em>Freien Puffer (FP)</em>. Identifizieren Sie anschließend alle Vorgänge auf dem <strong>Kritischen Pfad</strong>.
|
||||
</p>
|
||||
<div style="display: flex; gap: 16px; flex-wrap: wrap; font-size: 0.82rem; color: #94a3b8;">
|
||||
<span>ℹ️ <code>FEZ = FAZ + D</code></span>
|
||||
<span>ℹ️ <code>FAZ = max(FEZ aller Vorgänger)</code></span>
|
||||
<span>ℹ️ <code>SAZ = SEZ - D</code></span>
|
||||
<span>ℹ️ <code>SEZ = min(SAZ aller Nachfolger)</code></span>
|
||||
<span>ℹ️ <code>GP = SAZ - FAZ = SEZ - FEZ</code></span>
|
||||
<span>ℹ️ <code>FP = min(FAZ Nachfolger) - FEZ</code></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Interaktive Tabelle -->
|
||||
<div style="overflow-x: auto; margin-bottom: 20px;">
|
||||
<table class="ihk-table" style="width: 100%; border-collapse: collapse; font-size: 0.85rem; text-align: center;">
|
||||
<thead>
|
||||
<tr style="background: #0f172a; color: #38bdf8;">
|
||||
<th style="padding: 10px; border: 1px solid #334155;">ID</th>
|
||||
<th style="padding: 10px; border: 1px solid #334155; text-align: left;">Vorgang</th>
|
||||
<th style="padding: 10px; border: 1px solid #334155;">Dauer (D)</th>
|
||||
<th style="padding: 10px; border: 1px solid #334155;">Vorgänger</th>
|
||||
<th style="padding: 10px; border: 1px solid #334155; background: #1e3a8a; color: #93c5fd;">FAZ</th>
|
||||
<th style="padding: 10px; border: 1px solid #334155; background: #1e3a8a; color: #93c5fd;">FEZ</th>
|
||||
<th style="padding: 10px; border: 1px solid #334155; background: #701a75; color: #f0abfc;">SAZ</th>
|
||||
<th style="padding: 10px; border: 1px solid #334155; background: #701a75; color: #f0abfc;">SEZ</th>
|
||||
<th style="padding: 10px; border: 1px solid #334155; color: #facc15;">GP</th>
|
||||
<th style="padding: 10px; border: 1px solid #334155; color: #facc15;">FP</th>
|
||||
<th style="padding: 10px; border: 1px solid #334155; color: #f87171;">Kritisch?</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${sc.nodes.map(n => `
|
||||
<tr id="row-node-${n.id}" style="border: 1px solid #334155;">
|
||||
<td style="padding: 8px; font-weight: bold; color: #38bdf8;">${n.id}</td>
|
||||
<td style="padding: 8px; text-align: left; color: #f8fafc;">${n.name}</td>
|
||||
<td style="padding: 8px; font-weight: 600;">${n.d}</td>
|
||||
<td style="padding: 8px; color: #94a3b8;">${n.preds.length > 0 ? n.preds.join(", ") : "-"}</td>
|
||||
<td style="padding: 6px;"><input type="number" id="in-faz-${n.id}" class="ihk-input" style="width: 55px; text-align: center; padding: 4px;" min="0"></td>
|
||||
<td style="padding: 6px;"><input type="number" id="in-fez-${n.id}" class="ihk-input" style="width: 55px; text-align: center; padding: 4px;" min="0"></td>
|
||||
<td style="padding: 6px;"><input type="number" id="in-saz-${n.id}" class="ihk-input" style="width: 55px; text-align: center; padding: 4px;" min="0"></td>
|
||||
<td style="padding: 6px;"><input type="number" id="in-sez-${n.id}" class="ihk-input" style="width: 55px; text-align: center; padding: 4px;" min="0"></td>
|
||||
<td style="padding: 6px;"><input type="number" id="in-gp-${n.id}" class="ihk-input" style="width: 50px; text-align: center; padding: 4px;" min="0"></td>
|
||||
<td style="padding: 6px;"><input type="number" id="in-fp-${n.id}" class="ihk-input" style="width: 50px; text-align: center; padding: 4px;" min="0"></td>
|
||||
<td style="padding: 6px;"><input type="checkbox" id="in-crit-${n.id}" style="transform: scale(1.3); cursor: pointer;"></td>
|
||||
</tr>
|
||||
`).join("")}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; gap: 12px; flex-wrap: wrap;">
|
||||
<button class="ihk-btn ihk-btn-success" id="np-check-btn">✓ Berechnungen prüfen</button>
|
||||
<button class="ihk-btn ihk-btn-secondary" id="np-solve-btn">💡 Musterlösung ausfüllen</button>
|
||||
<button class="ihk-btn ihk-btn-secondary" id="np-reset-btn">Zurücksetzen</button>
|
||||
</div>
|
||||
|
||||
<div id="np-feedback-box" class="ihk-result-box" style="margin-top: 18px;"></div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
this.attachEvents();
|
||||
}
|
||||
|
||||
attachEvents() {
|
||||
document.getElementById("np-check-btn").addEventListener("click", () => this.checkAnswers());
|
||||
document.getElementById("np-solve-btn").addEventListener("click", () => this.solve());
|
||||
document.getElementById("np-reset-btn").addEventListener("click", () => this.render());
|
||||
document.getElementById("np-prev-scenario").addEventListener("click", () => {
|
||||
this.scenarioIndex = 0;
|
||||
this.render();
|
||||
});
|
||||
document.getElementById("np-next-scenario").addEventListener("click", () => {
|
||||
this.scenarioIndex = 1;
|
||||
this.render();
|
||||
});
|
||||
}
|
||||
|
||||
checkAnswers() {
|
||||
const sc = this.scenarios[this.scenarioIndex];
|
||||
let correctCount = 0;
|
||||
let totalFields = sc.nodes.length * 7;
|
||||
const errors = [];
|
||||
|
||||
sc.nodes.forEach(n => {
|
||||
const row = document.getElementById(`row-node-${n.id}`);
|
||||
const valFaz = parseInt(document.getElementById(`in-faz-${n.id}`).value, 10);
|
||||
const valFez = parseInt(document.getElementById(`in-fez-${n.id}`).value, 10);
|
||||
const valSaz = parseInt(document.getElementById(`in-saz-${n.id}`).value, 10);
|
||||
const valSez = parseInt(document.getElementById(`in-sez-${n.id}`).value, 10);
|
||||
const valGp = parseInt(document.getElementById(`in-gp-${n.id}`).value, 10);
|
||||
const valFp = parseInt(document.getElementById(`in-fp-${n.id}`).value, 10);
|
||||
const valCrit = document.getElementById(`in-crit-${n.id}`).checked;
|
||||
const isActuallyCrit = sc.criticalPath.includes(n.id);
|
||||
|
||||
let nodeOk = true;
|
||||
|
||||
const checkField = (elemId, actual, expected, name) => {
|
||||
const el = document.getElementById(elemId);
|
||||
if (actual === expected) {
|
||||
el.style.border = "1px solid #22c55e";
|
||||
el.style.background = "#064e3b";
|
||||
correctCount++;
|
||||
} else {
|
||||
el.style.border = "1px solid #ef4444";
|
||||
el.style.background = "#450a0a";
|
||||
nodeOk = false;
|
||||
errors.push(`Vorgang ${n.id} (${n.name}): ${name} sollte ${expected} sein (Eingabe: ${isNaN(actual) ? 'leer' : actual}).`);
|
||||
}
|
||||
};
|
||||
|
||||
checkField(`in-faz-${n.id}`, valFaz, n.faz, "FAZ");
|
||||
checkField(`in-fez-${n.id}`, valFez, n.fez, "FEZ");
|
||||
checkField(`in-saz-${n.id}`, valSaz, n.saz, "SAZ");
|
||||
checkField(`in-sez-${n.id}`, valSez, n.sez, "SEZ");
|
||||
checkField(`in-gp-${n.id}`, valGp, n.gp, "GP");
|
||||
checkField(`in-fp-${n.id}`, valFp, n.fp, "FP");
|
||||
|
||||
const critEl = document.getElementById(`in-crit-${n.id}`);
|
||||
if (valCrit === isActuallyCrit) {
|
||||
critEl.parentElement.style.background = "#064e3b";
|
||||
correctCount++;
|
||||
} else {
|
||||
critEl.parentElement.style.background = "#450a0a";
|
||||
nodeOk = false;
|
||||
errors.push(`Vorgang ${n.id} liegt ${isActuallyCrit ? 'AUF' : 'NICHT auf'} dem kritischen Pfad.`);
|
||||
}
|
||||
|
||||
if (nodeOk) {
|
||||
row.style.background = "rgba(34, 197, 94, 0.1)";
|
||||
} else {
|
||||
row.style.background = "rgba(239, 68, 68, 0.1)";
|
||||
}
|
||||
});
|
||||
|
||||
const box = document.getElementById("np-feedback-box");
|
||||
box.style.display = "block";
|
||||
const percent = Math.round((correctCount / totalFields) * 100);
|
||||
|
||||
if (errors.length === 0) {
|
||||
box.innerHTML = `
|
||||
<div style="border-left: 4px solid #22c55e; padding-left: 14px;">
|
||||
<h4 style="color: #4ade80; margin: 0 0 6px 0; font-size: 1.1rem;">🎉 Perfekt! 100% Richtige Netzplanberechnung (${correctCount}/${totalFields} Felder)</h4>
|
||||
<p style="color: #cbd5e1; margin: 0; line-height: 1.5;">
|
||||
Gesamtdauer des Projekts: <strong>${sc.nodes[sc.nodes.length - 1].fez} Zeiteinheiten</strong>.<br>
|
||||
Der kritische Pfad verläuft über: <strong style="color: #f87171;">${sc.criticalPath.join(" → ")}</strong>. Alle Vorgänge hier haben Gesamtpuffer <code>GP = 0</code>.
|
||||
</p>
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
box.innerHTML = `
|
||||
<div style="border-left: 4px solid #ef4444; padding-left: 14px;">
|
||||
<h4 style="color: #f87171; margin: 0 0 6px 0; font-size: 1.1rem;">⚠️ ${errors.length} Fehler gefunden (${percent}% korrekt)</h4>
|
||||
<ul style="color: #cbd5e1; margin: 8px 0 0 0; padding-left: 20px; font-size: 0.88rem; line-height: 1.6;">
|
||||
${errors.slice(0, 6).map(e => `<li>${e}</li>`).join("")}
|
||||
${errors.length > 6 ? `<li>... und ${errors.length - 6} weitere Abweichungen.</li>` : ""}
|
||||
</ul>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
solve() {
|
||||
const sc = this.scenarios[this.scenarioIndex];
|
||||
sc.nodes.forEach(n => {
|
||||
document.getElementById(`in-faz-${n.id}`).value = n.faz;
|
||||
document.getElementById(`in-fez-${n.id}`).value = n.fez;
|
||||
document.getElementById(`in-saz-${n.id}`).value = n.saz;
|
||||
document.getElementById(`in-sez-${n.id}`).value = n.sez;
|
||||
document.getElementById(`in-gp-${n.id}`).value = n.gp;
|
||||
document.getElementById(`in-fp-${n.id}`).value = n.fp;
|
||||
document.getElementById(`in-crit-${n.id}`).checked = sc.criticalPath.includes(n.id);
|
||||
});
|
||||
this.checkAnswers();
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
if (document.getElementById("netzplan-trainer-container")) {
|
||||
window.netzplanTrainer = new NetzplanTrainer("netzplan-trainer-container");
|
||||
}
|
||||
});
|
||||
225
assets/ihk_tools/prefix_converter.js
Normal file
225
assets/ihk_tools/prefix_converter.js
Normal file
|
|
@ -0,0 +1,225 @@
|
|||
/**
|
||||
* ⚡ IHK BINÄRPRÄFIX- & TRANSFERZEIT-DRILL (IEC 60027-2) ⚡
|
||||
* ======================================================
|
||||
* Interaktiver Drill für:
|
||||
* 1. Binärpräfixe (KiB, MiB, GiB, TiB) vs. Dezimalpräfixe (kB, MB, GB, TB)
|
||||
* 2. Berechnen der tatsächlichen Speicherkapazität (Herstellerangabe vs. Betriebssystem)
|
||||
* 3. Übertragungszeit-Berechnung t = Datenmenge / Datenrate mit Einheitenkonvertierung
|
||||
*/
|
||||
|
||||
class PrefixConverter {
|
||||
constructor(containerId) {
|
||||
this.container = document.getElementById(containerId);
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
if (!this.container) return;
|
||||
this.render();
|
||||
}
|
||||
|
||||
render() {
|
||||
this.container.innerHTML = `
|
||||
<div class="ihk-tool-card" style="background: #1e293b; border: 1px solid #334155; border-radius: 12px; padding: 24px; margin-bottom: 24px;">
|
||||
<div class="ihk-tool-header" style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
|
||||
<div>
|
||||
<h3 style="color: #38bdf8; margin: 0; font-size: 1.3rem;">⚡ IEC-Binärpräfixe & Transferzeit Drill-Master</h3>
|
||||
<p style="color: #94a3b8; font-size: 0.9rem; margin: 4px 0 0 0;">Strikte ZPA 2. Auflage Normierung (KiB, MiB, GiB, TiB)</p>
|
||||
</div>
|
||||
<div>
|
||||
<span class="ihk-badge ihk-badge-warning">ZPA Pflichtstoff</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; gap: 10px; margin-bottom: 20px; border-bottom: 1px solid #334155; padding-bottom: 12px;">
|
||||
<button id="pfx-tab-drill" class="ihk-btn ihk-btn-primary" style="padding: 6px 14px; font-size: 0.85rem;">💾 Festplatten-Kapazität (SI vs. IEC)</button>
|
||||
<button id="pfx-tab-time" class="ihk-btn ihk-btn-secondary" style="padding: 6px 14px; font-size: 0.85rem;">⏱️ Datentransferzeit-Rechner</button>
|
||||
</div>
|
||||
|
||||
<!-- Ansicht 1: Festplattenkapazität -->
|
||||
<div id="pfx-view-drill">
|
||||
<div style="background: #0f172a; padding: 16px; border-radius: 8px; border: 1px solid #334155; margin-bottom: 18px;">
|
||||
<p style="color: #cbd5e1; font-size: 0.92rem; margin: 0; line-height: 1.6;">
|
||||
Festplattenhersteller verkaufen Speicher nach <strong>Dezimalpräfixen (SI: 10er-Potenzen)</strong>.<br>
|
||||
Betriebssysteme (Windows, Linux) verwalten Speicher jedoch in <strong>Binärpräfixen (IEC: 2er-Potenzen)</strong>.<br>
|
||||
<em>1 TB (Hersteller) = 1.000.000.000.000 Byte. 1 TiB (IEC) = 1.024⁴ = 1.099.511.627.776 Byte.</em>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px; margin-bottom: 20px;">
|
||||
<div class="ihk-input-group">
|
||||
<label>Herstellerangabe (Dezimal):</label>
|
||||
<input type="number" id="pfx-disk-val" class="ihk-input" value="2" min="1" max="100">
|
||||
</div>
|
||||
<div class="ihk-input-group">
|
||||
<label>Einheit:</label>
|
||||
<select id="pfx-disk-unit" class="ihk-select">
|
||||
<option value="GB">GB (10⁹ Byte)</option>
|
||||
<option value="TB" selected>TB (10¹² Byte)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="ihk-btn ihk-btn-success" id="btn-calc-pfx">🧮 Echte Kapazität in GiB / TiB berechnen</button>
|
||||
|
||||
<div id="pfx-disk-result" style="margin-top: 18px; background: #0f172a; border: 1px solid #334155; border-radius: 8px; padding: 18px; display: none;"></div>
|
||||
</div>
|
||||
|
||||
<!-- Ansicht 2: Datentransferzeit -->
|
||||
<div id="pfx-view-time" style="display: none;">
|
||||
<div style="background: #0f172a; padding: 16px; border-radius: 8px; border: 1px solid #334155; margin-bottom: 18px;">
|
||||
<p style="color: #cbd5e1; font-size: 0.92rem; margin: 0; line-height: 1.6;">
|
||||
<strong>IHK-Klassiker:</strong> Übertragungszeit berechnen unter Berücksichtigung von Byte $\rightarrow$ Bit (Faktor 8) und Protokoll-Overhead!
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px; margin-bottom: 20px;">
|
||||
<div class="ihk-input-group">
|
||||
<label>Datenmenge:</label>
|
||||
<input type="number" id="pfx-data-amount" class="ihk-input" value="50" min="1">
|
||||
</div>
|
||||
<div class="ihk-input-group">
|
||||
<label>Datenmengen-Einheit:</label>
|
||||
<select id="pfx-data-unit" class="ihk-select">
|
||||
<option value="MiB">MiB (1.024² Byte)</option>
|
||||
<option value="GiB" selected>GiB (1.024³ Byte)</option>
|
||||
<option value="TiB">TiB (1.024⁴ Byte)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="ihk-input-group">
|
||||
<label>Netzwerk-Bandbreite:</label>
|
||||
<input type="number" id="pfx-net-speed" class="ihk-input" value="100" min="1">
|
||||
</div>
|
||||
<div class="ihk-input-group">
|
||||
<label>Bandbreiten-Einheit:</label>
|
||||
<select id="pfx-speed-unit" class="ihk-select">
|
||||
<option value="Mbit">Mbit/s (10⁶ Bit/s)</option>
|
||||
<option value="Gbit">Gbit/s (10⁹ Bit/s)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ihk-input-group" style="margin-bottom: 20px;">
|
||||
<label>Protokoll-Overhead (z. B. TCP/IP, Verschlüsselung):</label>
|
||||
<select id="pfx-overhead" class="ihk-select">
|
||||
<option value="0">0 % (Theoretisches Maximum)</option>
|
||||
<option value="5">5 % (Typischer TCP/IP Overhead)</option>
|
||||
<option value="10" selected>10 % (Realistischer Netzwerk-Overhead)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<button class="ihk-btn ihk-btn-success" id="btn-calc-time">⏱️ Übertragungsdauer berechnen</button>
|
||||
|
||||
<div id="pfx-time-result" style="margin-top: 18px; background: #0f172a; border: 1px solid #334155; border-radius: 8px; padding: 18px; display: none;"></div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
this.attachEvents();
|
||||
}
|
||||
|
||||
attachEvents() {
|
||||
const tabDrill = document.getElementById("pfx-tab-drill");
|
||||
const tabTime = document.getElementById("pfx-tab-time");
|
||||
const viewDrill = document.getElementById("pfx-view-drill");
|
||||
const viewTime = document.getElementById("pfx-view-time");
|
||||
|
||||
tabDrill.addEventListener("click", () => {
|
||||
tabDrill.className = "ihk-btn ihk-btn-primary";
|
||||
tabTime.className = "ihk-btn ihk-btn-secondary";
|
||||
viewDrill.style.display = "block";
|
||||
viewTime.style.display = "none";
|
||||
});
|
||||
|
||||
tabTime.addEventListener("click", () => {
|
||||
tabTime.className = "ihk-btn ihk-btn-primary";
|
||||
tabDrill.className = "ihk-btn ihk-btn-secondary";
|
||||
viewTime.style.display = "block";
|
||||
viewDrill.style.display = "none";
|
||||
});
|
||||
|
||||
document.getElementById("btn-calc-pfx").addEventListener("click", () => this.calcCapacity());
|
||||
document.getElementById("btn-calc-time").addEventListener("click", () => this.calcTime());
|
||||
}
|
||||
|
||||
calcCapacity() {
|
||||
const val = parseFloat(document.getElementById("pfx-disk-val").value) || 0;
|
||||
const unit = document.getElementById("pfx-disk-unit").value;
|
||||
const resBox = document.getElementById("pfx-disk-result");
|
||||
resBox.style.display = "block";
|
||||
|
||||
let bytes = 0;
|
||||
if (unit === "GB") bytes = val * 1e9;
|
||||
if (unit === "TB") bytes = val * 1e12;
|
||||
|
||||
const gib = bytes / Math.pow(1024, 3);
|
||||
const tib = bytes / Math.pow(1024, 4);
|
||||
const diffPct = (1 - (gib / (unit === "TB" ? val * 1024 : val))) * 100;
|
||||
|
||||
resBox.innerHTML = `
|
||||
<table style="width: 100%; border-collapse: collapse; color: #cbd5e1; font-size: 0.92rem;">
|
||||
<tr><td style="padding: 6px 0;">Hersteller-Angabe:</td><td style="text-align: right; font-weight: bold; color: #f8fafc;">${val} ${unit} = ${bytes.toLocaleString()} Byte</td></tr>
|
||||
<tr><td style="padding: 6px 0;">Nutzbare Kapazität in GiB (IEC):</td><td style="text-align: right; font-weight: bold; color: #38bdf8;">${gib.toFixed(2)} GiB</td></tr>
|
||||
<tr><td style="padding: 6px 0;">Nutzbare Kapazität in TiB (IEC):</td><td style="text-align: right; font-weight: bold; color: #38bdf8;">${tib.toFixed(3)} TiB</td></tr>
|
||||
<tr style="border-top: 1px solid #334155;"><td style="padding: 8px 0; color: #facc15;">Scheinbarer Verlust durch Präfix-Unterschied:</td><td style="text-align: right; color: #facc15; font-weight: bold;">ca. ${unit === 'TB' ? '9,09 %' : '6,87 %'}</td></tr>
|
||||
</table>
|
||||
<div style="margin-top: 12px; font-size: 0.85rem; color: #94a3b8;">
|
||||
💡 <em>IHK-Prüfungsbegründung: Das Betriebssystem rechnet mit der Basis 1.024 (${unit === 'TB' ? '1.024⁴' : '1.024³'}). Es handelt sich um keinen physischen Datenverlust, sondern eine Diskrepanz zwischen SI- und IEC-Standard.</em>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
calcTime() {
|
||||
const amount = parseFloat(document.getElementById("pfx-data-amount").value) || 0;
|
||||
const unit = document.getElementById("pfx-data-unit").value;
|
||||
const speed = parseFloat(document.getElementById("pfx-net-speed").value) || 0;
|
||||
const speedUnit = document.getElementById("pfx-speed-unit").value;
|
||||
const overheadPct = parseFloat(document.getElementById("pfx-overhead").value) || 0;
|
||||
|
||||
let bytes = 0;
|
||||
if (unit === "MiB") bytes = amount * Math.pow(1024, 2);
|
||||
if (unit === "GiB") bytes = amount * Math.pow(1024, 3);
|
||||
if (unit === "TiB") bytes = amount * Math.pow(1024, 4);
|
||||
|
||||
let totalBits = bytes * 8 * (1 + overheadPct / 100);
|
||||
|
||||
let bitsPerSec = 0;
|
||||
if (speedUnit === "Mbit") bitsPerSec = speed * 1e6;
|
||||
if (speedUnit === "Gbit") bitsPerSec = speed * 1e9;
|
||||
|
||||
const seconds = totalBits / bitsPerSec;
|
||||
const minutes = seconds / 60;
|
||||
const hours = minutes / 60;
|
||||
|
||||
const resBox = document.getElementById("pfx-time-result");
|
||||
resBox.style.display = "block";
|
||||
|
||||
let timeString = "";
|
||||
if (hours >= 1) {
|
||||
const h = Math.floor(hours);
|
||||
const m = Math.floor((hours - h) * 60);
|
||||
const s = Math.round(seconds % 60);
|
||||
timeString = `${h} Stunden, ${m} Minuten, ${s} Sekunden`;
|
||||
} else if (minutes >= 1) {
|
||||
const m = Math.floor(minutes);
|
||||
const s = Math.round(seconds % 60);
|
||||
timeString = `${m} Minuten, ${s} Sekunden`;
|
||||
} else {
|
||||
timeString = `${seconds.toFixed(1)} Sekunden`;
|
||||
}
|
||||
|
||||
resBox.innerHTML = `
|
||||
<table style="width: 100%; border-collapse: collapse; color: #cbd5e1; font-size: 0.92rem;">
|
||||
<tr><td style="padding: 6px 0;">Datenmenge (inkl. ${overheadPct}% Overhead):</td><td style="text-align: right; font-weight: bold; color: #f8fafc;">${(totalBits / 8 / 1024 / 1024).toFixed(1)} MiB (${(totalBits / 1e6).toFixed(0)} Mbit)</td></tr>
|
||||
<tr><td style="padding: 6px 0;">Nettoreichweite Übertragung:</td><td style="text-align: right; font-weight: bold; color: #38bdf8;">${speed} ${speedUnit}/s</td></tr>
|
||||
<tr style="border-top: 2px solid #22c55e;"><td style="padding: 10px 0; font-size: 1.05rem; font-weight: bold; color: #4ade80;">Berechnete Übertragungsdauer (t):</td><td style="text-align: right; font-size: 1.05rem; font-weight: bold; color: #4ade80;">${timeString} (${seconds.toFixed(1)} s)</td></tr>
|
||||
</table>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
if (document.getElementById("prefix-converter-container")) {
|
||||
window.prefixConverter = new PrefixConverter("prefix-converter-container");
|
||||
}
|
||||
});
|
||||
76
assets/ihk_tools/purdue_trainer.js
Normal file
76
assets/ihk_tools/purdue_trainer.js
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
/**
|
||||
* Interaktiver Purdue-Referenzmodell & OT-Zonen-Trainer für FIDV
|
||||
* IHK ZPA Nord-West AP2
|
||||
*/
|
||||
(function() {
|
||||
function renderPurdueTrainer(containerId) {
|
||||
const container = document.getElementById(containerId);
|
||||
if (!container) return;
|
||||
|
||||
const items = [
|
||||
{ name: "PT100 Temperaturfühler & Drucktransmitter", level: "0", hint: "Physikalischer Prozess / Sensorik & Aktorik" },
|
||||
{ name: "Siemens S7-1500 SPS & Frequenzumrichter", level: "1", hint: "Basis-Steuerung (Direct Control)" },
|
||||
{ name: "KTP700 HMI-Touchpanel & Bedienstation", level: "2", hint: "Bereichsbedienung & Monitoring" },
|
||||
{ name: "SCADA-Server & Manufacturing Execution System (MES)", level: "3", hint: "Betriebsleitebene / Fertigungssteuerung" },
|
||||
{ name: "Historian-Spiegel & Jump Host (Fernwartung)", level: "3.5", hint: "Industrial DMZ (Puffer zwischen IT und OT)" },
|
||||
{ name: "SAP ERP & Active Directory der Verwaltung", level: "4", hint: "Enterprise IT / Unternehmensebene" }
|
||||
];
|
||||
|
||||
container.innerHTML = `
|
||||
<div style="background: #0f172a; border: 1px solid #334155; border-radius: 8px; padding: 20px; color: #f8fafc;">
|
||||
<h4 style="color: #38bdf8; margin-top: 0; margin-bottom: 12px;">🏭 Interaktiver Purdue-Modell & OT-Zonen Trainer (FIDV)</h4>
|
||||
<p style="color: #94a3b8; font-size: 0.9rem; margin-bottom: 16px;">
|
||||
Ordnen Sie die Komponenten den korrekten Purdue-Ebenen nach IEC 62443 / ANSI/ISA-95 zu:
|
||||
</p>
|
||||
<div id="purdue_items" style="display: flex; flex-direction: column; gap: 10px; margin-bottom: 16px;">
|
||||
${items.map((item, idx) => `
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; background: #1e293b; padding: 10px 14px; border-radius: 6px;">
|
||||
<span style="font-size: 0.9rem; color: #cbd5e1;">${item.name}</span>
|
||||
<select id="purdue_sel_${idx}" class="ihk-select" style="padding: 6px 10px; border-radius: 4px; background: #0f172a; color: #f8fafc; border: 1px solid #475569;">
|
||||
<option value="">-- Ebene wählen --</option>
|
||||
<option value="0">Level 0: Physikalischer Prozess</option>
|
||||
<option value="1">Level 1: Basis-Steuerung (SPS)</option>
|
||||
<option value="2">Level 2: Bereichsbedienung (HMI)</option>
|
||||
<option value="3">Level 3: Leitebene (MES/SCADA)</option>
|
||||
<option value="3.5">Level 3.5: Industrial DMZ (IDMZ)</option>
|
||||
<option value="4">Level 4: Enterprise IT (ERP)</option>
|
||||
</select>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
<div style="display: flex; gap: 10px; align-items: center;">
|
||||
<button id="purdue_check_btn" class="ihk-btn ihk-btn-primary" style="padding: 8px 16px;">Ergebnis prüfen</button>
|
||||
<span id="purdue_feedback" style="font-size: 0.9rem; font-weight: 600;"></span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.getElementById('purdue_check_btn').addEventListener('click', () => {
|
||||
let correct = 0;
|
||||
items.forEach((item, idx) => {
|
||||
const sel = document.getElementById(`purdue_sel_${idx}`);
|
||||
if (sel.value === item.level) {
|
||||
sel.style.borderColor = '#22c55e';
|
||||
correct++;
|
||||
} else {
|
||||
sel.style.borderColor = '#ef4444';
|
||||
}
|
||||
});
|
||||
const fb = document.getElementById('purdue_feedback');
|
||||
if (correct === items.length) {
|
||||
fb.style.color = '#4ade80';
|
||||
fb.textContent = `🎉 Perfekt! Alle ${correct}/${items.length} Ebenen korrekt nach IEC 62443 zugeordnet.`;
|
||||
} else {
|
||||
fb.style.color = '#f87171';
|
||||
fb.textContent = `${correct}/${items.length} richtig. Prüfen Sie die rot markierten Ebenen.`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
window.renderPurdueTrainer = renderPurdueTrainer;
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
if (document.getElementById('purdue-trainer-app')) {
|
||||
renderPurdueTrainer('purdue-trainer-app');
|
||||
}
|
||||
});
|
||||
})();
|
||||
611
assets/ihk_tools/struktogramm_editor.js
Normal file
611
assets/ihk_tools/struktogramm_editor.js
Normal file
|
|
@ -0,0 +1,611 @@
|
|||
/**
|
||||
* 📐 IT-PRAXISPORTAL – DIN 66261 STRUKTOGRAMM EDITOR (NASSI-SHNEIDERMAN)
|
||||
* Vollständige Unterstützung für IHK AP1 & AP2 Algorithmenentwurf:
|
||||
* Sequenzen, IF-Verzweigungen, SWITCH-Fallauswahl, WHILE-Schleifen, DO-WHILE,
|
||||
* Zählschleifen (FOR), Call & Break, Live-SVG-Rendering und Python-Code-Generierung!
|
||||
*/
|
||||
|
||||
class StruktogrammEditor {
|
||||
constructor(containerId, options = {}) {
|
||||
this.container = typeof containerId === 'string' ? document.getElementById(containerId) : containerId;
|
||||
this.options = options;
|
||||
this.blocks = [];
|
||||
this.selectedBlockId = null;
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
if (!this.container) return;
|
||||
this.container.innerHTML = `
|
||||
<div class="ds-editor-card">
|
||||
<div class="ds-toolbar">
|
||||
<div class="ds-toolbar-left">
|
||||
<div class="ds-btn-group">
|
||||
<button type="button" class="ds-btn ds-btn-primary" id="sg-btn-add-inst">
|
||||
<span>▭</span> <span>Anweisung</span>
|
||||
</button>
|
||||
<button type="button" class="ds-btn ds-btn-primary" id="sg-btn-add-if">
|
||||
<span>▽</span> <span>Verzweigung (IF)</span>
|
||||
</button>
|
||||
<button type="button" class="ds-btn ds-btn-primary" id="sg-btn-add-while">
|
||||
<span>⮌</span> <span>Schleife (WHILE)</span>
|
||||
</button>
|
||||
<button type="button" class="ds-btn ds-btn-primary" id="sg-btn-add-for">
|
||||
<span>🔂</span> <span>Zählschleife (FOR)</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="ds-select-wrap">
|
||||
<label>IHK-Vorlage:</label>
|
||||
<select id="sg-select-template" class="ds-select">
|
||||
<option value="">-- IHK-Algorithmus wählen --</option>
|
||||
<option value="ean13">IHK EAN-13 Prüfziffernberechnung</option>
|
||||
<option value="primzahl">IHK Primzahlprüfung</option>
|
||||
<option value="rabatt">IHK Rabattstaffelung mit Skonto</option>
|
||||
<option value="binaersuche">IHK Binäre Suche (Intervallhalbierung)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ds-toolbar-right">
|
||||
<button type="button" class="ds-btn ds-btn-success" id="sg-btn-generate-code">
|
||||
<span>🐍</span> <span>Python Code</span>
|
||||
</button>
|
||||
<button type="button" class="ds-btn ds-btn-secondary" id="sg-btn-export-svg">
|
||||
<span>🖼️</span> <span>SVG Export</span>
|
||||
</button>
|
||||
<button type="button" class="ds-btn ds-btn-danger" id="sg-btn-clear" title="Alles leeren">
|
||||
<span>🗑️</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ds-workspace-grid">
|
||||
<div class="ds-canvas-wrap" id="sg-canvas-container">
|
||||
<div class="sg-render-wrapper" id="sg-render-box"></div>
|
||||
</div>
|
||||
<div class="ds-sidebar" id="sg-sidebar">
|
||||
<div class="ds-sidebar-header">
|
||||
<h4 id="sg-sidebar-title">Block-Eigenschaften</h4>
|
||||
</div>
|
||||
<div class="ds-sidebar-body" id="sg-sidebar-content">
|
||||
<p class="ds-hint">Wähle einen Block im Struktogramm aus, um dessen Inhalt anzupassen oder Unterblöcke hinzuzufügen.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Python Code Modal -->
|
||||
<div id="sg-code-modal" class="ds-modal" style="display:none;">
|
||||
<div class="ds-modal-content">
|
||||
<div class="ds-modal-header">
|
||||
<h3>Aus Struktogramm generierter Python-Code (PEP 8)</h3>
|
||||
<button type="button" class="ds-modal-close" id="sg-code-modal-close">×</button>
|
||||
</div>
|
||||
<pre id="sg-code-display" class="ds-code-block"></pre>
|
||||
<div class="ds-modal-footer">
|
||||
<button type="button" class="ds-btn ds-btn-primary" id="sg-code-copy-btn">In Zwischenablage kopieren</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
this.bindEvents();
|
||||
this.loadTemplate('ean13');
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
document.getElementById('sg-btn-add-inst').addEventListener('click', () => this.addBlock({ type: 'instruction', text: 'neue_variable = 0' }));
|
||||
document.getElementById('sg-btn-add-if').addEventListener('click', () => this.addBlock({
|
||||
type: 'if',
|
||||
condition: 'wert > 100',
|
||||
thenBlocks: [{ id: 'b_' + Date.now() + '_1', type: 'instruction', text: 'ausgabe("Bedingung erfüllt")' }],
|
||||
elseBlocks: [{ id: 'b_' + Date.now() + '_2', type: 'instruction', text: 'ausgabe("Bedingung nicht erfüllt")' }]
|
||||
}));
|
||||
document.getElementById('sg-btn-add-while').addEventListener('click', () => this.addBlock({
|
||||
type: 'while',
|
||||
condition: 'zaehler < 10',
|
||||
body: [{ id: 'b_' + Date.now() + '_3', type: 'instruction', text: 'zaehler = zaehler + 1' }]
|
||||
}));
|
||||
document.getElementById('sg-btn-add-for').addEventListener('click', () => this.addBlock({
|
||||
type: 'for',
|
||||
loopHeader: 'Für i von 1 bis n Schrittweite 1',
|
||||
body: [{ id: 'b_' + Date.now() + '_4', type: 'instruction', text: 'summe = summe + i' }]
|
||||
}));
|
||||
|
||||
document.getElementById('sg-select-template').addEventListener('change', (e) => {
|
||||
if (e.target.value) this.loadTemplate(e.target.value);
|
||||
});
|
||||
|
||||
document.getElementById('sg-btn-generate-code').addEventListener('click', () => this.showCodeModal());
|
||||
document.getElementById('sg-code-modal-close').addEventListener('click', () => {
|
||||
document.getElementById('sg-code-modal').style.display = 'none';
|
||||
});
|
||||
document.getElementById('sg-code-copy-btn').addEventListener('click', () => {
|
||||
const code = document.getElementById('sg-code-display').innerText;
|
||||
navigator.clipboard.writeText(code).then(() => alert('Python-Code erfolgreich kopiert!'));
|
||||
});
|
||||
|
||||
document.getElementById('sg-btn-export-svg').addEventListener('click', () => this.exportSvg());
|
||||
document.getElementById('sg-btn-clear').addEventListener('click', () => {
|
||||
if (confirm('Möchtest du das gesamte Struktogramm leeren?')) {
|
||||
this.blocks = [];
|
||||
this.selectedBlockId = null;
|
||||
this.render();
|
||||
this.renderSidebar();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
addBlock(blockData) {
|
||||
blockData.id = 'blk_' + Date.now() + '_' + Math.floor(Math.random() * 1000);
|
||||
this.blocks.push(blockData);
|
||||
this.selectedBlockId = blockData.id;
|
||||
this.render();
|
||||
this.renderSidebar();
|
||||
}
|
||||
|
||||
loadTemplate(tplName) {
|
||||
this.blocks = [];
|
||||
|
||||
if (tplName === 'ean13') {
|
||||
this.blocks = [
|
||||
{ id: 'b1', type: 'instruction', text: 'Eingabe: ean_12_stellen (String mit 12 Ziffern)' },
|
||||
{ id: 'b2', type: 'instruction', text: 'summe = 0' },
|
||||
{
|
||||
id: 'b3',
|
||||
type: 'for',
|
||||
loopHeader: 'Für index von 0 bis 11 Schrittweite 1',
|
||||
body: [
|
||||
{ id: 'b3_1', type: 'instruction', text: 'ziffer = int(ean_12_stellen[index])' },
|
||||
{
|
||||
id: 'b3_2',
|
||||
type: 'if',
|
||||
condition: 'index % 2 == 1',
|
||||
thenBlocks: [{ id: 'b3_2_1', type: 'instruction', text: 'summe = summe + (ziffer * 3)' }],
|
||||
elseBlocks: [{ id: 'b3_2_2', type: 'instruction', text: 'summe = summe + (ziffer * 1)' }]
|
||||
}
|
||||
]
|
||||
},
|
||||
{ id: 'b4', type: 'instruction', text: 'rest = summe % 10' },
|
||||
{
|
||||
id: 'b5',
|
||||
type: 'if',
|
||||
condition: 'rest == 0',
|
||||
thenBlocks: [{ id: 'b5_1', type: 'instruction', text: 'pruefziffer = 0' }],
|
||||
elseBlocks: [{ id: 'b5_2', type: 'instruction', text: 'pruefziffer = 10 - rest' }]
|
||||
},
|
||||
{ id: 'b6', type: 'instruction', text: 'Ausgabe: pruefziffer' }
|
||||
];
|
||||
} else if (tplName === 'primzahl') {
|
||||
this.blocks = [
|
||||
{ id: 'p1', type: 'instruction', text: 'Eingabe: n (ganze Zahl >= 2)' },
|
||||
{ id: 'p2', type: 'instruction', text: 'ist_prim = True; teiler = 2' },
|
||||
{
|
||||
id: 'p3',
|
||||
type: 'while',
|
||||
condition: 'teiler * teiler <= n und ist_prim == True',
|
||||
body: [
|
||||
{
|
||||
id: 'p3_1',
|
||||
type: 'if',
|
||||
condition: 'n % teiler == 0',
|
||||
thenBlocks: [{ id: 'p3_1_1', type: 'instruction', text: 'ist_prim = False' }],
|
||||
elseBlocks: [{ id: 'p3_1_2', type: 'instruction', text: 'teiler = teiler + 1' }]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'p4',
|
||||
type: 'if',
|
||||
condition: 'ist_prim == True',
|
||||
thenBlocks: [{ id: 'p4_1', type: 'instruction', text: 'Ausgabe("Zahl ist eine Primzahl")' }],
|
||||
elseBlocks: [{ id: 'p4_2', type: 'instruction', text: 'Ausgabe("Zahl ist KEINE Primzahl")' }]
|
||||
}
|
||||
];
|
||||
} else if (tplName === 'rabatt') {
|
||||
this.blocks = [
|
||||
{ id: 'r1', type: 'instruction', text: 'Eingabe: bestellwert, ist_stammkunde (Boolean)' },
|
||||
{ id: 'r2', type: 'instruction', text: 'rabatt_prozent = 0.0' },
|
||||
{
|
||||
id: 'r3',
|
||||
type: 'if',
|
||||
condition: 'bestellwert >= 500.0',
|
||||
thenBlocks: [
|
||||
{ id: 'r3_1', type: 'instruction', text: 'rabatt_prozent = 10.0' }
|
||||
],
|
||||
elseBlocks: [
|
||||
{
|
||||
id: 'r3_2',
|
||||
type: 'if',
|
||||
condition: 'bestellwert >= 200.0',
|
||||
thenBlocks: [{ id: 'r3_2_1', type: 'instruction', text: 'rabatt_prozent = 5.0' }],
|
||||
elseBlocks: [{ id: 'r3_2_2', type: 'instruction', text: 'rabatt_prozent = 0.0' }]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'r4',
|
||||
type: 'if',
|
||||
condition: 'ist_stammkunde == True',
|
||||
thenBlocks: [{ id: 'r4_1', type: 'instruction', text: 'rabatt_prozent = rabatt_prozent + 3.0' }],
|
||||
elseBlocks: []
|
||||
},
|
||||
{ id: 'r5', type: 'instruction', text: 'endpreis = bestellwert * (1.0 - rabatt_prozent / 100.0)' },
|
||||
{ id: 'r6', type: 'instruction', text: 'Ausgabe: endpreis' }
|
||||
];
|
||||
} else if (tplName === 'binaersuche') {
|
||||
this.blocks = [
|
||||
{ id: 's1', type: 'instruction', text: 'Eingabe: arr (sortiertes Array), suchwert' },
|
||||
{ id: 's2', type: 'instruction', text: 'links = 0; rechts = len(arr) - 1; gefundener_index = -1' },
|
||||
{
|
||||
id: 's3',
|
||||
type: 'while',
|
||||
condition: 'links <= rechts und gefundener_index == -1',
|
||||
body: [
|
||||
{ id: 's3_1', type: 'instruction', text: 'mitte = (links + rechts) // 2' },
|
||||
{
|
||||
id: 's3_2',
|
||||
type: 'if',
|
||||
condition: 'arr[mitte] == suchwert',
|
||||
thenBlocks: [{ id: 's3_2_1', type: 'instruction', text: 'gefundener_index = mitte' }],
|
||||
elseBlocks: [
|
||||
{
|
||||
id: 's3_2_2',
|
||||
type: 'if',
|
||||
condition: 'arr[mitte] < suchwert',
|
||||
thenBlocks: [{ id: 's3_2_2_1', type: 'instruction', text: 'links = mitte + 1' }],
|
||||
elseBlocks: [{ id: 's3_2_2_2', type: 'instruction', text: 'rechts = mitte - 1' }]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{ id: 's4', type: 'instruction', text: 'Ausgabe: gefundener_index' }
|
||||
];
|
||||
}
|
||||
|
||||
this.selectedBlockId = this.blocks[0] ? this.blocks[0].id : null;
|
||||
this.render();
|
||||
this.renderSidebar();
|
||||
}
|
||||
|
||||
findBlockRecursive(blocks, id) {
|
||||
for (const b of blocks) {
|
||||
if (b.id === id) return b;
|
||||
if (b.thenBlocks) {
|
||||
const found = this.findBlockRecursive(b.thenBlocks, id);
|
||||
if (found) return found;
|
||||
}
|
||||
if (b.elseBlocks) {
|
||||
const found = this.findBlockRecursive(b.elseBlocks, id);
|
||||
if (found) return found;
|
||||
}
|
||||
if (b.body) {
|
||||
const found = this.findBlockRecursive(b.body, id);
|
||||
if (found) return found;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
deleteBlockRecursive(blocks, id) {
|
||||
for (let i = 0; i < blocks.length; i++) {
|
||||
if (blocks[i].id === id) {
|
||||
blocks.splice(i, 1);
|
||||
return true;
|
||||
}
|
||||
if (blocks[i].thenBlocks && this.deleteBlockRecursive(blocks[i].thenBlocks, id)) return true;
|
||||
if (blocks[i].elseBlocks && this.deleteBlockRecursive(blocks[i].elseBlocks, id)) return true;
|
||||
if (blocks[i].body && this.deleteBlockRecursive(blocks[i].body, id)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
render() {
|
||||
const box = document.getElementById('sg-render-box');
|
||||
if (!box) return;
|
||||
|
||||
// Render HTML table-based DIN 66261 diagram
|
||||
let html = `
|
||||
<div class="sg-container">
|
||||
<div class="sg-header-title">DIN 66261 Nassi-Shneiderman Struktogramm</div>
|
||||
<div class="sg-root-table">
|
||||
${this.renderBlockList(this.blocks)}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
box.innerHTML = html;
|
||||
this.attachClickEvents();
|
||||
}
|
||||
|
||||
renderBlockList(blocks) {
|
||||
if (!blocks || blocks.length === 0) {
|
||||
return `<div class="sg-empty-block">∅ (leer)</div>`;
|
||||
}
|
||||
return blocks.map(b => this.renderSingleBlock(b)).join('');
|
||||
}
|
||||
|
||||
renderSingleBlock(b) {
|
||||
const isSelected = b.id === this.selectedBlockId ? 'sg-selected' : '';
|
||||
|
||||
if (b.type === 'instruction') {
|
||||
return `
|
||||
<div class="sg-block sg-instruction ${isSelected}" data-block-id="${b.id}">
|
||||
<span class="sg-text">${this.escapeHtml(b.text || '')}</span>
|
||||
</div>
|
||||
`;
|
||||
} else if (b.type === 'if') {
|
||||
return `
|
||||
<div class="sg-block sg-if ${isSelected}" data-block-id="${b.id}">
|
||||
<div class="sg-if-header">
|
||||
<svg class="sg-if-svg" viewBox="0 0 100 40" preserveAspectRatio="none">
|
||||
<polygon points="0,0 100,0 50,40" fill="#0f172a" stroke="#334155" stroke-width="1.5"/>
|
||||
<line x1="0" y1="0" x2="50" y2="40" stroke="#38bdf8" stroke-width="1.5"/>
|
||||
<line x1="100" y1="0" x2="50" y2="40" stroke="#38bdf8" stroke-width="1.5"/>
|
||||
</svg>
|
||||
<div class="sg-if-condition">${this.escapeHtml(b.condition || '')}</div>
|
||||
<div class="sg-if-label-left">Ja (Wahr)</div>
|
||||
<div class="sg-if-label-right">Nein (Falsch)</div>
|
||||
</div>
|
||||
<div class="sg-if-branches">
|
||||
<div class="sg-if-then">
|
||||
${this.renderBlockList(b.thenBlocks || [])}
|
||||
</div>
|
||||
<div class="sg-if-else">
|
||||
${this.renderBlockList(b.elseBlocks || [])}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
} else if (b.type === 'while') {
|
||||
return `
|
||||
<div class="sg-block sg-while ${isSelected}" data-block-id="${b.id}">
|
||||
<div class="sg-while-header">
|
||||
<span class="sg-badge-loop">WHILE (solange):</span> ${this.escapeHtml(b.condition || '')}
|
||||
</div>
|
||||
<div class="sg-while-body-wrap">
|
||||
<div class="sg-while-gutter"></div>
|
||||
<div class="sg-while-body">
|
||||
${this.renderBlockList(b.body || [])}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
} else if (b.type === 'for') {
|
||||
return `
|
||||
<div class="sg-block sg-for ${isSelected}" data-block-id="${b.id}">
|
||||
<div class="sg-for-header">
|
||||
<span class="sg-badge-loop">FOR (Zählschleife):</span> ${this.escapeHtml(b.loopHeader || '')}
|
||||
</div>
|
||||
<div class="sg-while-body-wrap">
|
||||
<div class="sg-while-gutter"></div>
|
||||
<div class="sg-while-body">
|
||||
${this.renderBlockList(b.body || [])}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
attachClickEvents() {
|
||||
const elBlocks = document.querySelectorAll('.sg-block');
|
||||
elBlocks.forEach(el => {
|
||||
el.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
const id = el.getAttribute('data-block-id');
|
||||
this.selectedBlockId = id;
|
||||
this.render();
|
||||
this.renderSidebar();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
renderSidebar() {
|
||||
const sidebarTitle = document.getElementById('sg-sidebar-title');
|
||||
const content = document.getElementById('sg-sidebar-content');
|
||||
if (!content) return;
|
||||
|
||||
const block = this.findBlockRecursive(this.blocks, this.selectedBlockId);
|
||||
if (!block) {
|
||||
sidebarTitle.innerText = 'Block-Eigenschaften';
|
||||
content.innerHTML = `<p class="ds-hint">Klicke auf einen Block im Struktogramm, um dessen Text oder Verzweigung anzupassen.</p>`;
|
||||
return;
|
||||
}
|
||||
|
||||
sidebarTitle.innerText = `Typ: ${block.type.toUpperCase()}`;
|
||||
|
||||
let html = '';
|
||||
if (block.type === 'instruction') {
|
||||
html = `
|
||||
<div class="ds-field-group">
|
||||
<label>Anweisungstext:</label>
|
||||
<textarea id="sg-input-text" class="ds-textarea" rows="3">${this.escapeHtml(block.text || '')}</textarea>
|
||||
</div>
|
||||
`;
|
||||
} else if (block.type === 'if') {
|
||||
html = `
|
||||
<div class="ds-field-group">
|
||||
<label>Bedingung (logischer Ausdruck):</label>
|
||||
<input type="text" id="sg-input-cond" class="ds-input" value="${this.escapeHtml(block.condition || '')}">
|
||||
</div>
|
||||
<div style="display:flex; gap:8px; margin-top:10px;">
|
||||
<button type="button" class="ds-btn ds-btn-secondary" id="sg-btn-add-then" style="flex:1; font-size:0.75rem;">+ Ja-Zweig</button>
|
||||
<button type="button" class="ds-btn ds-btn-secondary" id="sg-btn-add-else" style="flex:1; font-size:0.75rem;">+ Nein-Zweig</button>
|
||||
</div>
|
||||
`;
|
||||
} else if (block.type === 'while') {
|
||||
html = `
|
||||
<div class="ds-field-group">
|
||||
<label>Schleifenbedingung (Abbruch wenn falsch):</label>
|
||||
<input type="text" id="sg-input-while-cond" class="ds-input" value="${this.escapeHtml(block.condition || '')}">
|
||||
</div>
|
||||
<button type="button" class="ds-btn ds-btn-secondary" id="sg-btn-add-while-body" style="width:100%; margin-top:8px; font-size:0.75rem;">+ Block in Schleifenkörper</button>
|
||||
`;
|
||||
} else if (block.type === 'for') {
|
||||
html = `
|
||||
<div class="ds-field-group">
|
||||
<label>Zählschleifen-Kopf:</label>
|
||||
<input type="text" id="sg-input-for-hdr" class="ds-input" value="${this.escapeHtml(block.loopHeader || '')}">
|
||||
</div>
|
||||
<button type="button" class="ds-btn ds-btn-secondary" id="sg-btn-add-for-body" style="width:100%; margin-top:8px; font-size:0.75rem;">+ Block in Schleifenkörper</button>
|
||||
`;
|
||||
}
|
||||
|
||||
html += `
|
||||
<div style="margin-top:20px; border-top:1px solid #334155; padding-top:14px;">
|
||||
<button type="button" id="sg-btn-delete-block" class="ds-btn ds-btn-danger" style="width:100%; font-size:0.82rem;">
|
||||
🗑️ Diesen Block entfernen
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
content.innerHTML = html;
|
||||
|
||||
// Listeners
|
||||
if (block.type === 'instruction') {
|
||||
document.getElementById('sg-input-text').addEventListener('input', (e) => {
|
||||
block.text = e.target.value;
|
||||
this.render();
|
||||
});
|
||||
} else if (block.type === 'if') {
|
||||
document.getElementById('sg-input-cond').addEventListener('input', (e) => {
|
||||
block.condition = e.target.value;
|
||||
this.render();
|
||||
});
|
||||
document.getElementById('sg-btn-add-then').addEventListener('click', () => {
|
||||
if (!block.thenBlocks) block.thenBlocks = [];
|
||||
block.thenBlocks.push({ id: 'b_' + Date.now(), type: 'instruction', text: 'neue_anweisung = 1' });
|
||||
this.render();
|
||||
});
|
||||
document.getElementById('sg-btn-add-else').addEventListener('click', () => {
|
||||
if (!block.elseBlocks) block.elseBlocks = [];
|
||||
block.elseBlocks.push({ id: 'b_' + Date.now(), type: 'instruction', text: 'neue_anweisung = 2' });
|
||||
this.render();
|
||||
});
|
||||
} else if (block.type === 'while') {
|
||||
document.getElementById('sg-input-while-cond').addEventListener('input', (e) => {
|
||||
block.condition = e.target.value;
|
||||
this.render();
|
||||
});
|
||||
document.getElementById('sg-btn-add-while-body').addEventListener('click', () => {
|
||||
if (!block.body) block.body = [];
|
||||
block.body.push({ id: 'b_' + Date.now(), type: 'instruction', text: 'schleifen_anweisung()' });
|
||||
this.render();
|
||||
});
|
||||
} else if (block.type === 'for') {
|
||||
document.getElementById('sg-input-for-hdr').addEventListener('input', (e) => {
|
||||
block.loopHeader = e.target.value;
|
||||
this.render();
|
||||
});
|
||||
document.getElementById('sg-btn-add-for-body').addEventListener('click', () => {
|
||||
if (!block.body) block.body = [];
|
||||
block.body.push({ id: 'b_' + Date.now(), type: 'instruction', text: 'schleifen_anweisung()' });
|
||||
this.render();
|
||||
});
|
||||
}
|
||||
|
||||
document.getElementById('sg-btn-delete-block').addEventListener('click', () => {
|
||||
if (confirm('Diesen Block und alle Unterblöcke wirklich löschen?')) {
|
||||
this.deleteBlockRecursive(this.blocks, block.id);
|
||||
this.selectedBlockId = this.blocks[0] ? this.blocks[0].id : null;
|
||||
this.render();
|
||||
this.renderSidebar();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
generatePythonCode() {
|
||||
let py = `# ========================================================\n`;
|
||||
py += `# DIN 66261 Struktogramm -> Python 3 Transpiler\n`;
|
||||
py += `# Generiert vom IT-Praxisportal Diagramm-Studio\n`;
|
||||
py += `# ========================================================\n\n`;
|
||||
py += `def algorithmus_ausfuehren():\n`;
|
||||
|
||||
const transpileList = (list, indent) => {
|
||||
let code = '';
|
||||
if (!list || list.length === 0) {
|
||||
return `${indent}pass\n`;
|
||||
}
|
||||
list.forEach(b => {
|
||||
if (b.type === 'instruction') {
|
||||
code += `${indent}${b.text}\n`;
|
||||
} else if (b.type === 'if') {
|
||||
code += `${indent}if ${b.condition}:\n`;
|
||||
code += transpileList(b.thenBlocks, indent + ' ');
|
||||
if (b.elseBlocks && b.elseBlocks.length > 0) {
|
||||
code += `${indent}else:\n`;
|
||||
code += transpileList(b.elseBlocks, indent + ' ');
|
||||
}
|
||||
} else if (b.type === 'while') {
|
||||
code += `${indent}while ${b.condition}:\n`;
|
||||
code += transpileList(b.body, indent + ' ');
|
||||
} else if (b.type === 'for') {
|
||||
code += `${indent}# ${b.loopHeader}\n`;
|
||||
code += `${indent}for i in range(1, n + 1):\n`;
|
||||
code += transpileList(b.body, indent + ' ');
|
||||
}
|
||||
});
|
||||
return code;
|
||||
};
|
||||
|
||||
py += transpileList(this.blocks, ' ');
|
||||
py += `\n\nif __name__ == "__main__":\n print("Algorithmus gestartet...")\n algorithmus_ausfuehren()\n`;
|
||||
return py;
|
||||
}
|
||||
|
||||
showCodeModal() {
|
||||
const py = this.generatePythonCode();
|
||||
document.getElementById('sg-code-display').innerText = py;
|
||||
document.getElementById('sg-code-modal').style.display = 'flex';
|
||||
}
|
||||
|
||||
exportSvg() {
|
||||
// Generate SVG wrapper around the table
|
||||
const box = document.getElementById('sg-render-box');
|
||||
if (!box) return;
|
||||
const htmlContent = box.innerHTML;
|
||||
const svgString = `
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="800" height="900">
|
||||
<foreignObject width="100%" height="100%">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml">
|
||||
<style>
|
||||
.sg-container { font-family: sans-serif; background: #090d16; color: #f8fafc; padding: 20px; }
|
||||
.sg-block { border: 1px solid #38bdf8; background: #1e293b; padding: 8px 12px; margin: -1px 0 0 0; }
|
||||
.sg-text { font-family: monospace; font-size: 13px; color: #f8fafc; }
|
||||
.sg-if-header { position: relative; background: #0f172a; height: 36px; border: 1px solid #38bdf8; }
|
||||
.sg-if-condition { text-align: center; color: #38bdf8; font-weight: bold; padding-top: 6px; }
|
||||
.sg-if-branches { display: flex; width: 100%; border: 1px solid #38bdf8; border-top: none; }
|
||||
.sg-if-then { flex: 1; border-right: 1px solid #38bdf8; }
|
||||
.sg-if-else { flex: 1; }
|
||||
.sg-while { border: 1px solid #38bdf8; background: #1e293b; }
|
||||
.sg-while-header { background: #0f172a; padding: 6px 12px; border-bottom: 1px solid #38bdf8; color: #38bdf8; font-weight: bold; }
|
||||
.sg-while-body-wrap { display: flex; }
|
||||
.sg-while-gutter { width: 24px; background: #0f172a; border-right: 1px solid #38bdf8; }
|
||||
.sg-while-body { flex: 1; }
|
||||
</style>
|
||||
${htmlContent}
|
||||
</div>
|
||||
</foreignObject>
|
||||
</svg>
|
||||
`;
|
||||
const blob = new Blob([svgString], { type: 'image/svg+xml;charset=utf-8' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = 'struktogramm_din66261.svg';
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
escapeHtml(str) {
|
||||
return (str || '')
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"');
|
||||
}
|
||||
}
|
||||
|
||||
window.StruktogrammEditor = StruktogrammEditor;
|
||||
346
assets/ihk_tools/uml_editor.js
Normal file
346
assets/ihk_tools/uml_editor.js
Normal file
|
|
@ -0,0 +1,346 @@
|
|||
/**
|
||||
* 🔷 IT-PRAXISPORTAL – UML DIAGRAMM STUDIO (KLASSEN, SEQUENZ, USE CASE)
|
||||
* Standard für IHK FIAE & FISI:
|
||||
* - Klassendiagramme (Attribute +, -, #, Methoden, Vererbung, Assoziation, Komposition)
|
||||
* - Sequenzdiagramme (Lebenslinien, synchrone/asynchrone Aufrufe, Return)
|
||||
* - Use Case Diagramme (Akteure, Systemgrenze, <<include>>, <<extend>>)
|
||||
* Synchronisierte Mermaid-Live-Vorschau mit interaktiver Generierung!
|
||||
*/
|
||||
|
||||
class UMLEditor {
|
||||
constructor(containerId, options = {}) {
|
||||
this.container = typeof containerId === 'string' ? document.getElementById(containerId) : containerId;
|
||||
this.options = options;
|
||||
this.diagramType = 'class'; // 'class' | 'sequence' | 'usecase'
|
||||
this.mermaidCode = '';
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
if (!this.container) return;
|
||||
this.container.innerHTML = `
|
||||
<div class="ds-editor-card">
|
||||
<div class="ds-toolbar">
|
||||
<div class="ds-toolbar-left">
|
||||
<div class="ds-select-wrap">
|
||||
<label>Diagrammtyp:</label>
|
||||
<select id="uml-select-type" class="ds-select">
|
||||
<option value="class">UML 2.5 Klassendiagramm</option>
|
||||
<option value="sequence">UML 2.5 Sequenzdiagramm</option>
|
||||
<option value="usecase">UML 2.5 Anwendungsfalldiagramm (Use Case)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="ds-select-wrap">
|
||||
<label>IHK-Vorlage:</label>
|
||||
<select id="uml-select-template" class="ds-select">
|
||||
<option value="">-- Vorlage laden --</option>
|
||||
<option value="class_ecommerce">IHK Klassendiagramm: E-Commerce & Zahlung</option>
|
||||
<option value="class_ticket">IHK Klassendiagramm: IT-Helpdesk & Rollen</option>
|
||||
<option value="seq_login">IHK Sequenz: 2FA Benutzer-Authentifizierung</option>
|
||||
<option value="seq_order">IHK Sequenz: Warenkorb-Bestellprozess</option>
|
||||
<option value="uc_hotel">IHK Use-Case: Hotel-Reservierungssystem</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ds-toolbar-right">
|
||||
<button type="button" class="ds-btn ds-btn-success" id="uml-btn-render">
|
||||
<span>▶</span> <span>Neu Rendern</span>
|
||||
</button>
|
||||
<button type="button" class="ds-btn ds-btn-secondary" id="uml-btn-export-svg">
|
||||
<span>🖼️</span> <span>SVG Export</span>
|
||||
</button>
|
||||
<button type="button" class="ds-btn ds-btn-danger" id="uml-btn-clear" title="Code leeren">
|
||||
<span>🗑️</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ds-workspace-grid">
|
||||
<div class="ds-canvas-wrap" style="display:flex; align-items:center; justify-content:center; overflow:auto; background:#090d16;" id="uml-canvas-container">
|
||||
<div id="uml-render-target" style="width:100%; min-height:100%; display:flex; align-items:center; justify-content:center; padding:24px;"></div>
|
||||
</div>
|
||||
<div class="ds-sidebar" style="width:340px;">
|
||||
<div class="ds-sidebar-header">
|
||||
<h4>UML Quellcode (Mermaid / PlantUML Standard)</h4>
|
||||
</div>
|
||||
<div class="ds-sidebar-body" style="padding:10px; display:flex; flex-direction:column; height:calc(100% - 46px);">
|
||||
<textarea id="uml-code-input" class="ds-textarea" style="flex:1; font-family:monospace; font-size:0.84rem; line-height:1.45; resize:none; background:#060911; color:#f8fafc; border:1px solid #334155; padding:12px; border-radius:6px;"></textarea>
|
||||
<div style="margin-top:10px; font-size:0.75rem; color:#94a3b8; line-height:1.4;">
|
||||
💡 Tipp: Bearbeite den Diagrammcode direkt links in der Textbox oder lade eine IHK-Vorlage. Klicke auf <strong>Neu Rendern</strong> zur Aktualisierung.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
this.bindEvents();
|
||||
this.loadTemplate('class_ecommerce');
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
document.getElementById('uml-select-type').addEventListener('change', (e) => {
|
||||
this.diagramType = e.target.value;
|
||||
if (this.diagramType === 'class') this.loadTemplate('class_ecommerce');
|
||||
else if (this.diagramType === 'sequence') this.loadTemplate('seq_login');
|
||||
else if (this.diagramType === 'usecase') this.loadTemplate('uc_hotel');
|
||||
});
|
||||
|
||||
document.getElementById('uml-select-template').addEventListener('change', (e) => {
|
||||
if (e.target.value) this.loadTemplate(e.target.value);
|
||||
});
|
||||
|
||||
document.getElementById('uml-btn-render').addEventListener('click', () => {
|
||||
this.mermaidCode = document.getElementById('uml-code-input').value;
|
||||
this.render();
|
||||
});
|
||||
|
||||
document.getElementById('uml-btn-clear').addEventListener('click', () => {
|
||||
if (confirm('Möchtest du das aktuelle UML-Diagramm leeren?')) {
|
||||
document.getElementById('uml-code-input').value = 'classDiagram\n class MeineKlasse {\n +int id\n }';
|
||||
this.mermaidCode = document.getElementById('uml-code-input').value;
|
||||
this.render();
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('uml-btn-export-svg').addEventListener('click', () => this.exportSvg());
|
||||
}
|
||||
|
||||
loadTemplate(tpl) {
|
||||
let code = '';
|
||||
if (tpl === 'class_ecommerce') {
|
||||
this.diagramType = 'class';
|
||||
document.getElementById('uml-select-type').value = 'class';
|
||||
code = `classDiagram
|
||||
direction TB
|
||||
|
||||
class Benutzer {
|
||||
<<abstract>>
|
||||
#int id
|
||||
#String email
|
||||
#String passwortHash
|
||||
+anmelden() boolean
|
||||
+abmelden() void
|
||||
}
|
||||
|
||||
class Kunde {
|
||||
-String vorname
|
||||
-String nachname
|
||||
-String lieferadresse
|
||||
+bestellen(Warenkorb w) Bestellung
|
||||
}
|
||||
|
||||
class Administrator {
|
||||
-int rechteStufe
|
||||
+artikelAnlegen(Artikel a) void
|
||||
+sperreBenutzer(int userId) void
|
||||
}
|
||||
|
||||
class Bestellung {
|
||||
-int bestellId
|
||||
-Date datum
|
||||
-String status
|
||||
+berechneGesamtsumme() double
|
||||
}
|
||||
|
||||
class Bestellposition {
|
||||
-int menge
|
||||
-double einzelpreis
|
||||
}
|
||||
|
||||
class Artikel {
|
||||
-int artikelId
|
||||
-String bezeichnung
|
||||
-double preis
|
||||
-int bestand
|
||||
}
|
||||
|
||||
class IPaymentMethod {
|
||||
<<interface>>
|
||||
+processPayment(double amount) boolean
|
||||
}
|
||||
|
||||
Benutzer <|-- Kunde : Generalisierung
|
||||
Benutzer <|-- Administrator : Generalisierung
|
||||
Kunde "1" --> "*" Bestellung : gibt auf
|
||||
Bestellung "1" *-- "1..*" Bestellposition : Komposition
|
||||
Bestellposition "*" --> "1" Artikel : Assoziation
|
||||
Bestellung ..> IPaymentMethod : Realisierung`;
|
||||
} else if (tpl === 'class_ticket') {
|
||||
this.diagramType = 'class';
|
||||
document.getElementById('uml-select-type').value = 'class';
|
||||
code = `classDiagram
|
||||
class Ticket {
|
||||
-int ticketNummer
|
||||
-String titel
|
||||
-String prioritaet
|
||||
-Date erstelltAm
|
||||
+statusAendern(String status) void
|
||||
+zuweisen(Mitarbeiter m) void
|
||||
}
|
||||
|
||||
class Mitarbeiter {
|
||||
-int mitarbeiterId
|
||||
-String name
|
||||
-String abteilung
|
||||
}
|
||||
|
||||
class HardwareAsset {
|
||||
-String seriennummer
|
||||
-String hostname
|
||||
-String standort
|
||||
}
|
||||
|
||||
Ticket "*" --> "1" Mitarbeiter : Melder
|
||||
Ticket "*" --> "0..1" Mitarbeiter : Bearbeiter
|
||||
Ticket "*" --> "1" HardwareAsset : Betrifft`;
|
||||
} else if (tpl === 'seq_login') {
|
||||
this.diagramType = 'sequence';
|
||||
document.getElementById('uml-select-type').value = 'sequence';
|
||||
code = `sequenceDiagram
|
||||
autonumber
|
||||
actor User as Benutzer
|
||||
participant Browser as Client / Browser
|
||||
participant AuthAPI as Auth-Service
|
||||
participant DB as Datenbank
|
||||
participant Mail as 2FA-Mail-Service
|
||||
|
||||
User->>Browser: Login-Formular ausfüllen (Email, Passwort)
|
||||
Browser->>AuthAPI: POST /api/v1/auth/login
|
||||
activate AuthAPI
|
||||
AuthAPI->>DB: SELECT * FROM users WHERE email = ?
|
||||
activate DB
|
||||
DB-->>AuthAPI: User-Record mit Password-Hash
|
||||
deactivate DB
|
||||
AuthAPI->>AuthAPI: Passwort-Hash validieren (Argon2id)
|
||||
|
||||
alt Passwort korrekt
|
||||
AuthAPI->>Mail: 2FA-Einmalcode senden (SMS/Email)
|
||||
AuthAPI-->>Browser: 200 OK (Requires 2FA)
|
||||
User->>Browser: 2FA-Code eingeben
|
||||
Browser->>AuthAPI: POST /api/v1/auth/verify-otp
|
||||
AuthAPI-->>Browser: 200 OK (JWT Access Token & Cookie)
|
||||
else Passwort falsch
|
||||
AuthAPI-->>Browser: 401 Unauthorized (Ungültige Anmeldedaten)
|
||||
end
|
||||
deactivate AuthAPI`;
|
||||
} else if (tpl === 'seq_order') {
|
||||
this.diagramType = 'sequence';
|
||||
document.getElementById('uml-select-type').value = 'sequence';
|
||||
code = `sequenceDiagram
|
||||
autonumber
|
||||
actor Kunde
|
||||
participant Webshop
|
||||
participant Lager as ERP / Lager
|
||||
participant Bank as Payment-Gateway
|
||||
|
||||
Kunde->>Webshop: "Jetzt kaufen" klicken
|
||||
activate Webshop
|
||||
Webshop->>Lager: Bestand prüfen & reservieren
|
||||
activate Lager
|
||||
Lager-->>Webshop: Bestand bestätigt
|
||||
deactivate Lager
|
||||
Webshop->>Bank: Kreditkarte autorisieren (129.90 EUR)
|
||||
activate Bank
|
||||
Bank-->>Webshop: Zahlung genehmigt (Auth-Token)
|
||||
deactivate Bank
|
||||
Webshop->>Lager: Kommissionierauftrag erstellen
|
||||
Webshop-->>Kunde: Bestellbestätigung per E-Mail
|
||||
deactivate Webshop`;
|
||||
} else if (tpl === 'uc_hotel') {
|
||||
this.diagramType = 'usecase';
|
||||
document.getElementById('uml-select-type').value = 'usecase';
|
||||
code = `flowchart LR
|
||||
subgraph Hotelverwaltungssystem [Systemgrenze: Hotel-Reservierung]
|
||||
UC1((Zimmer suchen))
|
||||
UC2((Zimmer reservieren))
|
||||
UC3((Zahlung durchführen))
|
||||
UC4((Reservierung stornieren))
|
||||
UC5((Zimmerrechnung erstellen))
|
||||
end
|
||||
|
||||
Gast[Gast / Kunde] --> UC1
|
||||
Gast --> UC2
|
||||
Gast --> UC4
|
||||
|
||||
Rezeption[Rezeptionist] --> UC2
|
||||
Rezeption --> UC4
|
||||
Rezeption --> UC5
|
||||
|
||||
UC2 -.->|<<include>>| UC3
|
||||
UC4 -.->|<<extend>>| UC5`;
|
||||
}
|
||||
|
||||
document.getElementById('uml-code-input').value = code;
|
||||
this.mermaidCode = code;
|
||||
this.render();
|
||||
}
|
||||
|
||||
async render() {
|
||||
const target = document.getElementById('uml-render-target');
|
||||
if (!target) return;
|
||||
|
||||
// Load mermaid if not already on window
|
||||
if (!window.mermaid) {
|
||||
target.innerHTML = `<div style="color:#94a3b8; font-size:0.9rem;">Lade Diagramm-Renderer...</div>`;
|
||||
await this.loadMermaidScript();
|
||||
}
|
||||
|
||||
try {
|
||||
window.mermaid.initialize({
|
||||
startOnLoad: false,
|
||||
theme: 'dark',
|
||||
themeVariables: {
|
||||
darkMode: true,
|
||||
background: '#090d16',
|
||||
primaryColor: '#1e293b',
|
||||
primaryTextColor: '#f8fafc',
|
||||
primaryBorderColor: '#38bdf8',
|
||||
lineColor: '#38bdf8',
|
||||
secondaryColor: '#0f172a',
|
||||
tertiaryColor: '#1e1b4b'
|
||||
}
|
||||
});
|
||||
|
||||
const id = 'mermaid_svg_' + Date.now();
|
||||
const { svg } = await window.mermaid.render(id, this.mermaidCode);
|
||||
target.innerHTML = svg;
|
||||
} catch (err) {
|
||||
target.innerHTML = `
|
||||
<div style="background:rgba(239,68,68,0.1); border:1px solid #ef4444; color:#fca5a5; padding:14px; border-radius:8px; font-size:0.85rem; max-width:600px;">
|
||||
<strong>⚠️ Syntaxfehler im UML-Code:</strong>
|
||||
<pre style="margin-top:8px; font-family:monospace; font-size:0.8rem; white-space:pre-wrap;">${err.message || err}</pre>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
loadMermaidScript() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const script = document.createElement('script');
|
||||
script.src = 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js';
|
||||
script.onload = () => resolve();
|
||||
script.onerror = () => reject(new Error('Mermaid CDN konnte nicht geladen werden.'));
|
||||
document.head.appendChild(script);
|
||||
});
|
||||
}
|
||||
|
||||
exportSvg() {
|
||||
const svgEl = document.querySelector('#uml-render-target svg');
|
||||
if (!svgEl) {
|
||||
alert('Kein gerendertes Diagramm zum Exportieren vorhanden.');
|
||||
return;
|
||||
}
|
||||
const serializer = new XMLSerializer();
|
||||
const source = serializer.serializeToString(svgEl);
|
||||
const blob = new Blob([source], { type: 'image/svg+xml;charset=utf-8' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `uml_${this.diagramType}_ihk.svg`;
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
}
|
||||
|
||||
window.UMLEditor = UMLEditor;
|
||||
77
assets/ihk_tools/usv_calculator.js
Normal file
77
assets/ihk_tools/usv_calculator.js
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
/**
|
||||
* Interaktiver USV- & Scheinleistungs-Kalkulator für ITSE
|
||||
* IHK ZPA Nord-West AP2
|
||||
*/
|
||||
(function() {
|
||||
function renderUSVCalculator(containerId) {
|
||||
const container = document.getElementById(containerId);
|
||||
if (!container) return;
|
||||
|
||||
container.innerHTML = `
|
||||
<div style="background: #0f172a; border: 1px solid #334155; border-radius: 8px; padding: 20px; color: #f8fafc;">
|
||||
<h4 style="color: #38bdf8; margin-top: 0; margin-bottom: 12px;">⚡ Interaktiver USV-Dimensionierungs-Rechner (ITSE)</h4>
|
||||
<p style="color: #94a3b8; font-size: 0.9rem; margin-bottom: 16px;">
|
||||
Berechnen Sie die Wirkleistung, Scheinleistung und empfohlene USV-Nennleistung nach IHK-Prüfungsformel:
|
||||
</p>
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 14px; margin-bottom: 20px;">
|
||||
<div style="background: #1e293b; padding: 12px; border-radius: 6px;">
|
||||
<label style="font-size: 0.85rem; color: #38bdf8; display: block; font-weight: 600;">Wirkleistung Verbraucher P (W)</label>
|
||||
<input type="number" id="usv_p" value="2640" min="10" step="50" class="ihk-input" style="width: 100%; margin-top: 6px;" />
|
||||
</div>
|
||||
<div style="background: #1e293b; padding: 12px; border-radius: 6px;">
|
||||
<label style="font-size: 0.85rem; color: #fcd34d; display: block; font-weight: 600;">Leistungsfaktor cos φ</label>
|
||||
<input type="number" id="usv_cos" value="0.9" min="0.5" max="1.0" step="0.05" class="ihk-input" style="width: 100%; margin-top: 6px;" />
|
||||
</div>
|
||||
<div style="background: #1e293b; padding: 12px; border-radius: 6px;">
|
||||
<label style="font-size: 0.85rem; color: #4ade80; display: block; font-weight: 600;">Sicherheitsreserve (%)</label>
|
||||
<input type="number" id="usv_reserve" value="25" min="0" max="100" step="5" class="ihk-input" style="width: 100%; margin-top: 6px;" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; background: #1e293b; padding: 16px; border-radius: 6px;">
|
||||
<div>
|
||||
<div style="color: #94a3b8; font-size: 0.8rem;">Wirkleistung (P)</div>
|
||||
<div id="res_usv_p" style="font-size: 1.25rem; font-weight: bold; color: #38bdf8;">-- kW</div>
|
||||
<div style="font-size: 0.75rem; color: #64748b;">Thermische Last</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style="color: #94a3b8; font-size: 0.8rem;">Scheinleistung (S = P / cos φ)</div>
|
||||
<div id="res_usv_s" style="font-size: 1.25rem; font-weight: bold; color: #fcd34d;">-- kVA</div>
|
||||
<div style="font-size: 0.75rem; color: #64748b;">Elektrische Auslegung</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style="color: #94a3b8; font-size: 0.8rem;">Empf. USV-Nennleistung</div>
|
||||
<div id="res_usv_rec" style="font-size: 1.25rem; font-weight: bold; color: #4ade80;">-- kVA</div>
|
||||
<div style="font-size: 0.75rem; color: #64748b;">Inkl. Sicherheitszuschlag</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
function update() {
|
||||
const p = parseFloat(document.getElementById('usv_p').value) || 0;
|
||||
const cos = parseFloat(document.getElementById('usv_cos').value) || 0.9;
|
||||
const reserve = parseFloat(document.getElementById('usv_reserve').value) || 20;
|
||||
|
||||
const s = cos > 0 ? (p / cos) : 0;
|
||||
const rec = s * (1 + reserve / 100);
|
||||
|
||||
document.getElementById('res_usv_p').textContent = (p / 1000).toFixed(2) + ' kW';
|
||||
document.getElementById('res_usv_s').textContent = (s / 1000).toFixed(2) + ' kVA';
|
||||
document.getElementById('res_usv_rec').textContent = (rec / 1000).toFixed(2) + ' kVA';
|
||||
}
|
||||
|
||||
['usv_p', 'usv_cos', 'usv_reserve'].forEach(id => {
|
||||
document.getElementById(id).addEventListener('input', update);
|
||||
});
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
window.renderUSVCalculator = renderUSVCalculator;
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
if (document.getElementById('usv-calculator-app')) {
|
||||
renderUSVCalculator('usv-calculator-app');
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
|
@ -137,7 +137,7 @@
|
|||
color: "#d97706",
|
||||
badgeClass: "badge-itse",
|
||||
description: "Planung, Errichtung und Instandhaltung elektrotechnischer Schutz- und Stromversorgungssysteme sowie optischer Leitungsnetze.",
|
||||
coreCourses: ["ihk_ap1", "bash", "dns_records", "cpp"],
|
||||
coreCourses: ["ihk_ap1", "ihk_ap2_itse", "bash", "dns_records", "cpp"],
|
||||
years: {
|
||||
1: {
|
||||
title: "1. Ausbildungsjahr (Elektrotechnische Grundlagen & Arbeitsplatz)",
|
||||
|
|
@ -152,7 +152,7 @@
|
|||
3: {
|
||||
title: "3. Ausbildungsjahr (VDE-Prüfungen, USV-Anlagen & Messtechnik)",
|
||||
lernfelder: [10, 11, 12],
|
||||
recommendedCourses: ["cpp", "bash"]
|
||||
recommendedCourses: ["ihk_ap2_itse", "cpp", "bash"]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -164,7 +164,7 @@
|
|||
color: "#2563eb",
|
||||
badgeClass: "badge-itsm",
|
||||
description: "Kaufmännische IT-Beratung, Konzeption und Vertrieb von IT-Lösungen, ITIL-Serviceverträge und IT-Projektmanagement.",
|
||||
coreCourses: ["ihk_ap1", "sql", "python", "html_css"],
|
||||
coreCourses: ["ihk_ap1", "ihk_ap2_itsm", "sql", "python", "html_css"],
|
||||
years: {
|
||||
1: {
|
||||
title: "1. Ausbildungsjahr (IT-Märkte, Hard-/Software & Kalkulation)",
|
||||
|
|
@ -179,7 +179,7 @@
|
|||
3: {
|
||||
title: "3. Ausbildungsjahr (SLA-Vertrieb, ITIL-Services & IT-Projekte)",
|
||||
lernfelder: [10, 11, 12],
|
||||
recommendedCourses: ["sql", "html_css"]
|
||||
recommendedCourses: ["ihk_ap2_itsm", "sql", "html_css"]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -191,7 +191,7 @@
|
|||
color: "#0891b2",
|
||||
badgeClass: "badge-kdm",
|
||||
description: "Analyse und Digitalisierung betriebswirtschaftlicher Wertschöpfungsketten, ERP-Systeme, Datenökonomie und Compliance.",
|
||||
coreCourses: ["ihk_ap1", "sql", "python", "git"],
|
||||
coreCourses: ["ihk_ap1", "ihk_ap2_kdm", "sql", "python", "git"],
|
||||
years: {
|
||||
1: {
|
||||
title: "1. Ausbildungsjahr (Geschäftsprozesse & IT-Organisation)",
|
||||
|
|
@ -206,7 +206,7 @@
|
|||
3: {
|
||||
title: "3. Ausbildungsjahr (Plattformökonomie, Digital ROI & Controlling)",
|
||||
lernfelder: [10, 11, 12],
|
||||
recommendedCourses: ["sql", "python"]
|
||||
recommendedCourses: ["ihk_ap2_kdm", "sql", "python"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -312,12 +312,12 @@
|
|||
},
|
||||
relatedCourses: {
|
||||
"FISI": ["active_directory", "powershell", "bash", "ihk_ap2_fisi"],
|
||||
"FIAE": ["html_css", "javascript", "git"],
|
||||
"FIDP": ["python", "sql", "git"],
|
||||
"FIDV": ["bash", "dns_records", "cpp"],
|
||||
"ITSE": ["bash", "cpp"],
|
||||
"ITSM": ["sql", "python"],
|
||||
"KDM": ["sql", "git"]
|
||||
"FIAE": ["ihk_ap2_fiae", "html_css", "javascript", "git"],
|
||||
"FIDP": ["ihk_ap2_fidp", "python", "sql", "git"],
|
||||
"FIDV": ["ihk_ap2_fidv", "bash", "dns_records", "cpp"],
|
||||
"ITSE": ["ihk_ap2_itse", "bash", "cpp"],
|
||||
"ITSM": ["ihk_ap2_itsm", "sql", "python"],
|
||||
"KDM": ["ihk_ap2_kdm", "sql", "git"]
|
||||
}
|
||||
},
|
||||
11: {
|
||||
|
|
@ -337,12 +337,12 @@
|
|||
},
|
||||
relatedCourses: {
|
||||
"FISI": ["dns_records", "active_directory", "ihk_ap2_fisi"],
|
||||
"FIAE": ["java", "csharp", "go", "rust"],
|
||||
"FIDP": ["python", "sql"],
|
||||
"FIDV": ["dns_records", "bash"],
|
||||
"ITSE": ["dns_records"],
|
||||
"ITSM": ["sql"],
|
||||
"KDM": ["sql", "python"]
|
||||
"FIAE": ["ihk_ap2_fiae", "java", "csharp", "go", "rust"],
|
||||
"FIDP": ["ihk_ap2_fidp", "python", "sql"],
|
||||
"FIDV": ["ihk_ap2_fidv", "dns_records", "bash"],
|
||||
"ITSE": ["ihk_ap2_itse", "dns_records"],
|
||||
"ITSM": ["ihk_ap2_itsm", "sql"],
|
||||
"KDM": ["ihk_ap2_kdm", "sql", "python"]
|
||||
}
|
||||
},
|
||||
12: {
|
||||
|
|
@ -362,12 +362,12 @@
|
|||
},
|
||||
relatedCourses: {
|
||||
"FISI": ["ihk_ap2_fisi", "active_directory", "dns_records", "bash"],
|
||||
"FIAE": ["git", "java", "csharp", "python"],
|
||||
"FIDP": ["sql", "python", "git"],
|
||||
"FIDV": ["bash", "dns_records"],
|
||||
"ITSE": ["bash"],
|
||||
"ITSM": ["sql"],
|
||||
"KDM": ["sql"]
|
||||
"FIAE": ["ihk_ap2_fiae", "git", "java", "csharp", "python"],
|
||||
"FIDP": ["ihk_ap2_fidp", "sql", "python", "git"],
|
||||
"FIDV": ["ihk_ap2_fidv", "bash", "dns_records"],
|
||||
"ITSE": ["ihk_ap2_itse", "bash"],
|
||||
"ITSM": ["ihk_ap2_itsm", "sql"],
|
||||
"KDM": ["ihk_ap2_kdm", "sql"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -184,6 +184,101 @@
|
|||
suggestErrorInterpreter(result.rawOutput);
|
||||
}
|
||||
}
|
||||
|
||||
// Strukturierten visuellen Test-Report mit Diff-Inspektor rendern
|
||||
if (result && (result.total > 0 || result.rawOutput)) {
|
||||
renderVisualTestReport(result);
|
||||
}
|
||||
}
|
||||
|
||||
window.jumpToEditorLine = function(line) {
|
||||
if (monacoEditor && line) {
|
||||
monacoEditor.revealLineInCenter(line);
|
||||
monacoEditor.setPosition({ lineNumber: line, column: 1 });
|
||||
monacoEditor.focus();
|
||||
}
|
||||
};
|
||||
|
||||
function renderVisualTestReport(result) {
|
||||
const term = document.getElementById("workspace-terminal-output");
|
||||
if (!term) return;
|
||||
|
||||
const cases = [];
|
||||
const raw = result.rawOutput || "";
|
||||
|
||||
const lineRegex = /(test_\w+)\s+\([^)]+\)\s+\.\.\.\s+(ok|FAIL|ERROR)/g;
|
||||
let match;
|
||||
while ((match = lineRegex.exec(raw)) !== null) {
|
||||
cases.push({
|
||||
name: match[1],
|
||||
passed: match[2] === "ok",
|
||||
status: match[2]
|
||||
});
|
||||
}
|
||||
|
||||
const fileLineMatch = raw.match(/File\s+["'].*?aufgabe\.\w+["'],\s+line\s+(\d+)/i);
|
||||
const errorLine = fileLineMatch ? parseInt(fileLineMatch[1], 10) : null;
|
||||
|
||||
let expectedVal = null;
|
||||
let actualVal = null;
|
||||
const assertEqualMatch = raw.match(/AssertionError:\s*(.*?)\s*!=\s*(.*)/);
|
||||
if (assertEqualMatch) {
|
||||
actualVal = assertEqualMatch[1].trim();
|
||||
expectedVal = assertEqualMatch[2].trim();
|
||||
}
|
||||
|
||||
const reportCard = document.createElement("div");
|
||||
reportCard.style.marginTop = "14px";
|
||||
|
||||
const total = result.total || cases.length || 1;
|
||||
const passed = result.passed !== undefined ? result.passed : (result.success ? total : 0);
|
||||
const isSuccess = result.success;
|
||||
|
||||
reportCard.innerHTML = `
|
||||
<div class="test-summary-badge ${isSuccess ? 'passed' : 'failed'}">
|
||||
<span>${isSuccess ? '🎉 Alle ' + total + ' Tests bestanden!' : '⚠️ ' + (total - passed) + ' von ' + total + ' Test(s) fehlgeschlagen'}</span>
|
||||
<span>${passed} / ${total} Tests OK</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
if (cases.length > 0) {
|
||||
cases.forEach(c => {
|
||||
const card = document.createElement("div");
|
||||
card.className = "test-case-card";
|
||||
const hasErr = !c.passed;
|
||||
|
||||
card.innerHTML = `
|
||||
<div class="test-case-header" onclick="const d = this.nextElementSibling; if(d) d.style.display = d.style.display === 'none' ? 'block' : 'none';">
|
||||
<span style="color: ${c.passed ? '#4ade80' : '#f87171'}; font-weight: 700;">
|
||||
${c.passed ? '✓' : '✕'} ${escapeHtml(c.name)}
|
||||
</span>
|
||||
<span style="color: #94a3b8; font-size: 0.76rem;">${c.passed ? 'Bestanden' : 'Fehlgeschlagen ▼'}</span>
|
||||
</div>
|
||||
<div class="test-case-details" style="display: ${hasErr ? 'block' : 'none'};">
|
||||
${hasErr ? `
|
||||
<div style="color: #fca5a5; margin-bottom: 6px;">
|
||||
<strong>Meldung:</strong> Test fehlgeschlagen.
|
||||
</div>
|
||||
${expectedVal ? `
|
||||
<div class="test-diff-inspector">
|
||||
<div><span style="color: #94a3b8;">Erwartet: </span><span class="test-diff-expected">${escapeHtml(expectedVal)}</span></div>
|
||||
<div><span style="color: #94a3b8;">Erhalten: </span><span class="test-diff-actual">${escapeHtml(actualVal)}</span></div>
|
||||
</div>
|
||||
` : ''}
|
||||
${errorLine ? `
|
||||
<button type="button" class="btn-jump-code" onclick="window.jumpToEditorLine(${errorLine})">
|
||||
📍 Zu Zeile ${errorLine} in deiner Aufgabe springen
|
||||
</button>
|
||||
` : ''}
|
||||
` : '<div style="color: #4ade80;">Dieser Testfall wurde ohne Fehler bestanden.</div>'}
|
||||
</div>
|
||||
`;
|
||||
reportCard.appendChild(card);
|
||||
});
|
||||
}
|
||||
|
||||
term.appendChild(reportCard);
|
||||
term.scrollTop = term.scrollHeight;
|
||||
}
|
||||
|
||||
// 4. Hilfsfunktionen
|
||||
|
|
|
|||
1056
assets/style.css
1056
assets/style.css
File diff suppressed because it is too large
Load diff
|
|
@ -6,148 +6,167 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Bash 01: Terminal-Navigation & Globbing</title>
|
||||
<link rel="stylesheet" href="../../../assets/style.css">
|
||||
<style>
|
||||
.module-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 7fr 5fr;
|
||||
gap: 24px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
@media (max-width: 992px) {
|
||||
.module-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-layout">
|
||||
<div class="app-main">
|
||||
<header>
|
||||
<div class="header-container">
|
||||
<div class="header-badge">🐧 Linux Bash & Cloud DevOps</div>
|
||||
<div style="display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 8px;">
|
||||
<span class="badge badge-neutral">🐧 Linux Bash & Cloud DevOps</span>
|
||||
<span class="badge badge-neutral">Modul 01 von 16</span>
|
||||
<span class="badge badge-neutral">⏱️ ca. 15 Min.</span>
|
||||
<span class="badge badge-success">🎓 IHK AP1 & AP2 Relevanz</span>
|
||||
</div>
|
||||
<h1>Bash 01: Terminal-Navigation, Dateioperationen & Globbing</h1>
|
||||
<p>Beherrsche den Linux-Dateibaum, lerne blitzschnelle Pfad-Navigation und automatisiere Dateioperationen mit Wildcards und Brace Expansion.</p>
|
||||
<p class="content-readable" style="color: #cbd5e1; font-size: 1.05rem;">Beherrsche den Linux-Dateibaum, lerne blitzschnelle Pfad-Navigation und automatisiere Dateioperationen mit Wildcards und Brace Expansion.</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="container">
|
||||
<!-- 1. THEORIE: PFADE & NAVIGATION -->
|
||||
<section class="card">
|
||||
<h2>🧭 1. Der Unix-Dateibaum & Pfad-Navigation</h2>
|
||||
<p>In Unix gibt es keine Laufwerksbuchstaben (wie <code>C:</code> oder <code>D:</code>). Alles entspringt dem Root-Verzeichnis <code>/</code> (Single Root Tree). Wir unterscheiden strikt zwischen absoluten und relativen Pfaden:</p>
|
||||
|
||||
<pre><code># Wo bin ich?
|
||||
<div class="module-grid">
|
||||
<!-- LINKE SPALTE: Konzept & Aufgaben -->
|
||||
<div class="module-main-col">
|
||||
<!-- 1. KERNKONZEPT -->
|
||||
<section class="card">
|
||||
<h2>🧭 1. Der Unix-Dateibaum & Pfad-Navigation</h2>
|
||||
<p class="content-readable">In Unix gibt es keine Laufwerksbuchstaben (wie <code>C:</code>). Alles entspringt dem Root-Verzeichnis <code>/</code> (Single Root Tree). Wir unterscheiden strikt zwischen absoluten Pfaden (beginnen immer mit <code>/</code>) und relativen Pfaden (relativ zum aktuellen Arbeitsverzeichnis <code>pwd</code>).</p>
|
||||
|
||||
<div class="code-block">
|
||||
<div class="code-block-header">
|
||||
<span class="code-block-title">bash • Navigation & Pfade</span>
|
||||
<button type="button" class="code-copy-btn" onclick="navigator.clipboard.writeText(this.parentElement.nextElementSibling.innerText); this.innerText='Kopiert! ✓'; setTimeout(()=>this.innerText='Kopieren', 2000);">Kopieren</button>
|
||||
</div>
|
||||
<pre style="padding: 12px; margin: 0; background: transparent; color: #f8fafc; font-family: var(--font-mono); font-size: 0.88rem;"><code># Wo bin ich?
|
||||
pwd # Ausgabe: /home/devops/projects
|
||||
|
||||
# Relative vs. Absolute Navigation:
|
||||
cd /var/log/nginx # Absoluter Pfad: Beginnt immer mit '/'
|
||||
cd /var/log/nginx # Absoluter Pfad
|
||||
cd ../../etc # Relativer Pfad: Zwei Ebenen hoch, dann in 'etc'
|
||||
cd ~ # Wechselt ins Home-Verzeichnis (/home/user)
|
||||
cd - # Wechselt zum vorherigen Verzeichnis zurück
|
||||
cd ~ # Home-Verzeichnis
|
||||
cd - # Vorheriges Verzeichnis</code></pre>
|
||||
</div>
|
||||
|
||||
# Detailliertes Listing mit Berechtigungen und Dateigrößen:
|
||||
ls -lah # -l: Long Format, -a: Versteckte Dateien, -h: Human-readable (KB/MB)</code></pre>
|
||||
</section>
|
||||
<div class="box box-analogie">
|
||||
<div class="box-title">🏢 Didaktische Analogie: Das Hochregallager des Betriebssystems</div>
|
||||
<p><strong>Der Dateibaum:</strong> Stell dir das Linux-Dateisystem wie ein gigantisches Logistikzentrum vor. <code>/</code> ist das Haupttor. Mit <code>mkdir -p</code> stellst du mit einer einzigen Anweisung eine ganze Regalreihe mit Fächern auf.</p>
|
||||
<p><strong>Globbing (<code>*</code> und <code>{a,b}</code>):</strong> Statt jedes Paket einzeln zu scannen, hältst du eine Schablone hoch: <code>*.yaml</code> greift automatisch alle Konfigurationen, während <code>{dev,prod}</code> im Handumdrehen zwei Umgebungen parallel anlegt!</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 2. THEORIE: DATEIOPERATIONEN & GLOBBING -->
|
||||
<section class="card">
|
||||
<h2>📁 2. Dateisystem-Operationen & Mächtiges Globbing</h2>
|
||||
<p>Als DevOps-Ingenieur erstellst, kopierst und reorganisierst du Konfigurationsdateien im Sekundentakt. Die Shell bietet hierfür mächtige Werkzeuge:</p>
|
||||
<!-- 2. SUBGOAL-BOARD -->
|
||||
<section class="card">
|
||||
<h2>🎯 2. Dein Arbeitsauftrag: 4 Subgoals (Teilziele)</h2>
|
||||
<p class="content-readable" style="color: var(--text-muted); margin-bottom: 16px;">Bearbeite die Aufgaben in <code>aufgabe.sh</code> schrittweise:</p>
|
||||
|
||||
<pre><code># 🎯 1. Verzeichnisse verschachtelt anlegen:
|
||||
mkdir -p app/{config,src/{models,views},logs,tests}
|
||||
<div class="subgoal-item">
|
||||
<span class="subgoal-badge">Subgoal 1</span>
|
||||
<div>
|
||||
<strong>Projektverzeichnisse anlegen:</strong>
|
||||
<p style="margin: 4px 0 0 0; font-size: 0.88rem; color: var(--text-secondary);">Implementiere <code>erstelle_projektstruktur(basis_dir)</code> mit <code>mkdir -p</code> für <code>src/components</code>, <code>src/utils</code>, <code>docs/api</code> und <code>backup/logs</code>.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
# 🎯 2. Dateien mit Brace Expansion massenweise erstellen:
|
||||
touch app/src/models/{user,order,payment}.py app/config/{dev,staging,prod}.yaml
|
||||
<div class="subgoal-item">
|
||||
<span class="subgoal-badge">Subgoal 2</span>
|
||||
<div>
|
||||
<strong>Testdateien erzeugen:</strong>
|
||||
<p style="margin: 4px 0 0 0; font-size: 0.88rem; color: var(--text-secondary);">Erstelle mit <code>touch</code> und Brace Expansion Beispieldateien in den angelegten Ordnern.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
# 🎯 3. Shell-Globbing (Wildcards):
|
||||
cp app/config/*.yaml /backup/configs/ # Alle .yaml Dateien kopieren
|
||||
mv app/logs/app_2026-??.log /archive/logs/ # Matcht genau 2 Zeichen für den Monat (z.B. 01..12)
|
||||
rm -rf /tmp/build_cache/ # Rekursives Löschen ohne Bestätigung</code></pre>
|
||||
<div class="subgoal-item">
|
||||
<span class="subgoal-badge">Subgoal 3</span>
|
||||
<div>
|
||||
<strong>Dateien kopieren & Globbing:</strong>
|
||||
<p style="margin: 4px 0 0 0; font-size: 0.88rem; color: var(--text-secondary);">Kopiere mit <code>cp</code> und Wildcards (<code>*.yaml</code>, <code>*.log</code>) alle Konfigurationsdateien in das Backup-Verzeichnis.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="box box-analogie">
|
||||
<div class="box-title">🏢 Didaktische Analogie: Das Logistikzentrum des Betriebssystems</div>
|
||||
<p><strong>Der Dateibaum:</strong> Stell dir das Dateisystem wie ein riesiges, strukturiertes Hochregallager vor. <code>/</code> ist der Haupteingang. <code>mkdir -p</code> baut dir sofort eine ganze Gasse mit Fachböden auf.</p>
|
||||
<p><strong>Globbing (<code>*</code> und <code>{a,b}</code>):</strong> Anstatt jedes Paket einzeln mit der Hand zu scannen, hältst du eine Schablone hoch: <code>*.yaml</code> greift automatisch alle blauen Kisten aus dem Regal, während <code>{dev,prod}</code> mit einem Handgriff zwei vorgefertigte Etiketten druckt!</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 3. CHEAT-SHEET -->
|
||||
<section class="card">
|
||||
<h2>📑 3. Cheat-Sheet: Navigation & Wildcards</h2>
|
||||
<div style="overflow-x: auto;">
|
||||
<table style="width: 100%; border-collapse: collapse; font-size: 0.9rem; text-align: left;">
|
||||
<thead>
|
||||
<tr style="border-bottom: 2px solid var(--border-color); background: var(--bg-subtle);">
|
||||
<th style="padding: 10px;">Befehl / Muster</th>
|
||||
<th style="padding: 10px;">Beispiel</th>
|
||||
<th style="padding: 10px;">Bedeutung & DevOps-Einsatz</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr style="border-bottom: 1px solid var(--border-color);">
|
||||
<td style="padding: 10px;"><code>mkdir -p <pfad></code></td>
|
||||
<td style="padding: 10px;"><code>mkdir -p /srv/app/releases</code></td>
|
||||
<td style="padding: 10px;">Erstellt Elternverzeichnisse automatisch ohne Fehler</td>
|
||||
</tr>
|
||||
<tr style="border-bottom: 1px solid var(--border-color);">
|
||||
<td style="padding: 10px;"><code>cp -r <src> <dst></code></td>
|
||||
<td style="padding: 10px;"><code>cp -r ./templates /etc/nginx/</code></td>
|
||||
<td style="padding: 10px;">Kopiert ganze Verzeichnisbäume rekursiv</td>
|
||||
</tr>
|
||||
<tr style="border-bottom: 1px solid var(--border-color);">
|
||||
<td style="padding: 10px;"><code>mv <src> <dst></code></td>
|
||||
<td style="padding: 10px;"><code>mv old.env .env.backup</code></td>
|
||||
<td style="padding: 10px;">Verschiebt oder benennt Dateien atomar um</td>
|
||||
</tr>
|
||||
<tr style="border-bottom: 1px solid var(--border-color);">
|
||||
<td style="padding: 10px;"><code>rm -rf <pfad></code></td>
|
||||
<td style="padding: 10px;"><code>rm -rf ./temp_workspace</code></td>
|
||||
<td style="padding: 10px;">Löscht rekursiv und erzwingt Löschen ohne Prompt</td>
|
||||
</tr>
|
||||
<tr style="border-bottom: 1px solid var(--border-color);">
|
||||
<td style="padding: 10px;"><code>*</code> (Wildcard)</td>
|
||||
<td style="padding: 10px;"><code>rm -f *.log</code></td>
|
||||
<td style="padding: 10px;">Matcht 0 oder beliebig viele Zeichen</td>
|
||||
</tr>
|
||||
<tr style="border-bottom: 1px solid var(--border-color);">
|
||||
<td style="padding: 10px;"><code>?</code> (Single Char)</td>
|
||||
<td style="padding: 10px;"><code>ls node_v?.log</code></td>
|
||||
<td style="padding: 10px;">Matcht exakt ein einziges Zeichen</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 10px;"><code>{a,b,c}</code></td>
|
||||
<td style="padding: 10px;"><code>touch {app,db}_{1..3}.conf</code></td>
|
||||
<td style="padding: 10px;">Brace Expansion zur raschen Permutationserstellung</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 4. AUFGABEN CHECKLISTE -->
|
||||
<section class="card">
|
||||
<h2>🎯 Aufgaben & Checkliste in <code>aufgabe.sh</code></h2>
|
||||
<div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t1">
|
||||
<label class="task-label" for="t1"><strong>TODO 1:</strong> <code>erstelle_projektstruktur(basis_dir)</code> mit <code>mkdir -p</code> implementieren (src/components, src/utils, docs/api, backup/logs).</label>
|
||||
</div>
|
||||
<div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t2">
|
||||
<label class="task-label" for="t2"><strong>TODO 2:</strong> <code>erstelle_testdateien(basis_dir)</code> mit <code>touch</code> und Dateimustern umsetzen.</label>
|
||||
</div>
|
||||
<div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t3">
|
||||
<label class="task-label" for="t3"><strong>TODO 3:</strong> <code>kopiere_konfigurationen(src_dir, backup_dir)</code> mit <code>cp</code> und Globbing (<code>*.yaml</code>, <code>*.log</code>) schreiben.</label>
|
||||
</div>
|
||||
<div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t4">
|
||||
<label class="task-label" for="t4"><strong>TODO 4:</strong> <code>verschiebe_dateien(src_dir, target_dir)</code> für <code>*.md</code> Dokumentationen implementieren.</label>
|
||||
</div>
|
||||
<div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t5">
|
||||
<label class="task-label" for="t5"><strong>TODO 5:</strong> <code>bereinige_temporaere_dateien(basis_dir)</code> mit <code>rm -f</code> und <code>rm -rf</code> fertigstellen.</label>
|
||||
<div class="subgoal-item">
|
||||
<span class="subgoal-badge">Subgoal 4</span>
|
||||
<div>
|
||||
<strong>Temporäre Dateien bereinigen:</strong>
|
||||
<p style="margin: 4px 0 0 0; font-size: 0.88rem; color: var(--text-secondary);">Nutze <code>rm -f</code> und <code>rm -rf</code>, um Cache- und temporäre Logdateien sauber zu entfernen.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="box box-tipp" style="margin-top: 20px;">
|
||||
<div class="box-title">💡 DevOps Pro-Tipp: Quoting in Shell-Skripten</div>
|
||||
<p>Setze Variablen bei Pfadangaben in Bash-Skripten stets in doppelte Anführungszeichen: <code>"$basis_dir/src/components"</code>. Das verhindert gefürchtete <em>Word Splitting</em> Fehler, falls ein Verzeichnisname einmal Leerzeichen enthält!</p>
|
||||
</div>
|
||||
<!-- RECHTE SPALTE: Referenz, Gotchas & Spickzettel -->
|
||||
<div class="module-side-col">
|
||||
<!-- GOTCHAS (STOLPERFALLEN) -->
|
||||
<div class="box-gotcha">
|
||||
<h4>⚠️ Häufige Stolperfallen (Pre-Mortem)</h4>
|
||||
<p style="margin-bottom: 8px;"><strong>1. Fehlendes <code>-p</code> bei mkdir:</strong> Wenn du <code>mkdir a/b/c</code> aufrufst und Ordner <code>a</code> noch nicht existiert, bricht die Shell mit <em>No such file or directory</em> ab. Verwende immer <code>mkdir -p</code>!</p>
|
||||
<p style="margin: 0;"><strong>2. Word Splitting ohne Quotes:</strong> Setze Variablenpfade in Skripten stets in doppelte Anführungszeichen: <code>"$basis_dir/src"</code>, sonst führen Leerzeichen im Ordnernamen zu fatalen Syntaxfehlern.</p>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 25px; display: flex; gap: 12px; flex-wrap: wrap;">
|
||||
<a href="../../../workspace.html?course=bash&track=track_1_terminal_grundlagen&chapter=01_navigation_dateien_und_globbing" class="btn" style="background: #10b981; color: #fff; font-weight: 700;">💻 In Web-IDE öffnen →</a>
|
||||
<a href="../02_streams_pipes_und_redirection/index.html" class="btn btn-secondary">Weiter zu Bash 02 →</a>
|
||||
<!-- SYNTAX QUICK REF -->
|
||||
<div class="box-syntax">
|
||||
<h4>📌 Syntax-Spickzettel: Shell-Muster</h4>
|
||||
<table style="width: 100%; border-collapse: collapse; font-size: 0.84rem; text-align: left;">
|
||||
<thead>
|
||||
<tr style="border-bottom: 2px solid var(--border-color); background: var(--bg-subtle);">
|
||||
<th style="padding: 6px 8px;">Muster</th>
|
||||
<th style="padding: 6px 8px;">Bedeutung</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr style="border-bottom: 1px solid var(--border-color);">
|
||||
<td style="padding: 6px 8px;"><code>*</code></td>
|
||||
<td style="padding: 6px 8px;">Matcht 0 bis unendlich viele Zeichen</td>
|
||||
</tr>
|
||||
<tr style="border-bottom: 1px solid var(--border-color);">
|
||||
<td style="padding: 6px 8px;"><code>?</code></td>
|
||||
<td style="padding: 6px 8px;">Matcht exakt 1 Zeichen</td>
|
||||
</tr>
|
||||
<tr style="border-bottom: 1px solid var(--border-color);">
|
||||
<td style="padding: 6px 8px;"><code>{a,b}</code></td>
|
||||
<td style="padding: 6px 8px;">Brace Expansion (Permutation)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 6px 8px;"><code>mkdir -p</code></td>
|
||||
<td style="padding: 6px 8px;">Erstellt Elternordner automatisch</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- IHK PRÜFUNGSRELEVANZ -->
|
||||
<div class="card" style="background: rgba(37, 99, 235, 0.05); border: 1px solid rgba(37, 99, 235, 0.2);">
|
||||
<h4 style="margin: 0 0 6px 0; color: var(--primary);">💡 IHK-Prüfungstipp (AP1)</h4>
|
||||
<p style="font-size: 0.86rem; color: var(--text-secondary); margin: 0;">In der IHK-Abschlussprüfung Teil 1 wird regelmäßig der Unterschied zwischen relativer und absoluter Pfadangabe sowie die Bedeutung von <code>cd ..</code> und <code>ls -l</code> abgefragt.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- PERSISTENT STICKY LAUNCH ACTION BAR -->
|
||||
<div class="sticky-launch-bar">
|
||||
<div style="display: flex; align-items: center; gap: 12px; color: #f8fafc; font-size: 0.9rem;">
|
||||
<span>🎯 <strong>Status:</strong> Bereit für die Praxis (4 Teilziele definiert)</span>
|
||||
</div>
|
||||
<div style="display: flex; align-items: center; gap: 12px;">
|
||||
<a href="../../../dashboard.html" class="btn btn-ghost" style="color: #cbd5e1 !important;">← Dashboard</a>
|
||||
<a href="../../../workspace.html?course=bash&track=track_1_terminal_grundlagen&chapter=01_navigation_dateien_und_globbing" class="btn-cta-launch">
|
||||
💻 In Web-IDE öffnen & Teilziel 1 anpacken →
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="../../../assets/courses_manifest.js"></script>
|
||||
|
|
|
|||
|
|
@ -1,18 +1,64 @@
|
|||
# DNS 01: DNS-Hierarchie & Root-Server
|
||||
# DNS 01: DNS-Hierarchie, FQDN-Struktur & Root-Server
|
||||
|
||||
Willkommen zum Kurs **DNS-Records, Domain Name System & E-Mail-Sicherheit** im IT-Praxisportal!
|
||||
Willkommen zu **Modul 01** des Kurses **DNS-Records, Domain Name System & E-Mail-Sicherheit**!
|
||||
|
||||
## 💡 Das Wichtigste in Kürze
|
||||
- **Ziel:** Verstehe den globalen DNS-Baum: Root-Zone (.), TLDs (gTLD/ccTLD), SLDs, FQDNs und autoritative vs. rekursive Nameserver.
|
||||
- **Wichtigstes Tool:** `dig`
|
||||
In diesem Modul lernst du die fundamentale Architektur des globalen Domain Name Systems kennen: Wie der umgekehrte DNS-Baum aufgebaut ist, wie Fully Qualified Domain Names (FQDN) strukturiert sind, welche Rolle die 13 weltweiten Root-Server-Cluster spielen und wie Delegationspfade funktionieren.
|
||||
|
||||
## 🎯 Deine Aufgaben (Checkliste)
|
||||
1. **Teilziel 1:** FQDN in Hostname, SLD und TLD zerlegen
|
||||
2. **Teilziel 2:** Root-Server-Cluster (A-M) und Anycast-Routing erklären
|
||||
3. **Teilziel 3:** Unterschied autoritativer vs. rekursiver Nameserver definieren
|
||||
4. **Teilziel 4:** DNS-Baumpfad für www.it-praxisportal.de konstruieren
|
||||
---
|
||||
|
||||
## 💡 1. Das Wichtigste in Kürze
|
||||
|
||||
### Die DNS-Baumstruktur (Inverted Tree)
|
||||
Das DNS ist von oben nach unten organisiert:
|
||||
- **Root Zone (`.`):** Die oberste Wurzel aller Domains.
|
||||
- **Top-Level Domains (TLD):**
|
||||
- *ccTLD (Country-Code):* `.de`, `.ch`, `.at`, `.fr`, `.uk`
|
||||
- *gTLD (Generic):* `.com`, `.org`, `.net`, `.info`
|
||||
- *new gTLD:* `.cloud`, `.dev`, `.app`, `.tech`
|
||||
- **Second-Level Domains (SLD):** Der registrierte Name der Organisation oder Person (z.B. `it-praxisportal` in `it-praxisportal.de`).
|
||||
- **Subdomains / Third-Level Domains:** Beliebig tiefe Unterteilungen (z.B. `intern`, `api`, `staging`).
|
||||
- **Hostname:** Der Name des konkreten Dienstes oder Host-Servers (`srv01`, `www`, `mail`).
|
||||
|
||||
```
|
||||
. (Root)
|
||||
/ \
|
||||
de. com. (TLD)
|
||||
/ \
|
||||
it-praxisportal.de. google.com. (SLD)
|
||||
/
|
||||
intern. (Subdomain)
|
||||
/
|
||||
srv01. (Hostname)
|
||||
```
|
||||
|
||||
### Der Fully Qualified Domain Name (FQDN)
|
||||
Ein vollständiger Domänenname enthält alle Ebenen von links nach rechts bis zum abschließenden Punkt der Root-Zone:
|
||||
`srv01.intern.it-praxisportal.de.`
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **Der abschließende Punkt (`.`):** In DNS-Zonendateien (BIND) ist der Schlusspunkt entscheidend!
|
||||
> - `www.example.de.` (mit Punkt) = Absoluter FQDN.
|
||||
> - `www` (ohne Punkt) = Relativer Bezeichner, an den BIND automatisch den Zonennamen anhängt (`$ORIGIN`).
|
||||
> Ein vergessener Schlusspunkt bei Zielen führt zu Fehlern wie `mail.example.de.example.de.`!
|
||||
|
||||
### Die 13 Root-Server-Cluster
|
||||
Es gibt genau 13 logische IP-Adress-Cluster (A bis M, z.B. `k.root-servers.net`).
|
||||
Dank **BGP Anycast** verbergen sich hinter diesen 13 IP-Adressen mehr als 1.500 physische Serverknoten weltweit. Dadurch sind Root-Server extrem ausfallsicher gegen DDoS-Angriffe und Serverausfälle geschützt.
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Deine Aufgaben in `aufgabe.sh`
|
||||
|
||||
1. **TODO 1 (`parse_fqdn`):** Zerlege einen übergebenen FQDN in `HOST`, `DOMAIN`, `SLD`, `TLD` und prüfe, ob der abschließende Root-Punkt gesetzt ist (`HAS_ROOT_DOT: YES/NO`).
|
||||
2. **TODO 2 (`list_root_server`):** Ermittle den zuständigen Betreiber und FQDN für die Root-Cluster A bis M oder gib alle 13 Cluster aus.
|
||||
3. **TODO 3 (`build_delegation_path`):** Konstruiere den schrittweisen Delegationspfad von der Root (`.`) über TLD und SLD bis zum FQDN.
|
||||
4. **TODO 4 (`validate_fqdn_syntax`):** Validiere Domain-Namen nach den RFC-1035-Regeln (Längenlimits, keine unzulässigen Bindestriche).
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Tests ausführen
|
||||
|
||||
Führe im Terminal folgenden Befehl aus:
|
||||
```bash
|
||||
bash test_aufgabe.sh
|
||||
```
|
||||
|
|
|
|||
|
|
@ -1,21 +1,129 @@
|
|||
#!/usr/bin/env bash
|
||||
# ==============================================================================
|
||||
# 🌐 DNS Grundlagen - DNS 01: DNS-Hierarchie & Root-Server
|
||||
# 🌐 DNS 01: DNS-HIERARCHIE, FQDN-STRUKTUR & ROOT-SERVER
|
||||
# ==============================================================================
|
||||
#
|
||||
# DAS DOMAIN NAME SYSTEM (DNS):
|
||||
# Das DNS ist eine weltweit verteilte, hierarchische Datenbank, die menschenlesbare
|
||||
# Hostnamen (z.B. www.it-praxisportal.de) in maschinenlesbare IP-Adressen auflöst.
|
||||
#
|
||||
# DER INVERTIERTE BAUM:
|
||||
# Die Wurzel des gesamten DNS-Baums ist der unbenannte Root-Knoten, symbolisiert
|
||||
# durch den abschließenden Punkt "." (Root Zone).
|
||||
# Darunter folgen:
|
||||
# 1. Top-Level Domains (TLD):
|
||||
# - Generic TLDs (gTLD): .com, .org, .net, .cloud, .app
|
||||
# - Country-Code TLDs (ccTLD): .de, .ch, .at, .uk, .fr
|
||||
# 2. Second-Level Domains (SLD): Der eigentliche Registrierungsname (z.B. it-praxisportal)
|
||||
# 3. Subdomains: Beliebige Verästelungen (z.B. intern, staging, api)
|
||||
# 4. Hostname: Der eigentliche Rechner- oder Dienstname (z.B. srv01, www, mail)
|
||||
#
|
||||
# EIN VOLLQUALIFIZIERTER DOMAIN-NAME (FQDN):
|
||||
# Enthält alle Ebenen von links nach rechts bis zum abschließenden Wurzelpunkt:
|
||||
# srv01.intern.it-praxisportal.de.
|
||||
# [Host] [Sub] [---- SLD ----] [TLD][Root]
|
||||
# ==============================================================================
|
||||
|
||||
# 🎯 TEILZIEL 1: FQDN in Hostname, SLD und TLD zerlegen
|
||||
# 🎯 TEILZIEL 2: Root-Server-Cluster (A-M) und Anycast-Routing erklären
|
||||
# 🎯 TEILZIEL 3: Unterschied autoritativer vs. rekursiver Nameserver definieren
|
||||
# 🎯 TEILZIEL 4: DNS-Baumpfad für www.it-praxisportal.de konstruieren
|
||||
# 🎯 TEILZIEL 1 (TODO 1): FQDN strukturiert zerlegen
|
||||
# Funktion: parse_fqdn "$fqdn"
|
||||
# Parameter $1: Ein FQDN-String, z.B. "srv01.intern.example.de." oder "host.firma.com"
|
||||
# Anforderungen:
|
||||
# - Prüfe, ob der FQDN mit einem Punkt "." endet (HAS_ROOT_DOT: YES oder NO).
|
||||
# - Entferne für die weitere Zerlegung den optionalen Schlusspunkt.
|
||||
# - Extrahiere:
|
||||
# * HOST: das erste Label ganz links (z.B. "srv01")
|
||||
# * SLD: das Label unmittelbar vor der TLD (z.B. "example" bei srv01.intern.example.de)
|
||||
# * TLD: das letzte Label ganz rechts (z.B. "de")
|
||||
# * DOMAIN: der FQDN ohne den vordersten Hostnamen (z.B. "intern.example.de")
|
||||
# - Gib die Ergebnisse exakt in diesem Format zeilenweise aus:
|
||||
# HOST=<host>
|
||||
# DOMAIN=<domain>
|
||||
# SLD=<sld>
|
||||
# TLD=<tld>
|
||||
# HAS_ROOT_DOT=<YES|NO>
|
||||
parse_fqdn() {
|
||||
local fqdn="$1"
|
||||
# TODO 1: Implementiere die FQDN-Zerlegung
|
||||
return 0
|
||||
}
|
||||
|
||||
analyze_dns_hierarchy() {
|
||||
local domain="${1:-it-praxisportal.de}"
|
||||
echo "▶ Analysiere DNS-Konfiguration für $domain..."
|
||||
# 🎯 TEILZIEL 2 (TODO 2): Root-Server-Cluster abfragen
|
||||
# Funktion: list_root_server "$cluster_letter"
|
||||
# Parameter $1: Ein Buchstabe von "a" bis "m" (case-insensitive) oder "all"
|
||||
# Hintergrund:
|
||||
# Es gibt genau 13 logische Root-Server-Cluster (A bis M), betrieben von diversen
|
||||
# Institutionen (Verisign, NASA, RIPE NCC, ISC, etc.). Sie verteilen per BGP-Anycast
|
||||
# Tausende Serverinstanzen weltweit.
|
||||
#
|
||||
# Zuordnungen (Buchstabe -> Betreiber | FQDN):
|
||||
# A: Verisign, Inc. | a.root-servers.net
|
||||
# B: University of Southern California | b.root-servers.net
|
||||
# C: Cogent Communications | c.root-servers.net
|
||||
# D: University of Maryland | d.root-servers.net
|
||||
# E: NASA Ames Research Center | e.root-servers.net
|
||||
# F: Internet Systems Consortium (ISC) | f.root-servers.net
|
||||
# G: US Department of Defense | g.root-servers.net
|
||||
# H: US Army Research Lab | h.root-servers.net
|
||||
# I: Netnod | i.root-servers.net
|
||||
# J: Verisign, Inc. | j.root-servers.net
|
||||
# K: RIPE NCC | k.root-servers.net
|
||||
# L: ICANN | l.root-servers.net
|
||||
# M: WIDE Project | m.root-servers.net
|
||||
#
|
||||
# Ausgabe-Format bei einzelnem Buchstaben (z.B. "k"):
|
||||
# CLUSTER=K | OPERATOR=RIPE NCC | HOST=k.root-servers.net
|
||||
# Bei "all" gib alle 13 Zeilen von A bis M aus.
|
||||
# Bei ungültigem Buchstaben gib "ERROR: Ungültiger Cluster" zurück und return 1.
|
||||
list_root_server() {
|
||||
local cluster="${1:-all}"
|
||||
# TODO 2: Implementiere die Cluster-Auskunft
|
||||
return 0
|
||||
}
|
||||
|
||||
# TODO: Implementiere die geforderten DNS-Prüfungen oder Record-Erstellungen
|
||||
return 1
|
||||
# 🎯 TEILZIEL 3 (TODO 3): Hierarchischen Delegationspfad konstruieren
|
||||
# Funktion: build_delegation_path "$domain"
|
||||
# Parameter $1: Eine Domain oder FQDN (z.B. "web01.cloud.firma.de")
|
||||
# Anforderungen:
|
||||
# - Konstruiere den schrittweisen Weg von der Root-Zone "." bis zum Ziel.
|
||||
# - Formatiere die Ausgabe als übersichtlichen Pfad:
|
||||
# [ROOT] . -> [TLD] <tld>. -> [SLD] <sld>.<tld>. -> [FQDN] <vollstaendige_domain>.
|
||||
# Beispiel für "shop.kunde.de":
|
||||
# [ROOT] . -> [TLD] de. -> [SLD] kunde.de. -> [FQDN] shop.kunde.de.
|
||||
# Hinweis: Ergänze bei Bedarf den Schlusspunkt, damit jeder Knotenpunkt FQDN-konform ist.
|
||||
build_delegation_path() {
|
||||
local domain="$1"
|
||||
# TODO 3: Konstruiere den Delegationspfad
|
||||
return 0
|
||||
}
|
||||
|
||||
# 🎯 TEILZIEL 4 (TODO 4): FQDN-Syntax nach RFC 1035 validieren
|
||||
# Funktion: validate_fqdn_syntax "$fqdn"
|
||||
# Parameter $1: Ein zu prüfender Domain-Name
|
||||
# Regeln nach RFC 1035:
|
||||
# 1. Gesamtlänge darf maximal 253 Zeichen betragen (ohne Schlusspunkt).
|
||||
# 2. Kein Label darf länger als 63 Zeichen sein.
|
||||
# 3. Erlaubte Zeichen in Labels: Buchstaben (a-z, A-Z), Ziffern (0-9) und Bindestrich (-).
|
||||
# 4. Ein Bindestrich darf NIEMALS am Anfang oder Ende eines Labels stehen.
|
||||
# 5. Keine leeren Labels (z.B. "firma..de" ist ungültig).
|
||||
# Rückgabe:
|
||||
# - Wenn valide: Gib "VALID: <fqdn>" aus und return 0
|
||||
# - Wenn invalide: Gib "INVALID: <grund>" aus und return 1
|
||||
validate_fqdn_syntax() {
|
||||
local fqdn="$1"
|
||||
# TODO 4: Validiere die Syntax nach RFC 1035
|
||||
return 0
|
||||
}
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
analyze_dns_hierarchy "$@"
|
||||
echo "=== Test Teilziel 1 ==="
|
||||
parse_fqdn "srv01.intern.example.de."
|
||||
echo ""
|
||||
echo "=== Test Teilziel 2 ==="
|
||||
list_root_server "k"
|
||||
echo ""
|
||||
echo "=== Test Teilziel 3 ==="
|
||||
build_delegation_path "www.it-praxisportal.de"
|
||||
echo ""
|
||||
echo "=== Test Teilziel 4 ==="
|
||||
validate_fqdn_syntax "www.it-praxisportal.de"
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
<link rel="icon" type="image/svg+xml" href="../../../assets/favicon.svg">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>DNS 01: DNS-Hierarchie & Root-Server</title>
|
||||
<title>DNS 01: DNS-Hierarchie & Root-Server</title>
|
||||
<link rel="stylesheet" href="../../../assets/style.css">
|
||||
</head>
|
||||
<body>
|
||||
|
|
@ -12,46 +12,121 @@
|
|||
<div class="app-main">
|
||||
<header>
|
||||
<div class="header-container">
|
||||
<div class="header-badge">🌐 DNS Grundlagen</div>
|
||||
<h1>DNS 01: DNS-Hierarchie & Root-Server</h1>
|
||||
<p>Verstehe den globalen DNS-Baum: Root-Zone (.), TLDs (gTLD/ccTLD), SLDs, FQDNs und autoritative vs. rekursive Nameserver.</p>
|
||||
<div class="header-badge">🌐 DNS & Infrastruktur</div>
|
||||
<h1>DNS 01: DNS-Hierarchie, FQDN & Root-Server</h1>
|
||||
<p>Verstehe den globalen DNS-Baum: Root-Zone (.), Top-Level Domains (TLD), Second-Level Domains (SLD) und Anycast Root-Cluster.</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="container">
|
||||
<!-- 1. THEORIE: DER DNS-BAUM -->
|
||||
<section class="card">
|
||||
<h2>💡 Theorie & Praxiskonzept</h2>
|
||||
<p>Verstehe den globalen DNS-Baum: Root-Zone (.), TLDs (gTLD/ccTLD), SLDs, FQDNs und autoritative vs. rekursive Nameserver.</p>
|
||||
<div class="code-container">
|
||||
<div class="code-header">
|
||||
<span>DNS Command & Syntax Reference</span>
|
||||
<span>Tool: dig</span>
|
||||
</div>
|
||||
<pre><code># Wichtige Befehle & Zonensyntax:
|
||||
dig dig
|
||||
named-checkzone example.com /etc/bind/zones/db.example.com</code></pre>
|
||||
<h2>🌳 1. Die DNS-Baumstruktur (Inverted Tree)</h2>
|
||||
<p>Das Domain Name System ist hierarchisch aufgebaut. An der Spitze steht nicht wie bei Dateipfaden ein Schrägstrich <code>/</code>, sondern der unscheinbare, aber essentielle Wurzelpunkt <code>.</code> (Root-Zone).</p>
|
||||
|
||||
<pre><code> . (Root Zone)
|
||||
/ \
|
||||
de. com. (Top-Level Domains)
|
||||
/ \
|
||||
it-praxisportal.de. beispiel.com. (Second-Level Domains)
|
||||
/
|
||||
intern. (Subdomain)
|
||||
/
|
||||
srv01. (Hostname / Leaf Node)</code></pre>
|
||||
|
||||
<div class="box box-analogie">
|
||||
<div class="box-title">🏢 Analogie: Das weltweite Postleitzahlen- & Adresssystem</div>
|
||||
<p>Ein FQDN liest sich im DNS von <strong>rechts nach links</strong> wie eine internationale Postadresse von unten nach oben: Zuerst das Land (<code>.de</code>), dann die Stadt/Organisation (<code>it-praxisportal</code>), dann die Abteilung (<code>intern</code>) und schließlich das konkrete Zimmer oder der Arbeitsplatzrechner (<code>srv01</code>)!</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 2. THEORIE: FQDN & SCHLUSSPUNKT -->
|
||||
<section class="card">
|
||||
<h2>🎯 Aufgaben & Checkliste</h2>
|
||||
<h2>📍 2. Der Fully Qualified Domain Name (FQDN)</h2>
|
||||
<p>Ein FQDN spezifiziert den vollständigen Pfad im DNS-Baum. In BIND-Zonendateien entscheidet der Schlusspunkt darüber, ob ein Eintrag absolut oder relativ interpretiert wird:</p>
|
||||
|
||||
<pre><code># Absoluter FQDN (mit Schlusspunkt):
|
||||
mail.firma.de. IN A 192.0.2.10 # Ziel ist exakt mail.firma.de
|
||||
|
||||
# Relativer Hostname (ohne Schlusspunkt):
|
||||
mail IN A 192.0.2.10 # BIND hängt $ORIGIN (firma.de.) automatisch an!
|
||||
|
||||
# ⚠️ HÄUFIGE FEHLERQUELLE IN DER PRAXIS:
|
||||
app.firma.de IN CNAME server01.cloud.de # OHNE Schlusspunkt!
|
||||
# BIND macht daraus: server01.cloud.de.firma.de. -> RESOLUTION FAILURE!</code></pre>
|
||||
</section>
|
||||
|
||||
<!-- 3. CHEAT-SHEET -->
|
||||
<section class="card">
|
||||
<h2>📑 3. Cheat-Sheet: DNS-Ebenen & RFC 1035 Limits</h2>
|
||||
<div style="overflow-x: auto;">
|
||||
<table style="width: 100%; border-collapse: collapse; font-size: 0.9rem; text-align: left;">
|
||||
<thead>
|
||||
<tr style="border-bottom: 2px solid var(--border-color); background: var(--bg-subtle);">
|
||||
<th style="padding: 10px;">Ebene / Parameter</th>
|
||||
<th style="padding: 10px;">Beispiel</th>
|
||||
<th style="padding: 10px;">RFC-Regeln & Grenzwerte</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr style="border-bottom: 1px solid var(--border-color);">
|
||||
<td style="padding: 10px;"><strong>Root Zone</strong></td>
|
||||
<td style="padding: 10px;"><code>.</code></td>
|
||||
<td style="padding: 10px;">Verwaltet durch IANA / ICANN; 13 Anycast-Cluster (A-M)</td>
|
||||
</tr>
|
||||
<tr style="border-bottom: 1px solid var(--border-color);">
|
||||
<td style="padding: 10px;"><strong>TLD (Top-Level)</strong></td>
|
||||
<td style="padding: 10px;"><code>.de</code>, <code>.com</code></td>
|
||||
<td style="padding: 10px;">ccTLD (Länder) oder gTLD (generisch); eigene Registry (z.B. DENIC)</td>
|
||||
</tr>
|
||||
<tr style="border-bottom: 1px solid var(--border-color);">
|
||||
<td style="padding: 10px;"><strong>SLD (Second-Level)</strong></td>
|
||||
<td style="padding: 10px;"><code>it-praxisportal</code></td>
|
||||
<td style="padding: 10px;">Registrierbarer Name; verweist auf autoritative Nameserver</td>
|
||||
</tr>
|
||||
<tr style="border-bottom: 1px solid var(--border-color);">
|
||||
<td style="padding: 10px;"><strong>Label-Länge</strong></td>
|
||||
<td style="padding: 10px;"><code>mein-server-01</code></td>
|
||||
<td style="padding: 10px;">Maximal <strong>63 Oktette (Zeichen)</strong> pro Label zwischen Punkten</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 10px;"><strong>FQDN Gesamtlänge</strong></td>
|
||||
<td style="padding: 10px;"><code>host.sub.firma.de.</code></td>
|
||||
<td style="padding: 10px;">Maximal <strong>253 Zeichen</strong> netto (255 Oktette binär inkl. Längenbytes)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 4. AUFGABEN CHECKLISTE -->
|
||||
<section class="card">
|
||||
<h2>🎯 Aufgaben & Checkliste in <code>aufgabe.sh</code></h2>
|
||||
<div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t1">
|
||||
<label class="task-label" for="t1"><strong>TODO 1:</strong> FQDN in Hostname, SLD und TLD zerlegen</label>
|
||||
</div><div class="task-item">
|
||||
<label class="task-label" for="t1"><strong>TODO 1:</strong> <code>parse_fqdn(fqdn)</code>: Host, Domain, SLD, TLD und Root-Dot-Prüfung implementieren.</label>
|
||||
</div>
|
||||
<div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t2">
|
||||
<label class="task-label" for="t2"><strong>TODO 2:</strong> Root-Server-Cluster (A-M) und Anycast-Routing erklären</label>
|
||||
</div><div class="task-item">
|
||||
<label class="task-label" for="t2"><strong>TODO 2:</strong> <code>list_root_server(cluster)</code>: Root-Cluster A bis M mit Betreiberorganisationen ausgeben.</label>
|
||||
</div>
|
||||
<div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t3">
|
||||
<label class="task-label" for="t3"><strong>TODO 3:</strong> Unterschied autoritativer vs. rekursiver Nameserver definieren</label>
|
||||
</div><div class="task-item">
|
||||
<label class="task-label" for="t3"><strong>TODO 3:</strong> <code>build_delegation_path(domain)</code>: Den hierarchischen Delegationspfad formatieren.</label>
|
||||
</div>
|
||||
<div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t4">
|
||||
<label class="task-label" for="t4"><strong>TODO 4:</strong> DNS-Baumpfad für www.it-praxisportal.de konstruieren</label>
|
||||
<label class="task-label" for="t4"><strong>TODO 4:</strong> <code>validate_fqdn_syntax(fqdn)</code>: RFC 1035 Regeln (Länge, Bindestriche, Zeichensatz) prüfen.</label>
|
||||
</div>
|
||||
|
||||
<div class="box box-tipp" style="margin-top: 20px;">
|
||||
<div class="box-title">💡 IHK Prüfungstipp: Warum genau 13 Root-Server-Namen?</div>
|
||||
<p>Historisch passte in ein unfragmentiertes 512-Byte UDP-DNS-Antwortpaket (RFC 1035) bei einer Abfrage nach der Root-Zone exakt die Delegation für 13 Nameserver samt ihrer IPv4-Glue-Records! Erst mit <strong>EDNS0</strong> wurde dieser Puffer erweitert.</p>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 25px; display: flex; gap: 12px; flex-wrap: wrap;">
|
||||
<a href="../../../workspace.html?course=dns_records&track=track_1_dns_architektur&chapter=01_dns_hierarchie_und_root_server" class="btn" style="background: #0284c7; color: #fff; font-weight: 700;">💻 In Web-IDE öffnen →</a>
|
||||
<a href="../02_rekursive_vs_iterative_abfragen/index.html" class="btn btn-secondary">Weiter zu Modul →</a>
|
||||
<a href="../../../workspace.html?course=dns_records&track=track_1_dns_architektur&chapter=01_dns_hierarchie_und_root_server" class="btn" style="background: #1d4ed8; color: #fff; font-weight: 700;">In Web-IDE öffnen →</a>
|
||||
<a href="../02_rekursive_vs_iterative_abfragen/index.html" class="btn btn-secondary">Weiter zu Modul 02 →</a>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
|
|
|||
|
|
@ -1,97 +1,143 @@
|
|||
#!/usr/bin/env bash
|
||||
# ==============================================================================
|
||||
# MUSTERLÖSUNG: DNS 01: DNS-Hierarchie & Root-Server
|
||||
# MUSTERLÖSUNG: DNS 01: DNS-HIERARCHIE, FQDN-STRUKTUR & ROOT-SERVER
|
||||
# ==============================================================================
|
||||
|
||||
analyze_dns_hierarchy() {
|
||||
local domain="${1:-it-praxisportal.de}"
|
||||
local target_dir="${2:-.}"
|
||||
parse_fqdn() {
|
||||
local raw="$1"
|
||||
[ -z "$raw" ] && return 1
|
||||
|
||||
echo "▶ Starte professionelle DNS-Operation für $domain..."
|
||||
local has_root="NO"
|
||||
if [[ "$raw" == *. ]]; then
|
||||
has_root="YES"
|
||||
fi
|
||||
|
||||
case "01_dns_hierarchie_und_root_server" in
|
||||
*01_dns_hierarchie*)
|
||||
echo "Host: www | SLD: it-praxisportal | TLD: de | Root: ."
|
||||
;;
|
||||
*02_rekursive*)
|
||||
echo "Simulation Iteration: Root (.) -> de. -> ns1.it-praxisportal.de -> 188.245.100.5"
|
||||
;;
|
||||
*03_ports_udp*)
|
||||
echo "Protocol: UDP/53 (Standard) | Fallback: TCP/53 | EDNS0 Buffer: 4096 bytes"
|
||||
;;
|
||||
*04_ttl*)
|
||||
echo "TTL Current: 86400s | Migration Planned TTL: 300s | Neg Cache: 3600s"
|
||||
;;
|
||||
*05_a_und_aaaa*)
|
||||
cat << 'ZONE' > "$target_dir/zone_records.txt"
|
||||
@ IN A 188.245.100.5
|
||||
@ IN AAAA 2a01:4f8:c010:d::1
|
||||
www IN A 188.245.100.5
|
||||
ZONE
|
||||
;;
|
||||
*06_cname*)
|
||||
echo "api IN CNAME app.it-praxisportal.de." > "$target_dir/cname_record.txt"
|
||||
;;
|
||||
*07_soa*)
|
||||
cat << 'ZONE' > "$target_dir/soa_record.txt"
|
||||
@ IN SOA ns1.it-praxisportal.de. hostmaster.it-praxisportal.de. (
|
||||
2026091201 ; Serial YYYYMMDDNN
|
||||
7200 ; Refresh (2h)
|
||||
3600 ; Retry (1h)
|
||||
1209600 ; Expire (2w)
|
||||
3600 ; Negative Cache TTL (1h)
|
||||
)
|
||||
ZONE
|
||||
;;
|
||||
*08_ns_und_ptr*)
|
||||
echo "100.245.188.in-addr.arpa. IN PTR mail.it-praxisportal.de." > "$target_dir/ptr_record.txt"
|
||||
;;
|
||||
*09_mx*)
|
||||
echo "@ IN MX 10 mail.it-praxisportal.de." > "$target_dir/mx_record.txt"
|
||||
echo "@ IN MX 20 backup-mail.it-praxisportal.de." >> "$target_dir/mx_record.txt"
|
||||
;;
|
||||
*10_txt_records*)
|
||||
echo '@ IN TXT "v=spf1 mx ip4:188.245.100.5 include:_spf.google.com -all"' > "$target_dir/spf_record.txt"
|
||||
;;
|
||||
*11_dkim*)
|
||||
echo 's1._domainkey IN TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz..."' > "$target_dir/dkim_record.txt"
|
||||
;;
|
||||
*12_dmarc*)
|
||||
echo '_dmarc IN TXT "v=DMARC1; p=reject; rua=mailto:dmarc-reports@it-praxisportal.de; pct=100; aspf=s"' > "$target_dir/dmarc_record.txt"
|
||||
;;
|
||||
*13_srv*)
|
||||
echo '_ldap._tcp.dc._msdcs.corp IN SRV 0 100 389 dc01.corp.it-praxisportal.de.' > "$target_dir/srv_record.txt"
|
||||
;;
|
||||
*14_caa*)
|
||||
echo '@ IN CAA 0 issue "letsencrypt.org"' > "$target_dir/caa_record.txt"
|
||||
echo '@ IN CAA 0 iodef "mailto:security@it-praxisportal.de"' >> "$target_dir/caa_record.txt"
|
||||
;;
|
||||
*15_dns_troubleshooting*)
|
||||
echo "STATUS: NOERROR | ANSWER: 1 | FLAGS: qr aa rd ra"
|
||||
;;
|
||||
*16_master*)
|
||||
cat << 'REPORT' > "$target_dir/dns_audit_report.json"
|
||||
{
|
||||
"domain": "it-praxisportal.de",
|
||||
"audit_status": "PASSED",
|
||||
"score": 100,
|
||||
"soa_serial_valid": true,
|
||||
"ns_redundancy": 2,
|
||||
"email_security": {
|
||||
"spf": "valid (-all)",
|
||||
"dkim": "present",
|
||||
"dmarc": "enforced (p=reject)"
|
||||
},
|
||||
"dnssec": "active"
|
||||
local clean="${raw%.}"
|
||||
IFS='.' read -ra labels <<< "$clean"
|
||||
local count=${#labels[@]}
|
||||
|
||||
if [ "$count" -lt 2 ]; then
|
||||
echo "ERROR: Mindestens SLD und TLD erforderlich"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local host="${labels[0]}"
|
||||
local tld="${labels[-1]}"
|
||||
local sld="${labels[-2]}"
|
||||
|
||||
local domain=""
|
||||
for (( i=1; i<count; i++ )); do
|
||||
if [ -z "$domain" ]; then
|
||||
domain="${labels[i]}"
|
||||
else
|
||||
domain="$domain.${labels[i]}"
|
||||
fi
|
||||
done
|
||||
|
||||
echo "HOST=$host"
|
||||
echo "DOMAIN=$domain"
|
||||
echo "SLD=$sld"
|
||||
echo "TLD=$tld"
|
||||
echo "HAS_ROOT_DOT=$has_root"
|
||||
return 0
|
||||
}
|
||||
REPORT
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "✅ DNS-Operation erfolgreich abgeschlossen."
|
||||
list_root_server() {
|
||||
local req="${1:-all}"
|
||||
req=$(echo "$req" | tr '[:upper:]' '[:lower:]')
|
||||
|
||||
declare -A OPERATORS=(
|
||||
["a"]="Verisign, Inc."
|
||||
["b"]="University of Southern California"
|
||||
["c"]="Cogent Communications"
|
||||
["d"]="University of Maryland"
|
||||
["e"]="NASA Ames Research Center"
|
||||
["f"]="Internet Systems Consortium (ISC)"
|
||||
["g"]="US Department of Defense"
|
||||
["h"]="US Army Research Lab"
|
||||
["i"]="Netnod"
|
||||
["j"]="Verisign, Inc."
|
||||
["k"]="RIPE NCC"
|
||||
["l"]="ICANN"
|
||||
["m"]="WIDE Project"
|
||||
)
|
||||
|
||||
if [ "$req" = "all" ]; then
|
||||
for letter in a b c d e f g h i j k l m; do
|
||||
local u=$(echo "$letter" | tr '[:lower:]' '[:upper:]')
|
||||
echo "CLUSTER=$u | OPERATOR=${OPERATORS[$letter]} | HOST=${letter}.root-servers.net"
|
||||
done
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ -n "${OPERATORS[$req]}" ]]; then
|
||||
local u=$(echo "$req" | tr '[:lower:]' '[:upper:]')
|
||||
echo "CLUSTER=$u | OPERATOR=${OPERATORS[$req]} | HOST=${req}.root-servers.net"
|
||||
return 0
|
||||
else
|
||||
echo "ERROR: Ungültiger Cluster"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
build_delegation_path() {
|
||||
local domain="$1"
|
||||
[ -z "$domain" ] && return 1
|
||||
|
||||
local clean="${domain%.}"
|
||||
IFS='.' read -ra labels <<< "$clean"
|
||||
local count=${#labels[@]}
|
||||
|
||||
if [ "$count" -lt 2 ]; then
|
||||
echo "ERROR: Domain ungültig"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local tld="${labels[-1]}"
|
||||
local sld="${labels[-2]}"
|
||||
|
||||
echo "[ROOT] . -> [TLD] ${tld}. -> [SLD] ${sld}.${tld}. -> [FQDN] ${clean}."
|
||||
return 0
|
||||
}
|
||||
|
||||
validate_fqdn_syntax() {
|
||||
local raw="$1"
|
||||
local clean="${raw%.}"
|
||||
|
||||
if [ ${#clean} -gt 253 ]; then
|
||||
echo "INVALID: Gesamtlänge überschreitet 253 Zeichen"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ "$clean" =~ \.\. ]]; then
|
||||
echo "INVALID: Leeres Label gefunden (Doppelpunkt)"
|
||||
return 1
|
||||
fi
|
||||
|
||||
IFS='.' read -ra labels <<< "$clean"
|
||||
for lbl in "${labels[@]}"; do
|
||||
if [ ${#lbl} -eq 0 ]; then
|
||||
echo "INVALID: Label darf nicht leer sein"
|
||||
return 1
|
||||
fi
|
||||
if [ ${#lbl} -gt 63 ]; then
|
||||
echo "INVALID: Label '${lbl}' überschreitet 63 Zeichen"
|
||||
return 1
|
||||
fi
|
||||
if [[ "$lbl" =~ ^- ]] || [[ "$lbl" =~ -$ ]]; then
|
||||
echo "INVALID: Bindestrich am Anfang oder Ende von '${lbl}' unzulässig"
|
||||
return 1
|
||||
fi
|
||||
if [[ ! "$lbl" =~ ^[a-zA-Z0-9-]+$ ]]; then
|
||||
echo "INVALID: Unerlaubte Zeichen in '${lbl}'"
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
|
||||
echo "VALID: ${raw}"
|
||||
return 0
|
||||
}
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
analyze_dns_hierarchy "$@"
|
||||
parse_fqdn "$@"
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -1,36 +1,108 @@
|
|||
#!/usr/bin/env bash
|
||||
# ==============================================================================
|
||||
# TESTSUITE: DNS 01: DNS-Hierarchie & Root-Server
|
||||
# 🧪 TESTSUITE: DNS 01 - DNS-HIERARCHIE & ROOT-SERVER
|
||||
# ==============================================================================
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
BLUE='\033[0;34m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
TARGET_SCRIPT="${1:-aufgabe.sh}"
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
TOTAL_TESTS=0
|
||||
PASSED_TESTS=0
|
||||
FAILED_TESTS=0
|
||||
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
echo -e "${BLUE}🧪 TESTSUITE: DNS 01: DNS-Hierarchie & Root-Server${NC}"
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
echo -e "${BLUE} Ziel-Skript: ${YELLOW}${TARGET_SCRIPT}${NC}"
|
||||
echo -e "${BLUE}================================================================${NC}\n"
|
||||
|
||||
TEST_DIR=$(mktemp -d /tmp/dns_test_XXXXXX)
|
||||
trap 'rm -rf "$TEST_DIR"' EXIT
|
||||
if [ ! -f "$SCRIPT_DIR/$TARGET_SCRIPT" ]; then
|
||||
echo -e "${RED}❌ Fehler: Datei '$TARGET_SCRIPT' nicht gefunden!${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
source "$SCRIPT_DIR/$TARGET_SCRIPT"
|
||||
|
||||
# Führe Funktion aus
|
||||
analyze_dns_hierarchy "example.com" "$TEST_DIR"
|
||||
RET=$?
|
||||
run_test() {
|
||||
local name="$1"
|
||||
local cmd="$2"
|
||||
((TOTAL_TESTS++))
|
||||
echo -e "▶ Test ${TOTAL_TESTS}: ${name}"
|
||||
if eval "$cmd"; then
|
||||
echo -e " ${GREEN}✓ BESTANDEN${NC}\n"
|
||||
((PASSED_TESTS++))
|
||||
else
|
||||
echo -e " ${RED}✗ FEHLGESCHLAGEN${NC}\n"
|
||||
((FAILED_TESTS++))
|
||||
fi
|
||||
}
|
||||
|
||||
if [ $RET -eq 0 ]; then
|
||||
echo -e "${GREEN}✓ Test 1: Funktion analyze_dns_hierarchy existiert und gibt 0 zurück.${NC}"
|
||||
echo -e "${GREEN}✓ Test 2: DNS Resource Records / Syntax validiert.${NC}"
|
||||
echo -e "${GREEN}✓ Test 3: Teilziele 1 bis 4 erfolgreich abgeschlossen.${NC}"
|
||||
echo -e "${GREEN}🎉 ERFOLG: Alle DNS-Prüfungen in DNS 01: DNS-Hierarchie & Root-Server bestanden!${NC}"
|
||||
# TEST 1: FQDN Zerlegung mit Schlusspunkt
|
||||
run_test "parse_fqdn mit Schlusspunkt (srv01.intern.example.de.)" '
|
||||
OUT=$(parse_fqdn "srv01.intern.example.de.")
|
||||
echo "$OUT" | grep -q "HOST=srv01" && \
|
||||
echo "$OUT" | grep -q "DOMAIN=intern.example.de" && \
|
||||
echo "$OUT" | grep -q "SLD=example" && \
|
||||
echo "$OUT" | grep -q "TLD=de" && \
|
||||
echo "$OUT" | grep -q "HAS_ROOT_DOT=YES"
|
||||
'
|
||||
|
||||
# TEST 2: FQDN Zerlegung ohne Schlusspunkt
|
||||
run_test "parse_fqdn ohne Schlusspunkt (api.cloud.kunde.com)" '
|
||||
OUT=$(parse_fqdn "api.cloud.kunde.com")
|
||||
echo "$OUT" | grep -q "HOST=api" && \
|
||||
echo "$OUT" | grep -q "DOMAIN=cloud.kunde.com" && \
|
||||
echo "$OUT" | grep -q "SLD=kunde" && \
|
||||
echo "$OUT" | grep -q "TLD=com" && \
|
||||
echo "$OUT" | grep -q "HAS_ROOT_DOT=NO"
|
||||
'
|
||||
|
||||
# TEST 3: Root-Server Auskunft Einzelcluster
|
||||
run_test "list_root_server Cluster K (RIPE NCC)" '
|
||||
OUT=$(list_root_server "k")
|
||||
echo "$OUT" | grep -q "CLUSTER=K" && \
|
||||
echo "$OUT" | grep -q "RIPE NCC" && \
|
||||
echo "$OUT" | grep -q "k.root-servers.net"
|
||||
'
|
||||
|
||||
# TEST 4: Root-Server Auskunft alle 13
|
||||
run_test "list_root_server alle 13 Cluster (A-M)" '
|
||||
OUT=$(list_root_server "all")
|
||||
COUNT=$(echo "$OUT" | grep -c "root-servers.net")
|
||||
[ "$COUNT" -eq 13 ]
|
||||
'
|
||||
|
||||
# TEST 5: Delegationspfad
|
||||
run_test "build_delegation_path für www.it-praxisportal.de" '
|
||||
OUT=$(build_delegation_path "www.it-praxisportal.de")
|
||||
echo "$OUT" | grep -q "\[ROOT\] \." && \
|
||||
echo "$OUT" | grep -q "\[TLD\] de\." && \
|
||||
echo "$OUT" | grep -q "\[SLD\] it-praxisportal\.de\." && \
|
||||
echo "$OUT" | grep -q "\[FQDN\] www\.it-praxisportal\.de\."
|
||||
'
|
||||
|
||||
# TEST 6: FQDN-Validierung (Valider Name)
|
||||
run_test "validate_fqdn_syntax mit gültigem FQDN" '
|
||||
validate_fqdn_syntax "sub-01.kunde.de" | grep -q "VALID:"
|
||||
'
|
||||
|
||||
# TEST 7: FQDN-Validierung (Ungültiger Name mit führendem Bindestrich)
|
||||
run_test "validate_fqdn_syntax fängt fehlerhafte Bindestriche ab (-bad.de)" '
|
||||
validate_fqdn_syntax "-bad.firma.de" | grep -q "INVALID:"
|
||||
'
|
||||
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
if [ "$FAILED_TESTS" -eq 0 ]; then
|
||||
echo -e "${GREEN}🎉 ERFOLG: Alle ${PASSED_TESTS}/${TOTAL_TESTS} Tests in DNS 01 erfolgreich bestanden!${NC}"
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
exit 0
|
||||
else
|
||||
echo -e "${RED}❌ FEHLER: Skript fehlgeschlagen mit Status $RET${NC}"
|
||||
echo -e "${RED}❌ FEHLER: ${FAILED_TESTS} von ${TOTAL_TESTS} Tests fehlgeschlagen.${NC}"
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -1,18 +1,76 @@
|
|||
# DNS 02: Rekursive vs. Iterative Abfragen
|
||||
# DNS 02: Rekursive vs. Iterative Abfragen & DNS-Flags
|
||||
|
||||
Willkommen zum Kurs **DNS-Records, Domain Name System & E-Mail-Sicherheit** im IT-Praxisportal!
|
||||
Willkommen zu **Modul 02** des Kurses **DNS-Records, Domain Name System & E-Mail-Sicherheit**!
|
||||
|
||||
## 💡 Das Wichtigste in Kürze
|
||||
- **Ziel:** Der schrittweise Ablauf einer DNS-Abfrage: Stub-Resolver, Recursive Resolver, Iteration bis zum Auth-Server.
|
||||
- **Wichtigstes Tool:** `dig +trace`
|
||||
In diesem Modul lernst du den Unterschied zwischen der rekursiven Anfrage eines Clients und der iterativen Recherche des Resolvers im globalen DNS-Baum kennen. Zudem analysierst du zentrale DNS-Header-Flags und schützt Nameserver vor dem gefährlichen **Open Resolver**-Zustand.
|
||||
|
||||
## 🎯 Deine Aufgaben (Checkliste)
|
||||
1. **Teilziel 1:** Flags RD (Recursion Desired) und RA (Recursion Available) prüfen
|
||||
2. **Teilziel 2:** Iterative Namensauflösung schrittweise simulieren
|
||||
3. **Teilziel 3:** Resolver-Cache und TTL-Abzug nachvollziehen
|
||||
4. **Teilziel 4:** DNS-Forwarder für Unternehmensnetze konfigurieren
|
||||
---
|
||||
|
||||
## 💡 1. Das Wichtigste in Kürze
|
||||
|
||||
### Rekursiv vs. Iterativ
|
||||
- **Rekursive Abfrage (Client → Resolver):**
|
||||
- Der Client fordert: *„Finde die IP für mich heraus, ich warte auf das fertige Ergebnis!“*
|
||||
- Signalisiert durch das Flag **RD** (Recursion Desired = 1).
|
||||
- Der Server antwortet mit **RA** (Recursion Available = 1), falls er diesen Dienst anbietet.
|
||||
- **Iterative Abfrage (Resolver → Nameserver-Hierarchie):**
|
||||
- Der Resolver fragt den Server: *„Kennst du die IP?“*
|
||||
- Der Nameserver antwortet: *„Nein, aber frage bitte den Server X, der ist für die Zone zuständig (Referral)!“*
|
||||
- Der Resolver hangelt sich von der Root (`.`) über die TLD (`.de`) bis zum autoritativen Server (`firma.de`).
|
||||
- Der letzte Server setzt das Flag **AA** (Authoritative Answer = 1), da er die Master-Zonendatei besitzt.
|
||||
|
||||
```
|
||||
+-------------+ +----------------------+
|
||||
| Client (PC) | -- RD=1 Query ->| Rekursiver Resolver |
|
||||
| | <- IP Result --| (z.B. Router / BIND) |
|
||||
+-------------+ +----------------------+
|
||||
| ^
|
||||
1. Root (.) | | Referral to .de
|
||||
v |
|
||||
+----------------------+
|
||||
| Root-Server (.) |
|
||||
+----------------------+
|
||||
| ^
|
||||
2. TLD (.de) | | Referral to firma.de
|
||||
v |
|
||||
+----------------------+
|
||||
| TLD-Server (.de) |
|
||||
+----------------------+
|
||||
| ^
|
||||
3. SLD (firma.de) | | AA=1 (192.0.2.1)
|
||||
v |
|
||||
+----------------------+
|
||||
| Auth NS (firma.de) |
|
||||
+----------------------+
|
||||
```
|
||||
|
||||
### Die wichtigsten DNS-Flags
|
||||
| Flag | Name | Bedeutung |
|
||||
| :--- | :--- | :--- |
|
||||
| **QR** | Query / Response | `0` = Anfrage, `1` = Antwort |
|
||||
| **AA** | Authoritative Answer | `1` = Antwort stammt direkt vom Master-Nameserver der Zone |
|
||||
| **TC** | Truncated | `1` = Antwort war größer als der UDP-Puffer und wurde abgeschnitten |
|
||||
| **RD** | Recursion Desired | `1` = Client wünscht rekursive Auflösung |
|
||||
| **RA** | Recursion Available | `1` = Server unterstützt Rekursion |
|
||||
|
||||
### Sicherheitsrisiko: Open Resolver
|
||||
Ein Nameserver mit `RA=1`, der Anfragen aus dem gesamten Internet (`0.0.0.0/0`) beantwortet, ist ein **Open Resolver**.
|
||||
Angreifer nutzen solche Server für **DNS Amplification Attacks**: Sie fälschen die Absender-IP (IP-Spoofing auf das Opfer) und senden kleine Anfragen mit riesiger Antwort (z.B. `dig ANY isc.org`), wodurch das Opfer mit Gigabit-Datenmengen überflutet wird!
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Deine Aufgaben in `aufgabe.sh`
|
||||
|
||||
1. **TODO 1 (`parse_dns_flags`):** Zerlege einen Flag-String und ermittle den Status von `QR`, `AA`, `TC`, `RD` und `RA`.
|
||||
2. **TODO 2 (`simulate_iterative_steps`):** Gib die 4 aufeinanderfolgenden Schritte der iterativen Auflösung für eine gegebene Domain aus.
|
||||
3. **TODO 3 (`detect_open_resolver`):** Prüfe Nameserver-Flags und ACL-Netzwerk auf Open-Resolver-Schwachstellen.
|
||||
4. **TODO 4 (`generate_forwarder_config`):** Generiere eine sichere BIND9-Konfiguration mit Upstream-Forwardern.
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Tests ausführen
|
||||
|
||||
Führe im Terminal folgenden Befehl aus:
|
||||
```bash
|
||||
bash test_aufgabe.sh
|
||||
```
|
||||
|
|
|
|||
|
|
@ -1,21 +1,124 @@
|
|||
#!/usr/bin/env bash
|
||||
# ==============================================================================
|
||||
# 🌐 DNS Resolver - DNS 02: Rekursive vs. Iterative Abfragen
|
||||
# 🌐 DNS 02: REKURSIVE VS. ITERATIVE ABFRAGEN & DNS-FLAGS
|
||||
# ==============================================================================
|
||||
#
|
||||
# ZWEI ARTEN DER NAMENSAUFLÖSUNG:
|
||||
# 1. Rekursive Abfrage:
|
||||
# - Der Client (Stub-Resolver) sendet eine Anfrage mit gesetztem RD-Flag (Recursion Desired)
|
||||
# an seinen konfigurierten Resolver (z.B. Router oder Unternehmens-DNS).
|
||||
# - Der Resolver übernimmt die komplette Arbeit und antwortet erst, wenn die
|
||||
# endgültige IP vorliegt oder ein Fehler auftritt.
|
||||
#
|
||||
# 2. Iterative Abfrage:
|
||||
# - Der rekursive Resolver fragt den DNS-Baum von oben nach unten ab.
|
||||
# - Jeder kontaktierte Server liefert entweder die Antwort ODER ein "Referral"
|
||||
# (Verweis) auf den nächsten zuständigen Nameserver:
|
||||
# Root (.) -> Referral zu TLD (.de) -> Referral zu SLD (firma.de) -> Antwort!
|
||||
#
|
||||
# WICHTIGE DNS-FLAGS:
|
||||
# QR : 0 = Query (Anfrage), 1 = Response (Antwort)
|
||||
# AA : Authoritative Answer (Der antwortende Server besitzt die Zonendatei)
|
||||
# TC : Truncated (Paket überschritt UDP-Puffergröße und wurde abgeschnitten)
|
||||
# RD : Recursion Desired (Client wünscht rekursive Auflösung)
|
||||
# RA : Recursion Available (Server unterstützt Rekursion)
|
||||
# ==============================================================================
|
||||
|
||||
# 🎯 TEILZIEL 1: Flags RD (Recursion Desired) und RA (Recursion Available) prüfen
|
||||
# 🎯 TEILZIEL 2: Iterative Namensauflösung schrittweise simulieren
|
||||
# 🎯 TEILZIEL 3: Resolver-Cache und TTL-Abzug nachvollziehen
|
||||
# 🎯 TEILZIEL 4: DNS-Forwarder für Unternehmensnetze konfigurieren
|
||||
# 🎯 TEILZIEL 1 (TODO 1): DNS-Flags analysieren
|
||||
# Funktion: parse_dns_flags "$flags_string"
|
||||
# Parameter $1: Ein String mit Leerzeichen-separierten Flags (z.B. "qr aa rd ra")
|
||||
# Anforderungen:
|
||||
# - Prüfe, welche der 5 Flags (QR, AA, TC, RD, RA) vorhanden sind (case-insensitive).
|
||||
# - Gib für jedes Flag eine Zeile im Format aus:
|
||||
# QR=<0_QUERY|1_RESPONSE>
|
||||
# AA=<YES|NO>
|
||||
# TC=<YES|NO>
|
||||
# RD=<YES|NO>
|
||||
# RA=<YES|NO>
|
||||
# Wenn "qr" im String enthalten ist -> QR=1_RESPONSE, sonst QR=0_QUERY.
|
||||
parse_dns_flags() {
|
||||
local raw_flags="$1"
|
||||
# TODO 1: Implementiere die Flag-Analyse
|
||||
return 0
|
||||
}
|
||||
|
||||
trace_dns_resolution() {
|
||||
local domain="${1:-it-praxisportal.de}"
|
||||
echo "▶ Analysiere DNS-Konfiguration für $domain..."
|
||||
# 🎯 TEILZIEL 2 (TODO 2): Iterativen Abfragepfad simulieren
|
||||
# Funktion: simulate_iterative_steps "$domain"
|
||||
# Parameter $1: Ein Domänenname (z.B. "web.firma.de")
|
||||
# Anforderungen:
|
||||
# - Zerlege die Domain und gib exakt folgende 4 logischen Schritte zeilenweise aus:
|
||||
# SCHRITT 1: Query an Root (.) -> Referral zu TLD Nameserver (<tld>.)
|
||||
# SCHRITT 2: Query an TLD Server (<tld>.) -> Referral zu autoritativem Nameserver (<sld>.<tld>.)
|
||||
# SCHRITT 3: Query an autoritativen Server (<sld>.<tld>.) -> Antwort erhalten (AA=1)
|
||||
# SCHRITT 4: Resolver speichert Ergebnis im Cache und sendet Antwort an Client (RA=1)
|
||||
# Beispiel bei "portal.schule.org":
|
||||
# SCHRITT 1: Query an Root (.) -> Referral zu TLD Nameserver (org.)
|
||||
# ...
|
||||
simulate_iterative_steps() {
|
||||
local domain="$1"
|
||||
# TODO 2: Simuliere die 4 iterativen Schritte
|
||||
return 0
|
||||
}
|
||||
|
||||
# TODO: Implementiere die geforderten DNS-Prüfungen oder Record-Erstellungen
|
||||
return 1
|
||||
# 🎯 TEILZIEL 3 (TODO 3): Open-Resolver Sicherheitsprüfung
|
||||
# Funktion: detect_open_resolver "$flags_string" "$allowed_network"
|
||||
# Parameter $1: Flags des Nameservers (z.B. "qr rd ra")
|
||||
# Parameter $2: Erlaubtes Zugriffsnetz (z.B. "0.0.0.0/0" oder "192.168.1.0/24")
|
||||
# Hintergrund:
|
||||
# Ein "Open Resolver" beantwortet rekursive Anfragen (RA=YES) für jedermann aus dem
|
||||
# gesamten Internet (0.0.0.0/0 oder any). Dies stellt ein massives Sicherheitsrisiko
|
||||
# für DNS Amplification DDoS-Angriffe dar!
|
||||
# Anforderungen:
|
||||
# - Prüfe, ob "ra" in den Flags enthalten ist.
|
||||
# - Wenn RA aktiv ist UND allowed_network == "0.0.0.0/0" oder "any":
|
||||
# Gib aus: "SECURITY_ALERT: Open Resolver erkannt! Rekursion aus 0.0.0.0/0 muss gesperrt werden."
|
||||
# return 1
|
||||
# - Wenn RA aktiv ist, aber allowed_network ein privates Subnetz ist (z.B. "192.168.*" oder "10.*"):
|
||||
# Gib aus: "SECURE: Rekursiver Resolver auf internes Netz beschränkt."
|
||||
# return 0
|
||||
# - Wenn RA nicht aktiv ist:
|
||||
# Gib aus: "INFO: Reiner autoritativer Server (keine Rekursion)."
|
||||
# return 0
|
||||
detect_open_resolver() {
|
||||
local flags="$1"
|
||||
local network="$2"
|
||||
# TODO 3: Implementiere die Sicherheitsprüfung
|
||||
return 0
|
||||
}
|
||||
|
||||
# 🎯 TEILZIEL 4 (TODO 4): BIND Forwarder-Konfiguration erzeugen
|
||||
# Funktion: generate_forwarder_config "$output_file" "$forwarder_ip1" "$forwarder_ip2"
|
||||
# Parameter $1: Zieldateipfad (z.B. "named.conf.options")
|
||||
# Parameter $2: Primäre Upstream-DNS-IP (z.B. "1.1.1.1")
|
||||
# Parameter $3: Sekundäre Upstream-DNS-IP (z.B. "9.9.9.9")
|
||||
# Anforderungen:
|
||||
# - Schreibe einen sauberen BIND9 options-Block in die Zieldatei:
|
||||
# options {
|
||||
# directory "/var/cache/bind";
|
||||
# recursion yes;
|
||||
# allow-query { 192.168.0.0/16; 10.0.0.0/8; localhost; };
|
||||
# forwarders {
|
||||
# <forwarder_ip1>;
|
||||
# <forwarder_ip2>;
|
||||
# };
|
||||
# forward only;
|
||||
# dnssec-validation auto;
|
||||
# };
|
||||
generate_forwarder_config() {
|
||||
local out_file="$1"
|
||||
local ip1="$2"
|
||||
local ip2="$3"
|
||||
# TODO 4: Schreibe die BIND-Konfigurationsdatei
|
||||
return 0
|
||||
}
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
trace_dns_resolution "$@"
|
||||
echo "=== Test Teilziel 1 ==="
|
||||
parse_dns_flags "qr aa rd ra"
|
||||
echo ""
|
||||
echo "=== Test Teilziel 2 ==="
|
||||
simulate_iterative_steps "shop.kunde.de"
|
||||
echo ""
|
||||
echo "=== Test Teilziel 3 ==="
|
||||
detect_open_resolver "qr ra" "0.0.0.0/0"
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -12,46 +12,113 @@
|
|||
<div class="app-main">
|
||||
<header>
|
||||
<div class="header-container">
|
||||
<div class="header-badge">🌐 DNS Resolver</div>
|
||||
<h1>DNS 02: Rekursive vs. Iterative Abfragen</h1>
|
||||
<p>Der schrittweise Ablauf einer DNS-Abfrage: Stub-Resolver, Recursive Resolver, Iteration bis zum Auth-Server.</p>
|
||||
<div class="header-badge">🌐 DNS & Infrastruktur</div>
|
||||
<h1>DNS 02: Rekursive vs. Iterative Abfragen & Flags</h1>
|
||||
<p>Verstehe den Unterschied zwischen Client-Rekursion und iterativer Baumrecherche. Beherrsche DNS-Flags und sichere Open Resolver ab.</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="container">
|
||||
<!-- 1. THEORIE: REKURSION VS ITERATION -->
|
||||
<section class="card">
|
||||
<h2>💡 Theorie & Praxiskonzept</h2>
|
||||
<p>Der schrittweise Ablauf einer DNS-Abfrage: Stub-Resolver, Recursive Resolver, Iteration bis zum Auth-Server.</p>
|
||||
<div class="code-container">
|
||||
<div class="code-header">
|
||||
<span>DNS Command & Syntax Reference</span>
|
||||
<span>Tool: dig +trace</span>
|
||||
</div>
|
||||
<pre><code># Wichtige Befehle & Zonensyntax:
|
||||
dig dig +trace
|
||||
named-checkzone example.com /etc/bind/zones/db.example.com</code></pre>
|
||||
<h2>🔄 1. Rekursion vs. Iteration im Detail</h2>
|
||||
<p>Clients (PCs, Smartphones) sind reine <em>Stub-Resolver</em>: Sie besitzen keine eigene DNS-Baumlogik, sondern delegieren die Arbeit mit <code>RD=1</code> vollständig an einen vollwertigen Caching-Resolver.</p>
|
||||
|
||||
<pre><code># Abfrage mit dig anzeigen und Flags auslesen:
|
||||
dig +nocmd www.it-praxisportal.de +noall +comments +answer
|
||||
|
||||
# Typischer Header im Output:
|
||||
# ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 48212
|
||||
# ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
|
||||
# ^^^^^^^^
|
||||
# qr = Antwort, rd = Rekursion angefordert, ra = Rekursion vom Server ausgeführt</code></pre>
|
||||
</section>
|
||||
|
||||
<!-- 2. THEORIE: OPEN RESOLVER RISIKO -->
|
||||
<section class="card">
|
||||
<h2>🛡️ 2. Das Open-Resolver-Sicherheitsrisiko</h2>
|
||||
<p>Wenn ein rekursiver Nameserver öffentlich im Internet steht und Anfragen von beliebigen IP-Adressen (<code>any</code>) beantwortet, wird er binnen kürzester Zeit für <strong>DNS Amplification DDoS</strong> missbraucht:</p>
|
||||
|
||||
<div class="box box-warnung">
|
||||
<div class="box-title">⚠️ BSI-Warnung: Open Resolver schließen!</div>
|
||||
<p>Ein Angreifer sendet ein gefälschtes 60-Byte UDP-Paket mit der Absender-IP seines Opfers an deinen Open Resolver. Die Antwort (z.B. DNSSEC-Schlüssel) ist 3.000 Bytes groß (Verstärkungsfaktor 50x!). Dein Server bombardiert das Opfer mit unangefordertem Traffic.</p>
|
||||
</div>
|
||||
|
||||
<p><strong>Die Lösung in BIND9 (<code>named.conf.options</code>):</strong></p>
|
||||
<pre><code>options {
|
||||
recursion yes;
|
||||
allow-query { 192.168.0.0/16; 10.0.0.0/8; localhost; }; # Nur internes Netz!
|
||||
forwarders { 1.1.1.1; 9.9.9.9; };
|
||||
forward only;
|
||||
};</code></pre>
|
||||
</section>
|
||||
|
||||
<!-- 3. CHEAT-SHEET -->
|
||||
<section class="card">
|
||||
<h2>📑 3. Cheat-Sheet: DNS-Header-Flags</h2>
|
||||
<div style="overflow-x: auto;">
|
||||
<table style="width: 100%; border-collapse: collapse; font-size: 0.9rem; text-align: left;">
|
||||
<thead>
|
||||
<tr style="border-bottom: 2px solid var(--border-color); background: var(--bg-subtle);">
|
||||
<th style="padding: 10px;">Flag</th>
|
||||
<th style="padding: 10px;">Bezeichnung</th>
|
||||
<th style="padding: 10px;">Bedeutung in der Praxis</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr style="border-bottom: 1px solid var(--border-color);">
|
||||
<td style="padding: 10px;"><code>QR</code></td>
|
||||
<td style="padding: 10px;">Query / Response</td>
|
||||
<td style="padding: 10px;">0 = Anfrage eines Clients; 1 = Antwortpaket eines Servers</td>
|
||||
</tr>
|
||||
<tr style="border-bottom: 1px solid var(--border-color);">
|
||||
<td style="padding: 10px;"><code>AA</code></td>
|
||||
<td style="padding: 10px;">Authoritative Answer</td>
|
||||
<td style="padding: 10px;">1 = Server besitzt die Original-Zone (kein Cache-Zwischenergebnis)</td>
|
||||
</tr>
|
||||
<tr style="border-bottom: 1px solid var(--border-color);">
|
||||
<td style="padding: 10px;"><code>TC</code></td>
|
||||
<td style="padding: 10px;">Truncated</td>
|
||||
<td style="padding: 10px;">1 = Antwort passte nicht in 512B UDP; Client muss per TCP/53 wiederholen</td>
|
||||
</tr>
|
||||
<tr style="border-bottom: 1px solid var(--border-color);">
|
||||
<td style="padding: 10px;"><code>RD</code></td>
|
||||
<td style="padding: 10px;">Recursion Desired</td>
|
||||
<td style="padding: 10px;">1 = Client bittet den Server, die Auflösung komplett zu übernehmen</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 10px;"><code>RA</code></td>
|
||||
<td style="padding: 10px;">Recursion Available</td>
|
||||
<td style="padding: 10px;">1 = Server beherrscht Rekursion und bietet diesen Dienst aktiv an</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 4. AUFGABEN CHECKLISTE -->
|
||||
<section class="card">
|
||||
<h2>🎯 Aufgaben & Checkliste</h2>
|
||||
<h2>🎯 Aufgaben & Checkliste in <code>aufgabe.sh</code></h2>
|
||||
<div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t1">
|
||||
<label class="task-label" for="t1"><strong>TODO 1:</strong> Flags RD (Recursion Desired) und RA (Recursion Available) prüfen</label>
|
||||
</div><div class="task-item">
|
||||
<label class="task-label" for="t1"><strong>TODO 1:</strong> <code>parse_dns_flags(flags)</code>: Analyse von QR, AA, TC, RD, RA umsetzen.</label>
|
||||
</div>
|
||||
<div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t2">
|
||||
<label class="task-label" for="t2"><strong>TODO 2:</strong> Iterative Namensauflösung schrittweise simulieren</label>
|
||||
</div><div class="task-item">
|
||||
<label class="task-label" for="t2"><strong>TODO 2:</strong> <code>simulate_iterative_steps(domain)</code>: Die 4 Phasen der Iteration protokollieren.</label>
|
||||
</div>
|
||||
<div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t3">
|
||||
<label class="task-label" for="t3"><strong>TODO 3:</strong> Resolver-Cache und TTL-Abzug nachvollziehen</label>
|
||||
</div><div class="task-item">
|
||||
<label class="task-label" for="t3"><strong>TODO 3:</strong> <code>detect_open_resolver(flags, network)</code>: Schwachstellenanalyse für offene Rekursion.</label>
|
||||
</div>
|
||||
<div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t4">
|
||||
<label class="task-label" for="t4"><strong>TODO 4:</strong> DNS-Forwarder für Unternehmensnetze konfigurieren</label>
|
||||
<label class="task-label" for="t4"><strong>TODO 4:</strong> <code>generate_forwarder_config(...)</code>: Sichere BIND-Options-Konfiguration schreiben.</label>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 25px; display: flex; gap: 12px; flex-wrap: wrap;">
|
||||
<a href="../../../workspace.html?course=dns_records&track=track_1_dns_architektur&chapter=02_rekursive_vs_iterative_abfragen" class="btn" style="background: #0284c7; color: #fff; font-weight: 700;">💻 In Web-IDE öffnen →</a>
|
||||
<a href="../03_ports_udp_tcp_und_dns_paketaufbau/index.html" class="btn btn-secondary">Weiter zu Modul →</a>
|
||||
<a href="../../../workspace.html?course=dns_records&track=track_1_dns_architektur&chapter=02_rekursive_vs_iterative_abfragen" class="btn" style="background: #1d4ed8; color: #fff; font-weight: 700;">In Web-IDE öffnen →</a>
|
||||
<a href="../03_ports_udp_tcp_und_dns_paketaufbau/index.html" class="btn btn-secondary">Weiter zu Modul 03 →</a>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
|
|
|||
|
|
@ -1,97 +1,93 @@
|
|||
#!/usr/bin/env bash
|
||||
# ==============================================================================
|
||||
# MUSTERLÖSUNG: DNS 02: Rekursive vs. Iterative Abfragen
|
||||
# MUSTERLÖSUNG: DNS 02: REKURSIVE VS. ITERATIVE ABFRAGEN & DNS-FLAGS
|
||||
# ==============================================================================
|
||||
|
||||
trace_dns_resolution() {
|
||||
local domain="${1:-it-praxisportal.de}"
|
||||
local target_dir="${2:-.}"
|
||||
parse_dns_flags() {
|
||||
local raw="$1"
|
||||
local lower=$(echo "$raw" | tr '[:upper:]' '[:lower:]')
|
||||
|
||||
echo "▶ Starte professionelle DNS-Operation für $domain..."
|
||||
local qr="0_QUERY"
|
||||
local aa="NO"
|
||||
local tc="NO"
|
||||
local rd="NO"
|
||||
local ra="NO"
|
||||
|
||||
case "02_rekursive_vs_iterative_abfragen" in
|
||||
*01_dns_hierarchie*)
|
||||
echo "Host: www | SLD: it-praxisportal | TLD: de | Root: ."
|
||||
;;
|
||||
*02_rekursive*)
|
||||
echo "Simulation Iteration: Root (.) -> de. -> ns1.it-praxisportal.de -> 188.245.100.5"
|
||||
;;
|
||||
*03_ports_udp*)
|
||||
echo "Protocol: UDP/53 (Standard) | Fallback: TCP/53 | EDNS0 Buffer: 4096 bytes"
|
||||
;;
|
||||
*04_ttl*)
|
||||
echo "TTL Current: 86400s | Migration Planned TTL: 300s | Neg Cache: 3600s"
|
||||
;;
|
||||
*05_a_und_aaaa*)
|
||||
cat << 'ZONE' > "$target_dir/zone_records.txt"
|
||||
@ IN A 188.245.100.5
|
||||
@ IN AAAA 2a01:4f8:c010:d::1
|
||||
www IN A 188.245.100.5
|
||||
ZONE
|
||||
;;
|
||||
*06_cname*)
|
||||
echo "api IN CNAME app.it-praxisportal.de." > "$target_dir/cname_record.txt"
|
||||
;;
|
||||
*07_soa*)
|
||||
cat << 'ZONE' > "$target_dir/soa_record.txt"
|
||||
@ IN SOA ns1.it-praxisportal.de. hostmaster.it-praxisportal.de. (
|
||||
2026091201 ; Serial YYYYMMDDNN
|
||||
7200 ; Refresh (2h)
|
||||
3600 ; Retry (1h)
|
||||
1209600 ; Expire (2w)
|
||||
3600 ; Negative Cache TTL (1h)
|
||||
)
|
||||
ZONE
|
||||
;;
|
||||
*08_ns_und_ptr*)
|
||||
echo "100.245.188.in-addr.arpa. IN PTR mail.it-praxisportal.de." > "$target_dir/ptr_record.txt"
|
||||
;;
|
||||
*09_mx*)
|
||||
echo "@ IN MX 10 mail.it-praxisportal.de." > "$target_dir/mx_record.txt"
|
||||
echo "@ IN MX 20 backup-mail.it-praxisportal.de." >> "$target_dir/mx_record.txt"
|
||||
;;
|
||||
*10_txt_records*)
|
||||
echo '@ IN TXT "v=spf1 mx ip4:188.245.100.5 include:_spf.google.com -all"' > "$target_dir/spf_record.txt"
|
||||
;;
|
||||
*11_dkim*)
|
||||
echo 's1._domainkey IN TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz..."' > "$target_dir/dkim_record.txt"
|
||||
;;
|
||||
*12_dmarc*)
|
||||
echo '_dmarc IN TXT "v=DMARC1; p=reject; rua=mailto:dmarc-reports@it-praxisportal.de; pct=100; aspf=s"' > "$target_dir/dmarc_record.txt"
|
||||
;;
|
||||
*13_srv*)
|
||||
echo '_ldap._tcp.dc._msdcs.corp IN SRV 0 100 389 dc01.corp.it-praxisportal.de.' > "$target_dir/srv_record.txt"
|
||||
;;
|
||||
*14_caa*)
|
||||
echo '@ IN CAA 0 issue "letsencrypt.org"' > "$target_dir/caa_record.txt"
|
||||
echo '@ IN CAA 0 iodef "mailto:security@it-praxisportal.de"' >> "$target_dir/caa_record.txt"
|
||||
;;
|
||||
*15_dns_troubleshooting*)
|
||||
echo "STATUS: NOERROR | ANSWER: 1 | FLAGS: qr aa rd ra"
|
||||
;;
|
||||
*16_master*)
|
||||
cat << 'REPORT' > "$target_dir/dns_audit_report.json"
|
||||
{
|
||||
"domain": "it-praxisportal.de",
|
||||
"audit_status": "PASSED",
|
||||
"score": 100,
|
||||
"soa_serial_valid": true,
|
||||
"ns_redundancy": 2,
|
||||
"email_security": {
|
||||
"spf": "valid (-all)",
|
||||
"dkim": "present",
|
||||
"dmarc": "enforced (p=reject)"
|
||||
},
|
||||
"dnssec": "active"
|
||||
[[ "$lower" =~ (^|[[:space:]])qr([[:space:]]|$) ]] && qr="1_RESPONSE"
|
||||
[[ "$lower" =~ (^|[[:space:]])aa([[:space:]]|$) ]] && aa="YES"
|
||||
[[ "$lower" =~ (^|[[:space:]])tc([[:space:]]|$) ]] && tc="YES"
|
||||
[[ "$lower" =~ (^|[[:space:]])rd([[:space:]]|$) ]] && rd="YES"
|
||||
[[ "$lower" =~ (^|[[:space:]])ra([[:space:]]|$) ]] && ra="YES"
|
||||
|
||||
echo "QR=$qr"
|
||||
echo "AA=$aa"
|
||||
echo "TC=$tc"
|
||||
echo "RD=$rd"
|
||||
echo "RA=$ra"
|
||||
return 0
|
||||
}
|
||||
REPORT
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "✅ DNS-Operation erfolgreich abgeschlossen."
|
||||
simulate_iterative_steps() {
|
||||
local domain="$1"
|
||||
[ -z "$domain" ] && return 1
|
||||
|
||||
local clean="${domain%.}"
|
||||
IFS='.' read -ra labels <<< "$clean"
|
||||
local count=${#labels[@]}
|
||||
|
||||
local tld="${labels[-1]}"
|
||||
local sld="${labels[-2]}"
|
||||
|
||||
echo "SCHRITT 1: Query an Root (.) -> Referral zu TLD Nameserver (${tld}.)"
|
||||
echo "SCHRITT 2: Query an TLD Server (${tld}.) -> Referral zu autoritativem Nameserver (${sld}.${tld}.)"
|
||||
echo "SCHRITT 3: Query an autoritativen Server (${sld}.${tld}.) -> Antwort erhalten (AA=1)"
|
||||
echo "SCHRITT 4: Resolver speichert Ergebnis im Cache und sendet Antwort an Client (RA=1)"
|
||||
return 0
|
||||
}
|
||||
|
||||
detect_open_resolver() {
|
||||
local flags="$1"
|
||||
local network="$2"
|
||||
local lower=$(echo "$flags" | tr '[:upper:]' '[:lower:]')
|
||||
|
||||
if [[ ! "$lower" =~ (^|[[:space:]])ra([[:space:]]|$) ]]; then
|
||||
echo "INFO: Reiner autoritativer Server (keine Rekursion)."
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ "$network" = "0.0.0.0/0" ] || [ "$network" = "any" ]; then
|
||||
echo "SECURITY_ALERT: Open Resolver erkannt! Rekursion aus 0.0.0.0/0 muss gesperrt werden."
|
||||
return 1
|
||||
else
|
||||
echo "SECURE: Rekursiver Resolver auf internes Netz beschränkt."
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
generate_forwarder_config() {
|
||||
local out_file="$1"
|
||||
local ip1="$2"
|
||||
local ip2="$3"
|
||||
|
||||
[ -z "$out_file" ] || [ -z "$ip1" ] || [ -z "$ip2" ] && return 1
|
||||
mkdir -p "$(dirname "$out_file")" 2>/dev/null
|
||||
|
||||
cat << EOF > "$out_file"
|
||||
options {
|
||||
directory "/var/cache/bind";
|
||||
recursion yes;
|
||||
allow-query { 192.168.0.0/16; 10.0.0.0/8; localhost; };
|
||||
forwarders {
|
||||
${ip1};
|
||||
${ip2};
|
||||
};
|
||||
forward only;
|
||||
dnssec-validation auto;
|
||||
};
|
||||
EOF
|
||||
return 0
|
||||
}
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
trace_dns_resolution "$@"
|
||||
parse_dns_flags "$@"
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -1,36 +1,103 @@
|
|||
#!/usr/bin/env bash
|
||||
# ==============================================================================
|
||||
# TESTSUITE: DNS 02: Rekursive vs. Iterative Abfragen
|
||||
# 🧪 TESTSUITE: DNS 02 - REKURSIVE VS. ITERATIVE ABFRAGEN
|
||||
# ==============================================================================
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
BLUE='\033[0;34m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
TARGET_SCRIPT="${1:-aufgabe.sh}"
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
TOTAL_TESTS=0
|
||||
PASSED_TESTS=0
|
||||
FAILED_TESTS=0
|
||||
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
echo -e "${BLUE}🧪 TESTSUITE: DNS 02: Rekursive vs. Iterative Abfragen${NC}"
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
echo -e "${BLUE} Ziel-Skript: ${YELLOW}${TARGET_SCRIPT}${NC}"
|
||||
echo -e "${BLUE}================================================================${NC}\n"
|
||||
|
||||
TEST_DIR=$(mktemp -d /tmp/dns_test_XXXXXX)
|
||||
trap 'rm -rf "$TEST_DIR"' EXIT
|
||||
if [ ! -f "$SCRIPT_DIR/$TARGET_SCRIPT" ]; then
|
||||
echo -e "${RED}❌ Fehler: Datei '$TARGET_SCRIPT' nicht gefunden!${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
source "$SCRIPT_DIR/$TARGET_SCRIPT"
|
||||
|
||||
# Führe Funktion aus
|
||||
trace_dns_resolution "example.com" "$TEST_DIR"
|
||||
RET=$?
|
||||
run_test() {
|
||||
local name="$1"
|
||||
local cmd="$2"
|
||||
((TOTAL_TESTS++))
|
||||
echo -e "▶ Test ${TOTAL_TESTS}: ${name}"
|
||||
if eval "$cmd"; then
|
||||
echo -e " ${GREEN}✓ BESTANDEN${NC}\n"
|
||||
((PASSED_TESTS++))
|
||||
else
|
||||
echo -e " ${RED}✗ FEHLGESCHLAGEN${NC}\n"
|
||||
((FAILED_TESTS++))
|
||||
fi
|
||||
}
|
||||
|
||||
if [ $RET -eq 0 ]; then
|
||||
echo -e "${GREEN}✓ Test 1: Funktion trace_dns_resolution existiert und gibt 0 zurück.${NC}"
|
||||
echo -e "${GREEN}✓ Test 2: DNS Resource Records / Syntax validiert.${NC}"
|
||||
echo -e "${GREEN}✓ Test 3: Teilziele 1 bis 4 erfolgreich abgeschlossen.${NC}"
|
||||
echo -e "${GREEN}🎉 ERFOLG: Alle DNS-Prüfungen in DNS 02: Rekursive vs. Iterative Abfragen bestanden!${NC}"
|
||||
# TEST 1: Flag-Parsing (Response, autoritativ, keine Rekursion)
|
||||
run_test "parse_dns_flags: qr aa (Authoritative Answer)" '
|
||||
OUT=$(parse_dns_flags "qr aa")
|
||||
echo "$OUT" | grep -q "QR=1_RESPONSE" && \
|
||||
echo "$OUT" | grep -q "AA=YES" && \
|
||||
echo "$OUT" | grep -q "RA=NO"
|
||||
'
|
||||
|
||||
# TEST 2: Flag-Parsing (Query mit Rekursionswunsch)
|
||||
run_test "parse_dns_flags: rd (Recursion Desired Query)" '
|
||||
OUT=$(parse_dns_flags "rd")
|
||||
echo "$OUT" | grep -q "QR=0_QUERY" && \
|
||||
echo "$OUT" | grep -q "RD=YES" && \
|
||||
echo "$OUT" | grep -q "AA=NO"
|
||||
'
|
||||
|
||||
# TEST 3: Iterations-Simulation
|
||||
run_test "simulate_iterative_steps für shop.kunde.de" '
|
||||
OUT=$(simulate_iterative_steps "shop.kunde.de")
|
||||
echo "$OUT" | grep -q "SCHRITT 1:.*Root.*de\." && \
|
||||
echo "$OUT" | grep -q "SCHRITT 2:.*TLD.*kunde\.de\." && \
|
||||
echo "$OUT" | grep -q "SCHRITT 3:.*autoritativen.*AA=1" && \
|
||||
echo "$OUT" | grep -q "SCHRITT 4:.*Cache.*RA=1"
|
||||
'
|
||||
|
||||
# TEST 4: Open Resolver Erkennung (Gefahr bei 0.0.0.0/0)
|
||||
run_test "detect_open_resolver schlägt Alarm bei freier Rekursion" '
|
||||
OUT=$(detect_open_resolver "qr rd ra" "0.0.0.0/0")
|
||||
echo "$OUT" | grep -q "SECURITY_ALERT: Open Resolver erkannt"
|
||||
'
|
||||
|
||||
# TEST 5: Sicherer interner Resolver
|
||||
run_test "detect_open_resolver meldet sicher bei privatem Subnetz" '
|
||||
OUT=$(detect_open_resolver "qr rd ra" "192.168.1.0/24")
|
||||
echo "$OUT" | grep -q "SECURE: Rekursiver Resolver"
|
||||
'
|
||||
|
||||
# TEST 6: BIND Forwarder Konfigurationsdatei
|
||||
run_test "generate_forwarder_config erzeugt valides named.conf.options" '
|
||||
TMP_CONF=$(mktemp /tmp/named_conf_XXXXXX)
|
||||
generate_forwarder_config "$TMP_CONF" "1.1.1.1" "8.8.8.8"
|
||||
[ -f "$TMP_CONF" ] && \
|
||||
grep -q "forwarders {" "$TMP_CONF" && \
|
||||
grep -q "1.1.1.1;" "$TMP_CONF" && \
|
||||
grep -q "8.8.8.8;" "$TMP_CONF" && \
|
||||
grep -q "forward only;" "$TMP_CONF"
|
||||
rm -f "$TMP_CONF"
|
||||
'
|
||||
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
if [ "$FAILED_TESTS" -eq 0 ]; then
|
||||
echo -e "${GREEN}🎉 ERFOLG: Alle ${PASSED_TESTS}/${TOTAL_TESTS} Tests in DNS 02 erfolgreich bestanden!${NC}"
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
exit 0
|
||||
else
|
||||
echo -e "${RED}❌ FEHLER: Skript fehlgeschlagen mit Status $RET${NC}"
|
||||
echo -e "${RED}❌ FEHLER: ${FAILED_TESTS} von ${TOTAL_TESTS} Tests fehlgeschlagen.${NC}"
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -1,18 +1,40 @@
|
|||
# DNS 03: UDP vs. TCP Port 53 & EDNS0
|
||||
# DNS 03: UDP vs. TCP Port 53, Truncation & EDNS0
|
||||
|
||||
Willkommen zum Kurs **DNS-Records, Domain Name System & E-Mail-Sicherheit** im IT-Praxisportal!
|
||||
Willkommen zu **Modul 03** des Kurses **DNS-Records, Domain Name System & E-Mail-Sicherheit**!
|
||||
|
||||
## 💡 Das Wichtigste in Kürze
|
||||
- **Ziel:** Transportprotokolle: Port 53 UDP für Standardabfragen (< 512 Bytes), Port 53 TCP für Zonentransfers (AXFR) & DNSSEC.
|
||||
- **Wichtigstes Tool:** `dig +tcp`
|
||||
In diesem Modul erfährst du, warum DNS auf **Port 53** sowohl UDP als auch TCP nutzt, was das historische **512-Byte-UDP-Limit** bedeutet, wie das **TC-Flag (Truncated)** funktioniert und warum **EDNS0 (RFC 6891)** für modernes DNSSEC unverzichtbar ist.
|
||||
|
||||
## 🎯 Deine Aufgaben (Checkliste)
|
||||
1. **Teilziel 1:** DNS-Anfrage über UDP Port 53 versenden
|
||||
2. **Teilziel 2:** Truncation Flag (TC-Bit) und TCP-Fallback erkennen
|
||||
3. **Teilziel 3:** EDNS0 (Extension Mechanisms for DNS) Puffergröße auf 4096 Byte setzen
|
||||
4. **Teilziel 4:** TCP-Verbindung für Zonentransfers absichern
|
||||
---
|
||||
|
||||
## 💡 1. Das Wichtigste in Kürze
|
||||
|
||||
### Port 53: Warum zwei Protokolle?
|
||||
1. **UDP/53 (User Datagram Protocol):**
|
||||
- Standard für DNS-Abfragen und normale Antworten.
|
||||
- Schnell, geringer Overhead (kein 3-Wege-Handshake wie bei TCP).
|
||||
- Historisches Limit nach RFC 1035: **Maximal 512 Bytes** Payload pro UDP-Paket.
|
||||
2. **TCP/53 (Transmission Control Protocol):**
|
||||
- **Zonentransfers (AXFR / IXFR):** Die Replikation vollständiger Zonen zwischen Master- und Secondary-Nameservern erfordert TCP für garantierte Datenübertragung.
|
||||
- **Fallback bei Truncation:** Passt eine Antwort nicht in den UDP-Puffer, sendet der Server ein Paket mit gesetztem `TC=1` (Truncated) Flag. Der Client wiederholt dieselbe Abfrage unverzüglich über TCP/53.
|
||||
|
||||
### EDNS0: Extension Mechanisms for DNS (RFC 6891)
|
||||
Um ständige TCP-Verbindungsaufbauten bei großen Antworten (z.B. DNSSEC-Signaturen mit >1000 Bytes) zu verhindern, führt EDNS0 eine Pseudo-Resource-Record (`OPT`) in der *Additional Section* ein.
|
||||
Damit signalisiert der Client dem Server: *„Mein UDP-Puffer kann bis zu 4096 (oder 1232) Bytes empfangen!“*
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Deine Aufgaben in `aufgabe.sh`
|
||||
|
||||
1. **TODO 1 (`select_dns_transport`):** Entscheide anhand von Paketgröße, Query-Typ und EDNS0-Status, ob UDP, TCP oder ein Fallback nötig ist.
|
||||
2. **TODO 2 (`parse_section_counts`):** Wertet die 4 Header-Zähler aus (Questions, Answers, Authority, Additional) und ermittelt den Response-Status.
|
||||
3. **TODO 3 (`verify_edns0_in_dig`):** Prüft einen `dig`-Output auf das Vorhandensein der `OPT PSEUDOSECTION` und liest die Puffergröße aus.
|
||||
4. **TODO 4 (`secure_zone_transfers`):** Schützt BIND9 vor unautorisierten Zonentransfers (`allow-transfer`).
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Tests ausführen
|
||||
|
||||
Führe im Terminal folgenden Befehl aus:
|
||||
```bash
|
||||
bash test_aufgabe.sh
|
||||
```
|
||||
|
|
|
|||
|
|
@ -1,21 +1,119 @@
|
|||
#!/usr/bin/env bash
|
||||
# ==============================================================================
|
||||
# 🌐 DNS Netzwerkprotokoll - DNS 03: UDP vs. TCP Port 53 & EDNS0
|
||||
# 🌐 DNS 03: UDP VS. TCP PORT 53, TRUNCATION & EDNS0
|
||||
# ==============================================================================
|
||||
#
|
||||
# TRANSPORTPROTOKOLLE IM DNS:
|
||||
# - Standard-Port: Port 53 (sowohl UDP als auch TCP).
|
||||
#
|
||||
# WANN UDP?
|
||||
# - Für normale Anfragen und kurze Antworten (Standard).
|
||||
# - Extrem schnell, verbindungslos, minimaler Netzwerk-Overhead.
|
||||
# - Historisches Limit nach RFC 1035: Maximal 512 Bytes Nutzlast pro UDP-Paket!
|
||||
#
|
||||
# WANN TCP?
|
||||
# 1. Zonentransfers (AXFR / IXFR):
|
||||
# Die Replikation kompletter Zonendateien von Master- auf Slave-Server erfolgt
|
||||
# stets über TCP/53 (zuverlässiger Datenstrom).
|
||||
# 2. Truncation Fallback:
|
||||
# Wenn eine Antwort größer als 512 Bytes ist (ohne EDNS0), setzt der Server
|
||||
# das TC-Flag (Truncated = 1). Der Client muss die Anfrage daraufhin sofort
|
||||
# über TCP/53 wiederholen!
|
||||
#
|
||||
# EDNS0 (RFC 6891 - Extension Mechanisms for DNS):
|
||||
# - Erweitert den UDP-Puffer auf typischerweise 1232 bis 4096 Bytes über einen
|
||||
# speziellen OPT-Pseudorecord in der Additional Section.
|
||||
# - Essentiell für DNSSEC, da kryptografische Signaturen (RRSIG) oft >512B groß sind.
|
||||
# ==============================================================================
|
||||
|
||||
# 🎯 TEILZIEL 1: DNS-Anfrage über UDP Port 53 versenden
|
||||
# 🎯 TEILZIEL 2: Truncation Flag (TC-Bit) und TCP-Fallback erkennen
|
||||
# 🎯 TEILZIEL 3: EDNS0 (Extension Mechanisms for DNS) Puffergröße auf 4096 Byte setzen
|
||||
# 🎯 TEILZIEL 4: TCP-Verbindung für Zonentransfers absichern
|
||||
# 🎯 TEILZIEL 1 (TODO 1): Transportprotokoll dynamisch auswählen
|
||||
# Funktion: select_dns_transport "$packet_size" "$query_type" "$edns_supported"
|
||||
# Parameter $1: Paketgröße in Bytes (Zahl, z.B. 450 oder 1200)
|
||||
# Parameter $2: Query-Typ (z.B. "A", "MX", "AXFR", "TXT")
|
||||
# Parameter $3: EDNS0-Unterstützung ("true" oder "false")
|
||||
# Regeln:
|
||||
# - Wenn query_type == "AXFR" oder "IXFR" -> Ausgabe: "TCP (Grund: Zonentransfer erfordert zwingend TCP)"
|
||||
# - Wenn packet_size <= 512 -> Ausgabe: "UDP (Grund: Paket passt in Standard-512B-Puffer)"
|
||||
# - Wenn packet_size > 512 UND edns_supported == "true" UND packet_size <= 4096 ->
|
||||
# Ausgabe: "UDP_EDNS0 (Grund: EDNS0 erweitert UDP-Puffer auf bis zu 4096 Bytes)"
|
||||
# - Wenn packet_size > 512 UND edns_supported == "false" ->
|
||||
# Ausgabe: "TCP_TRUNCATED (Grund: TC=1 gesetzt, Fallback auf TCP erforderlich)"
|
||||
select_dns_transport() {
|
||||
local size="$1"
|
||||
local qtype="$2"
|
||||
local edns="$3"
|
||||
# TODO 1: Implementiere die Protokollauswahl
|
||||
return 0
|
||||
}
|
||||
|
||||
inspect_dns_transport() {
|
||||
local domain="${1:-it-praxisportal.de}"
|
||||
echo "▶ Analysiere DNS-Konfiguration für $domain..."
|
||||
# 🎯 TEILZIEL 2 (TODO 2): DNS-Header Sektionszähler parsen
|
||||
# Funktion: parse_section_counts "$qdcount" "$ancount" "$nscount" "$arcount"
|
||||
# Parameter $1: QDCOUNT (Anzahl Fragen / Questions)
|
||||
# Parameter $2: ANCOUNT (Anzahl Antworten / Answers)
|
||||
# Parameter $3: NSCOUNT (Anzahl autoritativer Nameserver / Authority)
|
||||
# Parameter $4: ARCOUNT (Anzahl Zusatzinformationen / Additional)
|
||||
# Anforderungen:
|
||||
# - Formatiere die Ausgabe als übersichtlichen Report:
|
||||
# QUESTIONS=<qdcount>
|
||||
# ANSWERS=<ancount>
|
||||
# AUTHORITY=<nscount>
|
||||
# ADDITIONAL=<arcount>
|
||||
# STATUS=<EMPTY_RESPONSE | HAS_ANSWERS | REFERRAL_ONLY>
|
||||
# Status-Regeln:
|
||||
# - Wenn ancount > 0 -> "HAS_ANSWERS"
|
||||
# - Wenn ancount == 0 UND nscount > 0 -> "REFERRAL_ONLY"
|
||||
# - Wenn ancount == 0 UND nscount == 0 -> "EMPTY_RESPONSE"
|
||||
parse_section_counts() {
|
||||
local qd="$1"
|
||||
local an="$2"
|
||||
local ns="$3"
|
||||
local ar="$4"
|
||||
# TODO 2: Parse die Zähler und bestimme den Status
|
||||
return 0
|
||||
}
|
||||
|
||||
# TODO: Implementiere die geforderten DNS-Prüfungen oder Record-Erstellungen
|
||||
return 1
|
||||
# 🎯 TEILZIEL 3 (TODO 3): EDNS0-Prüfung aus dig-Output
|
||||
# Funktion: verify_edns0_in_dig "$dig_output_file"
|
||||
# Parameter $1: Pfad zu einer Textdatei mit einer `dig`-Ausgabe
|
||||
# Anforderungen:
|
||||
# - Suche in der Datei nach der Zeile: ";; OPT PSEUDOSECTION:"
|
||||
# - Prüfe, ob "EDNS: version: 0" und "udp:" vorhanden sind.
|
||||
# - Extrahiere die Puffergröße (z.B. "4096" oder "1232" hinter "udp:").
|
||||
# - Ausgabe wenn vorhanden: "EDNS0_ACTIVE: Puffergröße <size> Bytes" und return 0.
|
||||
# - Ausgabe wenn fehlt: "EDNS0_INACTIVE: Kein OPT Pseudo-Record gefunden (512B Limit)" und return 1.
|
||||
verify_edns0_in_dig() {
|
||||
local file="$1"
|
||||
# TODO 3: Prüfe den dig-Output auf EDNS0
|
||||
return 0
|
||||
}
|
||||
|
||||
# 🎯 TEILZIEL 4 (TODO 4): Zonentransfer-Sperre in BIND absichern
|
||||
# Funktion: secure_zone_transfers "$zone_name" "$allowed_slave_ip" "$output_file"
|
||||
# Parameter $1: Zonenname (z.B. "it-praxisportal.de")
|
||||
# Parameter $2: Erlaubte Slave-IP (z.B. "192.168.1.55")
|
||||
# Parameter $3: Zieldatei (z.B. "/etc/bind/named.conf.local")
|
||||
# Hintergrund:
|
||||
# Ein ungeschützter Nameserver erlaubt jedem Internetnutzer den Download der kompletten
|
||||
# Zonendatei via `dig AXFR @ns1.domain.de domain.de`. Dies verrät alle internen Server!
|
||||
# Anforderungen:
|
||||
# - Schreibe die Zonendefinition in die Zieldatei:
|
||||
# zone "<zone_name>" {
|
||||
# type master;
|
||||
# file "/var/lib/bind/db.<zone_name>";
|
||||
# allow-transfer { <allowed_slave_ip>; };
|
||||
# };
|
||||
secure_zone_transfers() {
|
||||
local zone="$1"
|
||||
local slave_ip="$2"
|
||||
local out_file="$3"
|
||||
# TODO 4: Erzeuge die abgesicherte BIND-Zonendeklaration
|
||||
return 0
|
||||
}
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
inspect_dns_transport "$@"
|
||||
echo "=== Test Teilziel 1 ==="
|
||||
select_dns_transport 700 "TXT" "false"
|
||||
echo ""
|
||||
echo "=== Test Teilziel 2 ==="
|
||||
parse_section_counts 1 2 0 1
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
<link rel="icon" type="image/svg+xml" href="../../../assets/favicon.svg">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>DNS 03: UDP vs. TCP Port 53 & EDNS0</title>
|
||||
<title>DNS 03: UDP vs. TCP Port 53 & EDNS0</title>
|
||||
<link rel="stylesheet" href="../../../assets/style.css">
|
||||
</head>
|
||||
<body>
|
||||
|
|
@ -12,46 +12,101 @@
|
|||
<div class="app-main">
|
||||
<header>
|
||||
<div class="header-container">
|
||||
<div class="header-badge">🌐 DNS Netzwerkprotokoll</div>
|
||||
<h1>DNS 03: UDP vs. TCP Port 53 & EDNS0</h1>
|
||||
<p>Transportprotokolle: Port 53 UDP für Standardabfragen (< 512 Bytes), Port 53 TCP für Zonentransfers (AXFR) & DNSSEC.</p>
|
||||
<div class="header-badge">🌐 DNS & Infrastruktur</div>
|
||||
<h1>DNS 03: UDP vs. TCP Port 53, Truncation & EDNS0</h1>
|
||||
<p>Lerne, wann DNS von UDP auf TCP umschaltet, wie Truncation (TC=1) funktioniert und warum EDNS0 den Puffer auf 4096 Bytes erweitert.</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="container">
|
||||
<!-- 1. THEORIE: UDP VS TCP -->
|
||||
<section class="card">
|
||||
<h2>💡 Theorie & Praxiskonzept</h2>
|
||||
<p>Transportprotokolle: Port 53 UDP für Standardabfragen (< 512 Bytes), Port 53 TCP für Zonentransfers (AXFR) & DNSSEC.</p>
|
||||
<div class="code-container">
|
||||
<div class="code-header">
|
||||
<span>DNS Command & Syntax Reference</span>
|
||||
<span>Tool: dig +tcp</span>
|
||||
</div>
|
||||
<pre><code># Wichtige Befehle & Zonensyntax:
|
||||
dig dig +tcp
|
||||
named-checkzone example.com /etc/bind/zones/db.example.com</code></pre>
|
||||
<h2>⚡ 1. Port 53: Wann UDP, wann TCP?</h2>
|
||||
<p>Ein weit verbreiteter Irrglaube ist, DNS laufe ausschließlich über UDP. In der Praxis benötigt jeder vollwertige DNS-Server <strong>beide Transportprotokolle auf Port 53</strong>:</p>
|
||||
|
||||
<pre><code># 1. Normale Abfrage über UDP (Standard):
|
||||
dig +bufsize=512 www.it-praxisportal.de
|
||||
|
||||
# 2. Explizite Abfrage über TCP erzwingen (+tcp):
|
||||
dig +tcp www.it-praxisportal.de
|
||||
|
||||
# 3. Zonentransfer (AXFR) läuft IMMER über TCP:
|
||||
dig AXFR @ns1.it-praxisportal.de it-praxisportal.de</code></pre>
|
||||
</section>
|
||||
|
||||
<!-- 2. THEORIE: EDNS0 BUFFER -->
|
||||
<section class="card">
|
||||
<h2>📦 2. Das 512-Byte Limit & EDNS0 (RFC 6891)</h2>
|
||||
<p>Historisch passte in Ethernet-Frames (unter Berücksichtigung des minimalen IPv4-MTU von 576 Bytes) nur ein 512-Byte UDP-Payload. Mit der Einführung von DNSSEC (kryptografische 2048-Bit RSA-Schlüssel) reichte dieser Platz nicht mehr aus.</p>
|
||||
|
||||
<div class="box box-analogie">
|
||||
<div class="box-title">📦 Analogie: Der Briefumschlag und das Paket</div>
|
||||
<p><strong>Standard UDP:</strong> Ein Standard-Briefumschlag (512 Bytes). Passt der Vertrag nicht hinein, schneidet der Postbote die Seiten ab, klebt den Stempel <code>TC=1 (Truncated)</code> drauf und sagt: <em>„Komm bitte mit einem Paketwagen (TCP) wieder!“</em></p>
|
||||
<p><strong>EDNS0:</strong> Mit EDNS0 schreibt der Empfänger auf den Umschlag: <em>„Ich habe einen großen Briefkasten (4096 Bytes). Du kannst mir dicke Briefe per Standard-Post senden!“</em></p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 3. CHEAT-SHEET -->
|
||||
<section class="card">
|
||||
<h2>🎯 Aufgaben & Checkliste</h2>
|
||||
<h2>📑 3. Cheat-Sheet: Transport-Entscheidungsmatrix</h2>
|
||||
<div style="overflow-x: auto;">
|
||||
<table style="width: 100%; border-collapse: collapse; font-size: 0.9rem; text-align: left;">
|
||||
<thead>
|
||||
<tr style="border-bottom: 2px solid var(--border-color); background: var(--bg-subtle);">
|
||||
<th style="padding: 10px;">Szenario</th>
|
||||
<th style="padding: 10px;">Protokoll</th>
|
||||
<th style="padding: 10px;">Grund & Verhalten</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr style="border-bottom: 1px solid var(--border-color);">
|
||||
<td style="padding: 10px;">Standard-Query (≤ 512 Bytes)</td>
|
||||
<td style="padding: 10px;"><code>UDP / 53</code></td>
|
||||
<td style="padding: 10px;">Maximale Performance ohne TCP-Handshake</td>
|
||||
</tr>
|
||||
<tr style="border-bottom: 1px solid var(--border-color);">
|
||||
<td style="padding: 10px;">Antwort > 512 Bytes (ohne EDNS0)</td>
|
||||
<td style="padding: 10px;"><code>UDP -> TCP</code></td>
|
||||
<td style="padding: 10px;">Server setzt <code>TC=1</code>; Client wiederholt per TCP</td>
|
||||
</tr>
|
||||
<tr style="border-bottom: 1px solid var(--border-color);">
|
||||
<td style="padding: 10px;">Antwort > 512B (mit EDNS0 ≤ 4096B)</td>
|
||||
<td style="padding: 10px;"><code>UDP (EDNS0)</code></td>
|
||||
<td style="padding: 10px;">Erweiterter Puffer via OPT Pseudo-Record</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 10px;">Zonentransfer (AXFR / IXFR)</td>
|
||||
<td style="padding: 10px;"><code>TCP / 53</code></td>
|
||||
<td style="padding: 10px;">Zwingend TCP wegen Zuverlässigkeit & Datenmenge</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 4. AUFGABEN CHECKLISTE -->
|
||||
<section class="card">
|
||||
<h2>🎯 Aufgaben & Checkliste in <code>aufgabe.sh</code></h2>
|
||||
<div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t1">
|
||||
<label class="task-label" for="t1"><strong>TODO 1:</strong> DNS-Anfrage über UDP Port 53 versenden</label>
|
||||
</div><div class="task-item">
|
||||
<label class="task-label" for="t1"><strong>TODO 1:</strong> <code>select_dns_transport(...)</code>: Protokoll dynamisch ermitteln (UDP, TCP, EDNS0).</label>
|
||||
</div>
|
||||
<div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t2">
|
||||
<label class="task-label" for="t2"><strong>TODO 2:</strong> Truncation Flag (TC-Bit) und TCP-Fallback erkennen</label>
|
||||
</div><div class="task-item">
|
||||
<label class="task-label" for="t2"><strong>TODO 2:</strong> <code>parse_section_counts(...)</code>: Header Section Counts und Status auswerten.</label>
|
||||
</div>
|
||||
<div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t3">
|
||||
<label class="task-label" for="t3"><strong>TODO 3:</strong> EDNS0 (Extension Mechanisms for DNS) Puffergröße auf 4096 Byte setzen</label>
|
||||
</div><div class="task-item">
|
||||
<label class="task-label" for="t3"><strong>TODO 3:</strong> <code>verify_edns0_in_dig(...)</code>: OPT Pseudosection in dig-Output validieren.</label>
|
||||
</div>
|
||||
<div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t4">
|
||||
<label class="task-label" for="t4"><strong>TODO 4:</strong> TCP-Verbindung für Zonentransfers absichern</label>
|
||||
<label class="task-label" for="t4"><strong>TODO 4:</strong> <code>secure_zone_transfers(...)</code>: BIND AXFR-Sperre mit <code>allow-transfer</code> schreiben.</label>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 25px; display: flex; gap: 12px; flex-wrap: wrap;">
|
||||
<a href="../../../workspace.html?course=dns_records&track=track_1_dns_architektur&chapter=03_ports_udp_tcp_und_dns_paketaufbau" class="btn" style="background: #0284c7; color: #fff; font-weight: 700;">💻 In Web-IDE öffnen →</a>
|
||||
<a href="../04_ttl_und_dns_propagation/index.html" class="btn btn-secondary">Weiter zu Modul →</a>
|
||||
<a href="../../../workspace.html?course=dns_records&track=track_1_dns_architektur&chapter=03_ports_udp_tcp_und_dns_paketaufbau" class="btn" style="background: #1d4ed8; color: #fff; font-weight: 700;">In Web-IDE öffnen →</a>
|
||||
<a href="../04_ttl_und_dns_propagation/index.html" class="btn btn-secondary">Weiter zu Modul 04 →</a>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
|
|
|||
|
|
@ -1,97 +1,91 @@
|
|||
#!/usr/bin/env bash
|
||||
# ==============================================================================
|
||||
# MUSTERLÖSUNG: DNS 03: UDP vs. TCP Port 53 & EDNS0
|
||||
# MUSTERLÖSUNG: DNS 03: UDP VS. TCP PORT 53, TRUNCATION & EDNS0
|
||||
# ==============================================================================
|
||||
|
||||
inspect_dns_transport() {
|
||||
local domain="${1:-it-praxisportal.de}"
|
||||
local target_dir="${2:-.}"
|
||||
select_dns_transport() {
|
||||
local size="$1"
|
||||
local qtype=$(echo "$2" | tr '[:lower:]' '[:upper:]')
|
||||
local edns=$(echo "$3" | tr '[:upper:]' '[:lower:]')
|
||||
|
||||
echo "▶ Starte professionelle DNS-Operation für $domain..."
|
||||
if [ "$qtype" = "AXFR" ] || [ "$qtype" = "IXFR" ]; then
|
||||
echo "TCP (Grund: Zonentransfer erfordert zwingend TCP)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
case "03_ports_udp_tcp_und_dns_paketaufbau" in
|
||||
*01_dns_hierarchie*)
|
||||
echo "Host: www | SLD: it-praxisportal | TLD: de | Root: ."
|
||||
;;
|
||||
*02_rekursive*)
|
||||
echo "Simulation Iteration: Root (.) -> de. -> ns1.it-praxisportal.de -> 188.245.100.5"
|
||||
;;
|
||||
*03_ports_udp*)
|
||||
echo "Protocol: UDP/53 (Standard) | Fallback: TCP/53 | EDNS0 Buffer: 4096 bytes"
|
||||
;;
|
||||
*04_ttl*)
|
||||
echo "TTL Current: 86400s | Migration Planned TTL: 300s | Neg Cache: 3600s"
|
||||
;;
|
||||
*05_a_und_aaaa*)
|
||||
cat << 'ZONE' > "$target_dir/zone_records.txt"
|
||||
@ IN A 188.245.100.5
|
||||
@ IN AAAA 2a01:4f8:c010:d::1
|
||||
www IN A 188.245.100.5
|
||||
ZONE
|
||||
;;
|
||||
*06_cname*)
|
||||
echo "api IN CNAME app.it-praxisportal.de." > "$target_dir/cname_record.txt"
|
||||
;;
|
||||
*07_soa*)
|
||||
cat << 'ZONE' > "$target_dir/soa_record.txt"
|
||||
@ IN SOA ns1.it-praxisportal.de. hostmaster.it-praxisportal.de. (
|
||||
2026091201 ; Serial YYYYMMDDNN
|
||||
7200 ; Refresh (2h)
|
||||
3600 ; Retry (1h)
|
||||
1209600 ; Expire (2w)
|
||||
3600 ; Negative Cache TTL (1h)
|
||||
)
|
||||
ZONE
|
||||
;;
|
||||
*08_ns_und_ptr*)
|
||||
echo "100.245.188.in-addr.arpa. IN PTR mail.it-praxisportal.de." > "$target_dir/ptr_record.txt"
|
||||
;;
|
||||
*09_mx*)
|
||||
echo "@ IN MX 10 mail.it-praxisportal.de." > "$target_dir/mx_record.txt"
|
||||
echo "@ IN MX 20 backup-mail.it-praxisportal.de." >> "$target_dir/mx_record.txt"
|
||||
;;
|
||||
*10_txt_records*)
|
||||
echo '@ IN TXT "v=spf1 mx ip4:188.245.100.5 include:_spf.google.com -all"' > "$target_dir/spf_record.txt"
|
||||
;;
|
||||
*11_dkim*)
|
||||
echo 's1._domainkey IN TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz..."' > "$target_dir/dkim_record.txt"
|
||||
;;
|
||||
*12_dmarc*)
|
||||
echo '_dmarc IN TXT "v=DMARC1; p=reject; rua=mailto:dmarc-reports@it-praxisportal.de; pct=100; aspf=s"' > "$target_dir/dmarc_record.txt"
|
||||
;;
|
||||
*13_srv*)
|
||||
echo '_ldap._tcp.dc._msdcs.corp IN SRV 0 100 389 dc01.corp.it-praxisportal.de.' > "$target_dir/srv_record.txt"
|
||||
;;
|
||||
*14_caa*)
|
||||
echo '@ IN CAA 0 issue "letsencrypt.org"' > "$target_dir/caa_record.txt"
|
||||
echo '@ IN CAA 0 iodef "mailto:security@it-praxisportal.de"' >> "$target_dir/caa_record.txt"
|
||||
;;
|
||||
*15_dns_troubleshooting*)
|
||||
echo "STATUS: NOERROR | ANSWER: 1 | FLAGS: qr aa rd ra"
|
||||
;;
|
||||
*16_master*)
|
||||
cat << 'REPORT' > "$target_dir/dns_audit_report.json"
|
||||
{
|
||||
"domain": "it-praxisportal.de",
|
||||
"audit_status": "PASSED",
|
||||
"score": 100,
|
||||
"soa_serial_valid": true,
|
||||
"ns_redundancy": 2,
|
||||
"email_security": {
|
||||
"spf": "valid (-all)",
|
||||
"dkim": "present",
|
||||
"dmarc": "enforced (p=reject)"
|
||||
},
|
||||
"dnssec": "active"
|
||||
if [ "$size" -le 512 ]; then
|
||||
echo "UDP (Grund: Paket passt in Standard-512B-Puffer)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ "$size" -gt 512 ] && [ "$edns" = "true" ] && [ "$size" -le 4096 ]; then
|
||||
echo "UDP_EDNS0 (Grund: EDNS0 erweitert UDP-Puffer auf bis zu 4096 Bytes)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ "$size" -gt 512 ] && [ "$edns" = "false" ]; then
|
||||
echo "TCP_TRUNCATED (Grund: TC=1 gesetzt, Fallback auf TCP erforderlich)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "TCP_FALLBACK"
|
||||
return 0
|
||||
}
|
||||
REPORT
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "✅ DNS-Operation erfolgreich abgeschlossen."
|
||||
parse_section_counts() {
|
||||
local qd="${1:-1}"
|
||||
local an="${2:-0}"
|
||||
local ns="${3:-0}"
|
||||
local ar="${4:-0}"
|
||||
|
||||
local status="EMPTY_RESPONSE"
|
||||
if [ "$an" -gt 0 ]; then
|
||||
status="HAS_ANSWERS"
|
||||
elif [ "$ns" -gt 0 ]; then
|
||||
status="REFERRAL_ONLY"
|
||||
fi
|
||||
|
||||
echo "QUESTIONS=$qd"
|
||||
echo "ANSWERS=$an"
|
||||
echo "AUTHORITY=$ns"
|
||||
echo "ADDITIONAL=$ar"
|
||||
echo "STATUS=$status"
|
||||
return 0
|
||||
}
|
||||
|
||||
verify_edns0_in_dig() {
|
||||
local file="$1"
|
||||
[ ! -f "$file" ] && return 1
|
||||
|
||||
if grep -q "OPT PSEUDOSECTION:" "$file"; then
|
||||
local size=$(grep -oE "udp:[[:space:]]*[0-9]+" "$file" | grep -oE "[0-9]+")
|
||||
[ -z "$size" ] && size="4096"
|
||||
echo "EDNS0_ACTIVE: Puffergröße ${size} Bytes"
|
||||
return 0
|
||||
else
|
||||
echo "EDNS0_INACTIVE: Kein OPT Pseudo-Record gefunden (512B Limit)"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
secure_zone_transfers() {
|
||||
local zone="$1"
|
||||
local slave_ip="$2"
|
||||
local out_file="$3"
|
||||
|
||||
[ -z "$zone" ] || [ -z "$slave_ip" ] || [ -z "$out_file" ] && return 1
|
||||
mkdir -p "$(dirname "$out_file")" 2>/dev/null
|
||||
|
||||
cat << EOF > "$out_file"
|
||||
zone "${zone}" {
|
||||
type master;
|
||||
file "/var/lib/bind/db.${zone}";
|
||||
allow-transfer { ${slave_ip}; };
|
||||
};
|
||||
EOF
|
||||
return 0
|
||||
}
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
inspect_dns_transport "$@"
|
||||
select_dns_transport "$@"
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -1,36 +1,107 @@
|
|||
#!/usr/bin/env bash
|
||||
# ==============================================================================
|
||||
# TESTSUITE: DNS 03: UDP vs. TCP Port 53 & EDNS0
|
||||
# 🧪 TESTSUITE: DNS 03 - UDP VS. TCP PORT 53 & EDNS0
|
||||
# ==============================================================================
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
BLUE='\033[0;34m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
TARGET_SCRIPT="${1:-aufgabe.sh}"
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
TOTAL_TESTS=0
|
||||
PASSED_TESTS=0
|
||||
FAILED_TESTS=0
|
||||
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
echo -e "${BLUE}🧪 TESTSUITE: DNS 03: UDP vs. TCP Port 53 & EDNS0${NC}"
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
echo -e "${BLUE} Ziel-Skript: ${YELLOW}${TARGET_SCRIPT}${NC}"
|
||||
echo -e "${BLUE}================================================================${NC}\n"
|
||||
|
||||
TEST_DIR=$(mktemp -d /tmp/dns_test_XXXXXX)
|
||||
trap 'rm -rf "$TEST_DIR"' EXIT
|
||||
if [ ! -f "$SCRIPT_DIR/$TARGET_SCRIPT" ]; then
|
||||
echo -e "${RED}❌ Fehler: Datei '$TARGET_SCRIPT' nicht gefunden!${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
source "$SCRIPT_DIR/$TARGET_SCRIPT"
|
||||
|
||||
# Führe Funktion aus
|
||||
inspect_dns_transport "example.com" "$TEST_DIR"
|
||||
RET=$?
|
||||
run_test() {
|
||||
local name="$1"
|
||||
local cmd="$2"
|
||||
((TOTAL_TESTS++))
|
||||
echo -e "▶ Test ${TOTAL_TESTS}: ${name}"
|
||||
if eval "$cmd"; then
|
||||
echo -e " ${GREEN}✓ BESTANDEN${NC}\n"
|
||||
((PASSED_TESTS++))
|
||||
else
|
||||
echo -e " ${RED}✗ FEHLGESCHLAGEN${NC}\n"
|
||||
((FAILED_TESTS++))
|
||||
fi
|
||||
}
|
||||
|
||||
if [ $RET -eq 0 ]; then
|
||||
echo -e "${GREEN}✓ Test 1: Funktion inspect_dns_transport existiert und gibt 0 zurück.${NC}"
|
||||
echo -e "${GREEN}✓ Test 2: DNS Resource Records / Syntax validiert.${NC}"
|
||||
echo -e "${GREEN}✓ Test 3: Teilziele 1 bis 4 erfolgreich abgeschlossen.${NC}"
|
||||
echo -e "${GREEN}🎉 ERFOLG: Alle DNS-Prüfungen in DNS 03: UDP vs. TCP Port 53 & EDNS0 bestanden!${NC}"
|
||||
# TEST 1: Zonentransfer erfordert immer TCP
|
||||
run_test "select_dns_transport: AXFR wählt TCP" '
|
||||
select_dns_transport 200 "AXFR" "false" | grep -q "TCP (Grund: Zonentransfer"
|
||||
'
|
||||
|
||||
# TEST 2: Kleines Paket wählt Standard UDP
|
||||
run_test "select_dns_transport: Paket <= 512B wählt UDP" '
|
||||
select_dns_transport 350 "A" "false" | grep -q "UDP (Grund: Paket passt in Standard-512B-Puffer)"
|
||||
'
|
||||
|
||||
# TEST 3: Großes Paket mit EDNS0 bleibt auf UDP
|
||||
run_test "select_dns_transport: Paket 1400B mit EDNS0 bleibt UDP_EDNS0" '
|
||||
select_dns_transport 1400 "TXT" "true" | grep -q "UDP_EDNS0"
|
||||
'
|
||||
|
||||
# TEST 4: Großes Paket ohne EDNS0 triggert TC Truncation Fallback
|
||||
run_test "select_dns_transport: Paket 800B ohne EDNS0 meldet TCP_TRUNCATED" '
|
||||
select_dns_transport 800 "ANY" "false" | grep -q "TCP_TRUNCATED"
|
||||
'
|
||||
|
||||
# TEST 5: Sektionszähler parsen
|
||||
run_test "parse_section_counts ermittelt HAS_ANSWERS Status" '
|
||||
OUT=$(parse_section_counts 1 3 2 1)
|
||||
echo "$OUT" | grep -q "QUESTIONS=1" && \
|
||||
echo "$OUT" | grep -q "ANSWERS=3" && \
|
||||
echo "$OUT" | grep -q "STATUS=HAS_ANSWERS"
|
||||
'
|
||||
|
||||
# TEST 6: EDNS0 Erkennung aus dig-Mock
|
||||
run_test "verify_edns0_in_dig erkennt OPT PSEUDOSECTION" '
|
||||
TMP_DIG=$(mktemp /tmp/dig_mock_XXXXXX)
|
||||
cat << "EOF" > "$TMP_DIG"
|
||||
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 1234
|
||||
;; OPT PSEUDOSECTION:
|
||||
; EDNS: version: 0, flags:; udp: 4096
|
||||
;; QUESTION SECTION:
|
||||
;example.com. IN A
|
||||
EOF
|
||||
OUT=$(verify_edns0_in_dig "$TMP_DIG")
|
||||
rm -f "$TMP_DIG"
|
||||
echo "$OUT" | grep -q "EDNS0_ACTIVE: Puffergröße 4096 Bytes"
|
||||
'
|
||||
|
||||
# TEST 7: BIND Zonentransfer Absicherung
|
||||
run_test "secure_zone_transfers schreibt allow-transfer Direktive" '
|
||||
TMP_ZONE=$(mktemp /tmp/zone_conf_XXXXXX)
|
||||
secure_zone_transfers "firma.de" "10.0.0.20" "$TMP_ZONE"
|
||||
[ -f "$TMP_ZONE" ] && \
|
||||
grep -q "zone "firma.de"" "$TMP_ZONE" && \
|
||||
grep -q "allow-transfer { 10.0.0.20; };" "$TMP_ZONE"
|
||||
rm -f "$TMP_ZONE"
|
||||
'
|
||||
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
if [ "$FAILED_TESTS" -eq 0 ]; then
|
||||
echo -e "${GREEN}🎉 ERFOLG: Alle ${PASSED_TESTS}/${TOTAL_TESTS} Tests in DNS 03 erfolgreich bestanden!${NC}"
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
exit 0
|
||||
else
|
||||
echo -e "${RED}❌ FEHLER: Skript fehlgeschlagen mit Status $RET${NC}"
|
||||
echo -e "${RED}❌ FEHLER: ${FAILED_TESTS} von ${TOTAL_TESTS} Tests fehlgeschlagen.${NC}"
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -1,18 +1,45 @@
|
|||
# DNS 04: TTL & DNS-Propagation
|
||||
# DNS 04: TTL (Time to Live), Caching & DNS-Propagation
|
||||
|
||||
Willkommen zum Kurs **DNS-Records, Domain Name System & E-Mail-Sicherheit** im IT-Praxisportal!
|
||||
Willkommen zu **Modul 04** des Kurses **DNS-Records, Domain Name System & E-Mail-Sicherheit**!
|
||||
|
||||
## 💡 Das Wichtigste in Kürze
|
||||
- **Ziel:** Time-To-Live (TTL), Cache-Invalidierung, Negative Caching (RFC 2308) und Migrationsstrategien.
|
||||
- **Wichtigstes Tool:** `dig +nocache`
|
||||
In diesem Modul lernst du das Herzstück der globalen DNS-Skalierbarkeit kennen: **Caching und die Time to Live (TTL)**. Du erfährst, warum DNS-Propagation Zeit benötigt, wie Servermigrationen ohne Downtime geplant werden und was **Negative Caching** bedeutet.
|
||||
|
||||
## 🎯 Deine Aufgaben (Checkliste)
|
||||
1. **Teilziel 1:** TTL-Werte in Resource Records analysieren
|
||||
2. **Teilziel 2:** TTL vor einer Server-Migration schrittweise reduzieren (300 Sek.)
|
||||
3. **Teilziel 3:** Negative Caching TTL im SOA-Record verstehen
|
||||
4. **Teilziel 4:** DNS-Propagation globaler Nameserver prüfen
|
||||
---
|
||||
|
||||
## 💡 1. Das Wichtigste in Kürze
|
||||
|
||||
### Wie Caching funktioniert
|
||||
- Jeder DNS-Record besitzt eine TTL in Sekunden.
|
||||
- Fragt ein Resolver einen Eintrag an, speichert er ihn im lokalen RAM-Cache.
|
||||
- Bei Folgeabfragen liefert der Resolver die Antwort direkt aus dem Cache und zählt die TTL sekundengenau herunter.
|
||||
- Nach Ablauf der TTL (`TTL=0`) verwirft der Resolver den Eintrag und kontaktiert den autoritativen Server erneut.
|
||||
|
||||
### Die typische Migrations-Katastrophe
|
||||
Ein Unternehmen will am Samstag um 20:00 Uhr seine Website auf eine neue Server-IP umziehen. Die aktuelle TTL des A-Records beträgt **86.400 Sekunden (24 Stunden)**.
|
||||
- *Fehler:* Der Administrator ändert die IP erst am Samstag um 20:00 Uhr.
|
||||
- *Die Folge:* Bis zu 24 Stunden lang leiten Caches von Telekom, Vodafone, Google und Co. Besucher auf den alten Server weiter!
|
||||
- *Best Practice:*
|
||||
1. Mindestens **24 bis 48 Stunden vor dem Umzug** die TTL auf **300 Sekunden (5 Minuten)** senken.
|
||||
2. Am Samstag umziehen (die Änderung ist nun innerhalb von 5 Minuten weltweit aktiv).
|
||||
3. Nach erfolgreichem Test die TTL wieder auf 86.400s anheben.
|
||||
|
||||
### Negative Caching (RFC 2308)
|
||||
Auch Nicht-Existenz (`NXDOMAIN`) wird gecacht! Die Gültigkeitsdauer dafür bestimmt das **5. numerische Feld im SOA-Record** (Minimum / Negative Cache TTL). Typische Werte liegen zwischen 1.800s (30 Min.) und 7.200s (2 Std.).
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Deine Aufgaben in `aufgabe.sh`
|
||||
|
||||
1. **TODO 1 (`format_ttl_human`):** Rechne Sekundenwerte in Tage, Stunden, Minuten und Sekunden um.
|
||||
2. **TODO 2 (`plan_dns_migration`):** Berechne die Vorlaufzeit für eine Servermigration basierend auf der aktuellen TTL.
|
||||
3. **TODO 3 (`simulate_cache_decay`):** Berechne die verbleibende Rest-TTL im Resolver-Cache.
|
||||
4. **TODO 4 (`extract_soa_negative_ttl`):** Extrahiere das Negative Caching Feld aus einem SOA-Record und prüfe die RFC-Konformität.
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Tests ausführen
|
||||
|
||||
Führe im Terminal folgenden Befehl aus:
|
||||
```bash
|
||||
bash test_aufgabe.sh
|
||||
```
|
||||
|
|
|
|||
|
|
@ -1,21 +1,116 @@
|
|||
#!/usr/bin/env bash
|
||||
# ==============================================================================
|
||||
# 🌐 DNS Caching - DNS 04: TTL & DNS-Propagation
|
||||
# 🌐 DNS 04: TTL (TIME TO LIVE), CACHING & DNS-PROPAGATION
|
||||
# ==============================================================================
|
||||
#
|
||||
# DAS CROWN-JEWEL DES DNS: CACHING & TTL
|
||||
# Ohne Caching würde das weltweite Internet unter Milliarden täglichen DNS-Queries
|
||||
# zusammenbrechen.
|
||||
#
|
||||
# WIE FUNKTIONIERT DIE TIME TO LIVE (TTL)?
|
||||
# - Der autoritative Nameserver legt für jeden Resource Record fest, wie viele
|
||||
# Sekunden lang ein rekursiver Resolver diesen Eintrag im lokalen Cache speichern darf.
|
||||
# - Bei jeder Client-Anfrage zieht der Resolver die verstrichene Zeit von der TTL ab.
|
||||
# - Sobald die TTL 0 erreicht, wird der Record aus dem Cache verworfen und beim
|
||||
# nächsten Aufruf frisch vom autoritativen Server angefordert.
|
||||
#
|
||||
# DIE SERVERMIGRATIONS-FALLE:
|
||||
# Wenn ein Webserver umgezogen werden soll und die aktuelle TTL 86400 Sekunden (24 Stunden)
|
||||
# beträgt, müssen Administratoren die TTL mindestens 24 bis 48 Stunden VOR dem Umzug
|
||||
# auf z.B. 300 Sekunden (5 Minuten) herabsetzen!
|
||||
# Andernfalls routen Caches weltweit bis zu 24 Stunden lang Anfragen auf den alten Server.
|
||||
#
|
||||
# NEGATIVE CACHING (RFC 2308):
|
||||
# Auch Fehlversuche (NXDOMAIN = Domain existiert nicht) werden gecacht!
|
||||
# Die Gültigkeitsdauer dafür bestimmt das 5. numerische Feld im SOA-Record (Minimum / Neg. TTL).
|
||||
# ==============================================================================
|
||||
|
||||
# 🎯 TEILZIEL 1: TTL-Werte in Resource Records analysieren
|
||||
# 🎯 TEILZIEL 2: TTL vor einer Server-Migration schrittweise reduzieren (300 Sek.)
|
||||
# 🎯 TEILZIEL 3: Negative Caching TTL im SOA-Record verstehen
|
||||
# 🎯 TEILZIEL 4: DNS-Propagation globaler Nameserver prüfen
|
||||
# 🎯 TEILZIEL 1 (TODO 1): TTL-Sekunden in lesbare Zeitangabe umrechnen
|
||||
# Funktion: format_ttl_human "$seconds"
|
||||
# Parameter $1: Sekunden als Ganzzahl (z.B. 86400, 3600, 300)
|
||||
# Anforderungen:
|
||||
# - Rechne die Sekunden in Tage (d), Stunden (h), Minuten (m) und Sekunden (s) um.
|
||||
# - Format:
|
||||
# * Wenn >= 86400: z.B. "1d 0h 0m 0s" (bei 86400) oder "1d 2h 30m 0s"
|
||||
# * Wenn < 86400 und >= 3600: z.B. "1h 0m 0s" (bei 3600)
|
||||
# * Wenn < 3600 und >= 60: z.B. "5m 0s" (bei 300)
|
||||
# * Wenn < 60: z.B. "45s"
|
||||
format_ttl_human() {
|
||||
local sec="$1"
|
||||
# TODO 1: Implementiere die TTL-Formatierung
|
||||
return 0
|
||||
}
|
||||
|
||||
calculate_ttl_migration() {
|
||||
local domain="${1:-it-praxisportal.de}"
|
||||
echo "▶ Analysiere DNS-Konfiguration für $domain..."
|
||||
# 🎯 TEILZIEL 2 (TODO 2): Migrations-Zeitplan berechnen
|
||||
# Funktion: plan_dns_migration "$current_ttl" "$maintenance_window_start_hours_from_now"
|
||||
# Parameter $1: Aktuelle TTL in Sekunden (z.B. 86400 für 24h)
|
||||
# Parameter $2: Geplanter Wartungsstart in Stunden ab jetzt (z.B. 48)
|
||||
# Anforderungen:
|
||||
# - Berechne, wie viele Stunden vor dem Wartungsstart die TTL spätestens gesenkt werden muss:
|
||||
# * ttl_in_hours = current_ttl / 3600
|
||||
# * min_advance_hours = ttl_in_hours
|
||||
# - Empfohlene Übergangs-TTL: 300 Sekunden (5 Minuten)
|
||||
# - Prüfe, ob das Wartungsfenster weit genug in der Zukunft liegt:
|
||||
# * Wenn maintenance_window_start_hours_from_now < min_advance_hours:
|
||||
# Gib aus: "MIGRATION_RISK: Wartungsfenster zu nah! Mindestens <min_advance_hours>h Vorlaufzeit erforderlich."
|
||||
# return 1
|
||||
# * Andernfalls gib strukturiert aus:
|
||||
# ADVANCE_HOURS=<min_advance_hours>
|
||||
# TEMP_TTL=300
|
||||
# STATUS=READY_TO_SCHEDULE
|
||||
# EMPFEHLUNG=Senke die TTL <min_advance_hours> Stunden vor Beginn auf 300s.
|
||||
plan_dns_migration() {
|
||||
local current_ttl="$1"
|
||||
local maint_hours="$2"
|
||||
# TODO 2: Berechne den Migrationszeitplan
|
||||
return 0
|
||||
}
|
||||
|
||||
# TODO: Implementiere die geforderten DNS-Prüfungen oder Record-Erstellungen
|
||||
return 1
|
||||
# 🎯 TEILZIEL 3 (TODO 3): Cache-Degradation simulieren
|
||||
# Funktion: simulate_cache_decay "$initial_ttl" "$elapsed_seconds"
|
||||
# Parameter $1: Ursprüngliche TTL des Records (z.B. 3600)
|
||||
# Parameter $2: Bereits vergangene Zeit in Sekunden (z.B. 1200 oder 4000)
|
||||
# Anforderungen:
|
||||
# - Berechne die verbleibende Rest-TTL: rest = initial_ttl - elapsed_seconds
|
||||
# - Wenn rest > 0:
|
||||
# Gib aus: "CACHE_HIT: Rest-TTL beträgt <rest>s"
|
||||
# return 0
|
||||
# - Wenn rest <= 0:
|
||||
# Gib aus: "CACHE_EXPIRED: TTL abgelaufen (0s). Neuer iterativer Query erforderlich."
|
||||
# return 1
|
||||
simulate_cache_decay() {
|
||||
local init_ttl="$1"
|
||||
local elapsed="$2"
|
||||
# TODO 3: Simuliere den Cache-Ablauf
|
||||
return 0
|
||||
}
|
||||
|
||||
# 🎯 TEILZIEL 4 (TODO 4): SOA Negative Caching TTL extrahieren
|
||||
# Funktion: extract_soa_negative_ttl "$soa_record_line"
|
||||
# Parameter $1: Eine SOA-Record-Zeile (z.B. "@ IN SOA ns1.firma.de. admin.firma.de. 2026091501 7200 3600 1209600 3600")
|
||||
# Anforderungen:
|
||||
# - Das letzte Feld ist die Negative Cache TTL (RFC 2308).
|
||||
# - Extrahiere diesen Zahlenwert.
|
||||
# - Prüfe die RFC-Empfehlung (Standard: 1800s bis 7200s, max 86400s):
|
||||
# * Wenn neg_ttl >= 600 und neg_ttl <= 86400:
|
||||
# Gib aus: "NEG_TTL=<neg_ttl>s | COMPLIANT=YES"
|
||||
# return 0
|
||||
# * Wenn neg_ttl < 600 oder neg_ttl > 86400:
|
||||
# Gib aus: "NEG_TTL=<neg_ttl>s | COMPLIANT=NO (Empfehlung: 1800s bis 7200s)"
|
||||
# return 1
|
||||
extract_soa_negative_ttl() {
|
||||
local soa_line="$1"
|
||||
# TODO 4: Extrahiere und validiere das Negative Caching Feld
|
||||
return 0
|
||||
}
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
calculate_ttl_migration "$@"
|
||||
echo "=== Test Teilziel 1 ==="
|
||||
format_ttl_human 86400
|
||||
echo ""
|
||||
echo "=== Test Teilziel 2 ==="
|
||||
plan_dns_migration 86400 48
|
||||
echo ""
|
||||
echo "=== Test Teilziel 3 ==="
|
||||
simulate_cache_decay 3600 1200
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
<link rel="icon" type="image/svg+xml" href="../../../assets/favicon.svg">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>DNS 04: TTL & DNS-Propagation</title>
|
||||
<title>DNS 04: TTL & DNS-Propagation</title>
|
||||
<link rel="stylesheet" href="../../../assets/style.css">
|
||||
</head>
|
||||
<body>
|
||||
|
|
@ -12,46 +12,108 @@
|
|||
<div class="app-main">
|
||||
<header>
|
||||
<div class="header-container">
|
||||
<div class="header-badge">🌐 DNS Caching</div>
|
||||
<h1>DNS 04: TTL & DNS-Propagation</h1>
|
||||
<p>Time-To-Live (TTL), Cache-Invalidierung, Negative Caching (RFC 2308) und Migrationsstrategien.</p>
|
||||
<div class="header-badge">🌐 DNS & Infrastruktur</div>
|
||||
<h1>DNS 04: TTL (Time to Live) & DNS-Propagation</h1>
|
||||
<p>Verstehe Caching-Mechanismen, plane Servermigrationen ohne Downtime und analysiere Negative Caching nach RFC 2308.</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="container">
|
||||
<!-- 1. THEORIE: TTL & CACHE -->
|
||||
<section class="card">
|
||||
<h2>💡 Theorie & Praxiskonzept</h2>
|
||||
<p>Time-To-Live (TTL), Cache-Invalidierung, Negative Caching (RFC 2308) und Migrationsstrategien.</p>
|
||||
<div class="code-container">
|
||||
<div class="code-header">
|
||||
<span>DNS Command & Syntax Reference</span>
|
||||
<span>Tool: dig +nocache</span>
|
||||
</div>
|
||||
<pre><code># Wichtige Befehle & Zonensyntax:
|
||||
dig dig +nocache
|
||||
named-checkzone example.com /etc/bind/zones/db.example.com</code></pre>
|
||||
<h2>⏳ 1. Caching & die Time to Live (TTL)</h2>
|
||||
<p>Die TTL bestimmt, wie lange ein rekursiver Resolver einen Record zwischenspeichern darf. Mit <code>dig</code> kannst du beobachten, wie die TTL im Cache bei jeder Abfrage abnimmt:</p>
|
||||
|
||||
<pre><code># 1. Erste Abfrage (vom autoritativen Server geholt):
|
||||
dig www.it-praxisportal.de +noall +answer
|
||||
# Ausgabe: www.it-praxisportal.de. 3600 IN A 188.245.100.5
|
||||
|
||||
# 2. Zweite Abfrage 45 Sekunden später (aus dem lokalen Cache):
|
||||
dig www.it-praxisportal.de +noall +answer
|
||||
# Ausgabe: www.it-praxisportal.de. 3555 IN A 188.245.100.5 <- TTL zählt runter!</code></pre>
|
||||
</section>
|
||||
|
||||
<!-- 2. THEORIE: MIGRATION -->
|
||||
<section class="card">
|
||||
<h2>🚀 2. Professioneller Serverumzug ohne Downtime</h2>
|
||||
<p>Wenn du einen produktiven Web- oder Mailserver migrierst, ist die TTL dein wichtigstes Werkzeug:</p>
|
||||
|
||||
<div class="box box-tipp">
|
||||
<div class="box-title">💡 Der 3-Schritte-Migrationsplan für Systemintegratoren</div>
|
||||
<ol style="margin-left: 20px; line-height: 1.6;">
|
||||
<li><strong>T minus 48 Stunden:</strong> Senke die TTL des A/AAAA-Records von 86.400s (24h) auf <strong>300s (5 Min.)</strong>. Alle alten Caches laufen vor dem Umzug ab.</li>
|
||||
<li><strong>Wartungsfenster (Tag T):</strong> Ändere die IP-Adresse auf den neuen Server. Nach spätestens 5 Minuten sprechen alle Clients weltweit den neuen Server an!</li>
|
||||
<li><strong>T plus 24 Stunden:</strong> Sobald alles stabil läuft, hebe die TTL wieder auf 86.400s an, um die DNS-Last zu minimieren.</li>
|
||||
</ol>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 3. CHEAT-SHEET -->
|
||||
<section class="card">
|
||||
<h2>🎯 Aufgaben & Checkliste</h2>
|
||||
<h2>📑 3. Cheat-Sheet: Typische TTL-Richtwerte in der Praxis</h2>
|
||||
<div style="overflow-x: auto;">
|
||||
<table style="width: 100%; border-collapse: collapse; font-size: 0.9rem; text-align: left;">
|
||||
<thead>
|
||||
<tr style="border-bottom: 2px solid var(--border-color); background: var(--bg-subtle);">
|
||||
<th style="padding: 10px;">Record-Typ</th>
|
||||
<th style="padding: 10px;">Empfohlene TTL</th>
|
||||
<th style="padding: 10px;">Begründung</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr style="border-bottom: 1px solid var(--border-color);">
|
||||
<td style="padding: 10px;"><code>NS</code> / <code>SOA</code></td>
|
||||
<td style="padding: 10px;">86.400s (24h) bis 172.800s (48h)</td>
|
||||
<td style="padding: 10px;">Nameserver-Infrastruktur ändert sich extrem selten; entlastet TLDs</td>
|
||||
</tr>
|
||||
<tr style="border-bottom: 1px solid var(--border-color);">
|
||||
<td style="padding: 10px;"><code>MX</code></td>
|
||||
<td style="padding: 10px;">14.400s (4h) bis 86.400s (24h)</td>
|
||||
<td style="padding: 10px;">Mailserver-Routen sind stabil; Backup-MX fängt Ausfälle ab</td>
|
||||
</tr>
|
||||
<tr style="border-bottom: 1px solid var(--border-color);">
|
||||
<td style="padding: 10px;">Standard <code>A</code> / <code>AAAA</code></td>
|
||||
<td style="padding: 10px;">3.600s (1h) bis 14.400s (4h)</td>
|
||||
<td style="padding: 10px;">Gute Balance zwischen Lastreduktion und Flexibilität</td>
|
||||
</tr>
|
||||
<tr style="border-bottom: 1px solid var(--border-color);">
|
||||
<td style="padding: 10px;">Migrations-Vorbereitung</td>
|
||||
<td style="padding: 10px;">300s (5 Min.) bis 600s (10 Min.)</td>
|
||||
<td style="padding: 10px;">Ermöglicht schnellen IP-Wechsel bei minimaler Propagation-Downtime</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 10px;">Negative Caching (NXDOMAIN)</td>
|
||||
<td style="padding: 10px;">1.800s (30m) bis 7.200s (2h)</td>
|
||||
<td style="padding: 10px;">5. Feld im SOA; verhindert Query-Fluten bei Tippfehlern</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 4. AUFGABEN CHECKLISTE -->
|
||||
<section class="card">
|
||||
<h2>🎯 Aufgaben & Checkliste in <code>aufgabe.sh</code></h2>
|
||||
<div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t1">
|
||||
<label class="task-label" for="t1"><strong>TODO 1:</strong> TTL-Werte in Resource Records analysieren</label>
|
||||
</div><div class="task-item">
|
||||
<label class="task-label" for="t1"><strong>TODO 1:</strong> <code>format_ttl_human(sec)</code>: Sekunden in d, h, m, s umrechnen.</label>
|
||||
</div>
|
||||
<div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t2">
|
||||
<label class="task-label" for="t2"><strong>TODO 2:</strong> TTL vor einer Server-Migration schrittweise reduzieren (300 Sek.)</label>
|
||||
</div><div class="task-item">
|
||||
<label class="task-label" for="t2"><strong>TODO 2:</strong> <code>plan_dns_migration(...)</code>: Vorlaufzeit und temporäre TTL für Serverumzug berechnen.</label>
|
||||
</div>
|
||||
<div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t3">
|
||||
<label class="task-label" for="t3"><strong>TODO 3:</strong> Negative Caching TTL im SOA-Record verstehen</label>
|
||||
</div><div class="task-item">
|
||||
<label class="task-label" for="t3"><strong>TODO 3:</strong> <code>simulate_cache_decay(...)</code>: Rest-TTL ermitteln oder Cache Expiry melden.</label>
|
||||
</div>
|
||||
<div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t4">
|
||||
<label class="task-label" for="t4"><strong>TODO 4:</strong> DNS-Propagation globaler Nameserver prüfen</label>
|
||||
<label class="task-label" for="t4"><strong>TODO 4:</strong> <code>extract_soa_negative_ttl(...)</code>: Negative Caching Feld aus SOA prüfen.</label>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 25px; display: flex; gap: 12px; flex-wrap: wrap;">
|
||||
<a href="../../../workspace.html?course=dns_records&track=track_1_dns_architektur&chapter=04_ttl_und_dns_propagation" class="btn" style="background: #0284c7; color: #fff; font-weight: 700;">💻 In Web-IDE öffnen →</a>
|
||||
<a href="../05_a_und_aaaa_records/index.html" class="btn btn-secondary">Weiter zu Modul →</a>
|
||||
<a href="../../../workspace.html?course=dns_records&track=track_1_dns_architektur&chapter=04_ttl_und_dns_propagation" class="btn" style="background: #1d4ed8; color: #fff; font-weight: 700;">In Web-IDE öffnen →</a>
|
||||
<a href="../05_a_und_aaaa_records/index.html" class="btn btn-secondary">Weiter zu Lehrpfad 2 (Modul 05) →</a>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
|
|
|||
|
|
@ -1,97 +1,87 @@
|
|||
#!/usr/bin/env bash
|
||||
# ==============================================================================
|
||||
# MUSTERLÖSUNG: DNS 04: TTL & DNS-Propagation
|
||||
# MUSTERLÖSUNG: DNS 04: TTL (TIME TO LIVE), CACHING & DNS-PROPAGATION
|
||||
# ==============================================================================
|
||||
|
||||
calculate_ttl_migration() {
|
||||
local domain="${1:-it-praxisportal.de}"
|
||||
local target_dir="${2:-.}"
|
||||
format_ttl_human() {
|
||||
local s="$1"
|
||||
[ -z "$s" ] && return 1
|
||||
|
||||
echo "▶ Starte professionelle DNS-Operation für $domain..."
|
||||
local d=$((s / 86400))
|
||||
local rem=$((s % 86400))
|
||||
local h=$((rem / 3600))
|
||||
rem=$((rem % 3600))
|
||||
local m=$((rem / 60))
|
||||
local sec=$((rem % 60))
|
||||
|
||||
case "04_ttl_und_dns_propagation" in
|
||||
*01_dns_hierarchie*)
|
||||
echo "Host: www | SLD: it-praxisportal | TLD: de | Root: ."
|
||||
;;
|
||||
*02_rekursive*)
|
||||
echo "Simulation Iteration: Root (.) -> de. -> ns1.it-praxisportal.de -> 188.245.100.5"
|
||||
;;
|
||||
*03_ports_udp*)
|
||||
echo "Protocol: UDP/53 (Standard) | Fallback: TCP/53 | EDNS0 Buffer: 4096 bytes"
|
||||
;;
|
||||
*04_ttl*)
|
||||
echo "TTL Current: 86400s | Migration Planned TTL: 300s | Neg Cache: 3600s"
|
||||
;;
|
||||
*05_a_und_aaaa*)
|
||||
cat << 'ZONE' > "$target_dir/zone_records.txt"
|
||||
@ IN A 188.245.100.5
|
||||
@ IN AAAA 2a01:4f8:c010:d::1
|
||||
www IN A 188.245.100.5
|
||||
ZONE
|
||||
;;
|
||||
*06_cname*)
|
||||
echo "api IN CNAME app.it-praxisportal.de." > "$target_dir/cname_record.txt"
|
||||
;;
|
||||
*07_soa*)
|
||||
cat << 'ZONE' > "$target_dir/soa_record.txt"
|
||||
@ IN SOA ns1.it-praxisportal.de. hostmaster.it-praxisportal.de. (
|
||||
2026091201 ; Serial YYYYMMDDNN
|
||||
7200 ; Refresh (2h)
|
||||
3600 ; Retry (1h)
|
||||
1209600 ; Expire (2w)
|
||||
3600 ; Negative Cache TTL (1h)
|
||||
)
|
||||
ZONE
|
||||
;;
|
||||
*08_ns_und_ptr*)
|
||||
echo "100.245.188.in-addr.arpa. IN PTR mail.it-praxisportal.de." > "$target_dir/ptr_record.txt"
|
||||
;;
|
||||
*09_mx*)
|
||||
echo "@ IN MX 10 mail.it-praxisportal.de." > "$target_dir/mx_record.txt"
|
||||
echo "@ IN MX 20 backup-mail.it-praxisportal.de." >> "$target_dir/mx_record.txt"
|
||||
;;
|
||||
*10_txt_records*)
|
||||
echo '@ IN TXT "v=spf1 mx ip4:188.245.100.5 include:_spf.google.com -all"' > "$target_dir/spf_record.txt"
|
||||
;;
|
||||
*11_dkim*)
|
||||
echo 's1._domainkey IN TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz..."' > "$target_dir/dkim_record.txt"
|
||||
;;
|
||||
*12_dmarc*)
|
||||
echo '_dmarc IN TXT "v=DMARC1; p=reject; rua=mailto:dmarc-reports@it-praxisportal.de; pct=100; aspf=s"' > "$target_dir/dmarc_record.txt"
|
||||
;;
|
||||
*13_srv*)
|
||||
echo '_ldap._tcp.dc._msdcs.corp IN SRV 0 100 389 dc01.corp.it-praxisportal.de.' > "$target_dir/srv_record.txt"
|
||||
;;
|
||||
*14_caa*)
|
||||
echo '@ IN CAA 0 issue "letsencrypt.org"' > "$target_dir/caa_record.txt"
|
||||
echo '@ IN CAA 0 iodef "mailto:security@it-praxisportal.de"' >> "$target_dir/caa_record.txt"
|
||||
;;
|
||||
*15_dns_troubleshooting*)
|
||||
echo "STATUS: NOERROR | ANSWER: 1 | FLAGS: qr aa rd ra"
|
||||
;;
|
||||
*16_master*)
|
||||
cat << 'REPORT' > "$target_dir/dns_audit_report.json"
|
||||
{
|
||||
"domain": "it-praxisportal.de",
|
||||
"audit_status": "PASSED",
|
||||
"score": 100,
|
||||
"soa_serial_valid": true,
|
||||
"ns_redundancy": 2,
|
||||
"email_security": {
|
||||
"spf": "valid (-all)",
|
||||
"dkim": "present",
|
||||
"dmarc": "enforced (p=reject)"
|
||||
},
|
||||
"dnssec": "active"
|
||||
}
|
||||
REPORT
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "✅ DNS-Operation erfolgreich abgeschlossen."
|
||||
if [ "$s" -ge 86400 ]; then
|
||||
echo "${d}d ${h}h ${m}m ${sec}s"
|
||||
elif [ "$s" -ge 3600 ]; then
|
||||
echo "${h}h ${m}m ${sec}s"
|
||||
elif [ "$s" -ge 60 ]; then
|
||||
echo "${m}m ${sec}s"
|
||||
else
|
||||
echo "${sec}s"
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
plan_dns_migration() {
|
||||
local current_ttl="$1"
|
||||
local maint_hours="$2"
|
||||
|
||||
local ttl_hours=$((current_ttl / 3600))
|
||||
[ "$ttl_hours" -lt 1 ] && ttl_hours=1
|
||||
|
||||
if [ "$maint_hours" -lt "$ttl_hours" ]; then
|
||||
echo "MIGRATION_RISK: Wartungsfenster zu nah! Mindestens ${ttl_hours}h Vorlaufzeit erforderlich."
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "ADVANCE_HOURS=${ttl_hours}"
|
||||
echo "TEMP_TTL=300"
|
||||
echo "STATUS=READY_TO_SCHEDULE"
|
||||
echo "EMPFEHLUNG=Senke die TTL ${ttl_hours} Stunden vor Beginn auf 300s."
|
||||
return 0
|
||||
}
|
||||
|
||||
simulate_cache_decay() {
|
||||
local init="$1"
|
||||
local elapsed="$2"
|
||||
|
||||
local rest=$((init - elapsed))
|
||||
if [ "$rest" -gt 0 ]; then
|
||||
echo "CACHE_HIT: Rest-TTL beträgt ${rest}s"
|
||||
return 0
|
||||
else
|
||||
echo "CACHE_EXPIRED: TTL abgelaufen (0s). Neuer iterativer Query erforderlich."
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
extract_soa_negative_ttl() {
|
||||
local line="$1"
|
||||
local clean=$(echo "$line" | tr -d '()' | tr -s '[:space:]' ' ')
|
||||
clean="${clean## }"
|
||||
clean="${clean%% }"
|
||||
|
||||
# Extrahiere das letzte Token
|
||||
local last_token="${clean##* }"
|
||||
|
||||
if [[ ! "$last_token" =~ ^[0-9]+$ ]]; then
|
||||
echo "ERROR: Ungültiges SOA-Format"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ "$last_token" -ge 600 ] && [ "$last_token" -le 86400 ]; then
|
||||
echo "NEG_TTL=${last_token}s | COMPLIANT=YES"
|
||||
return 0
|
||||
else
|
||||
echo "NEG_TTL=${last_token}s | COMPLIANT=NO (Empfehlung: 1800s bis 7200s)"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
calculate_ttl_migration "$@"
|
||||
format_ttl_human "$@"
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -1,36 +1,94 @@
|
|||
#!/usr/bin/env bash
|
||||
# ==============================================================================
|
||||
# TESTSUITE: DNS 04: TTL & DNS-Propagation
|
||||
# 🧪 TESTSUITE: DNS 04 - TTL & DNS-PROPAGATION
|
||||
# ==============================================================================
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
BLUE='\033[0;34m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
TARGET_SCRIPT="${1:-aufgabe.sh}"
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
TOTAL_TESTS=0
|
||||
PASSED_TESTS=0
|
||||
FAILED_TESTS=0
|
||||
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
echo -e "${BLUE}🧪 TESTSUITE: DNS 04: TTL & DNS-Propagation${NC}"
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
echo -e "${BLUE} Ziel-Skript: ${YELLOW}${TARGET_SCRIPT}${NC}"
|
||||
echo -e "${BLUE}================================================================${NC}\n"
|
||||
|
||||
TEST_DIR=$(mktemp -d /tmp/dns_test_XXXXXX)
|
||||
trap 'rm -rf "$TEST_DIR"' EXIT
|
||||
if [ ! -f "$SCRIPT_DIR/$TARGET_SCRIPT" ]; then
|
||||
echo -e "${RED}❌ Fehler: Datei '$TARGET_SCRIPT' nicht gefunden!${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
source "$SCRIPT_DIR/$TARGET_SCRIPT"
|
||||
|
||||
# Führe Funktion aus
|
||||
calculate_ttl_migration "example.com" "$TEST_DIR"
|
||||
RET=$?
|
||||
run_test() {
|
||||
local name="$1"
|
||||
local cmd="$2"
|
||||
((TOTAL_TESTS++))
|
||||
echo -e "▶ Test ${TOTAL_TESTS}: ${name}"
|
||||
if eval "$cmd"; then
|
||||
echo -e " ${GREEN}✓ BESTANDEN${NC}\n"
|
||||
((PASSED_TESTS++))
|
||||
else
|
||||
echo -e " ${RED}✗ FEHLGESCHLAGEN${NC}\n"
|
||||
((FAILED_TESTS++))
|
||||
fi
|
||||
}
|
||||
|
||||
if [ $RET -eq 0 ]; then
|
||||
echo -e "${GREEN}✓ Test 1: Funktion calculate_ttl_migration existiert und gibt 0 zurück.${NC}"
|
||||
echo -e "${GREEN}✓ Test 2: DNS Resource Records / Syntax validiert.${NC}"
|
||||
echo -e "${GREEN}✓ Test 3: Teilziele 1 bis 4 erfolgreich abgeschlossen.${NC}"
|
||||
echo -e "${GREEN}🎉 ERFOLG: Alle DNS-Prüfungen in DNS 04: TTL & DNS-Propagation bestanden!${NC}"
|
||||
# TEST 1: TTL Formatierung Tage
|
||||
run_test "format_ttl_human: 86400 Sekunden zu 1d 0h 0m 0s" '
|
||||
format_ttl_human 86400 | grep -q "1d 0h 0m 0s"
|
||||
'
|
||||
|
||||
# TEST 2: TTL Formatierung Minuten
|
||||
run_test "format_ttl_human: 300 Sekunden zu 5m 0s" '
|
||||
format_ttl_human 300 | grep -q "5m 0s"
|
||||
'
|
||||
|
||||
# TEST 3: Migrations-Zeitplaner mit ausreichend Vorlauf
|
||||
run_test "plan_dns_migration: 86400s TTL mit 48h Vorlauf ist planbar" '
|
||||
OUT=$(plan_dns_migration 86400 48)
|
||||
echo "$OUT" | grep -q "ADVANCE_HOURS=24" && \
|
||||
echo "$OUT" | grep -q "TEMP_TTL=300" && \
|
||||
echo "$OUT" | grep -q "STATUS=READY_TO_SCHEDULE"
|
||||
'
|
||||
|
||||
# TEST 4: Migrations-Zeitplaner warnt bei zu kurzem Vorlauf
|
||||
run_test "plan_dns_migration: Warnt bei 12h Vorlauf bei 24h TTL" '
|
||||
OUT=$(plan_dns_migration 86400 12)
|
||||
echo "$OUT" | grep -q "MIGRATION_RISK: Wartungsfenster zu nah"
|
||||
'
|
||||
|
||||
# TEST 5: Cache Decay (Noch im Cache)
|
||||
run_test "simulate_cache_decay meldet CACHE_HIT bei Restzeit" '
|
||||
simulate_cache_decay 3600 1000 | grep -q "CACHE_HIT: Rest-TTL beträgt 2600s"
|
||||
'
|
||||
|
||||
# TEST 6: Cache Decay (Abgelaufen)
|
||||
run_test "simulate_cache_decay meldet CACHE_EXPIRED nach Ablauf" '
|
||||
simulate_cache_decay 3600 4000 | grep -q "CACHE_EXPIRED"
|
||||
'
|
||||
|
||||
# TEST 7: SOA Negative Caching TTL Extraktion
|
||||
run_test "extract_soa_negative_ttl liest 3600s aus SOA und meldet COMPLIANT" '
|
||||
SOA_LINE="@ IN SOA ns1.example.de. admin.example.de. 2026091501 7200 3600 1209600 3600"
|
||||
extract_soa_negative_ttl "$SOA_LINE" | grep -q "NEG_TTL=3600s | COMPLIANT=YES"
|
||||
'
|
||||
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
if [ "$FAILED_TESTS" -eq 0 ]; then
|
||||
echo -e "${GREEN}🎉 ERFOLG: Alle ${PASSED_TESTS}/${TOTAL_TESTS} Tests in DNS 04 erfolgreich bestanden!${NC}"
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
exit 0
|
||||
else
|
||||
echo -e "${RED}❌ FEHLER: Skript fehlgeschlagen mit Status $RET${NC}"
|
||||
echo -e "${RED}❌ FEHLER: ${FAILED_TESTS} von ${TOTAL_TESTS} Tests fehlgeschlagen.${NC}"
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -1,18 +1,45 @@
|
|||
# DNS 05: A & AAAA Records (IPv4 / IPv6)
|
||||
# DNS 05: A- & AAAA-Records (IPv4 & IPv6 Dual-Stack)
|
||||
|
||||
Willkommen zum Kurs **DNS-Records, Domain Name System & E-Mail-Sicherheit** im IT-Praxisportal!
|
||||
Willkommen zu **Modul 05** des Kurses **DNS-Records, Domain Name System & E-Mail-Sicherheit**!
|
||||
|
||||
## 💡 Das Wichtigste in Kürze
|
||||
- **Ziel:** Das Fundament des Web: Hostname zu IPv4 (A) und IPv6 (AAAA) sowie DNS Round-Robin Lastverteilung.
|
||||
- **Wichtigstes Tool:** `named-checkzone`
|
||||
In diesem Modul lernst du die elementaren Vorwärts-Resource-Records des Internets kennen: **A-Records für IPv4** und **AAAA-Records für IPv6**. Du erfährst, wie moderne Dual-Stack-Architekturen aufgebaut werden, wie Round-Robin DNS funktioniert und führst ein automatisches Dual-Stack-Audit durch.
|
||||
|
||||
## 🎯 Deine Aufgaben (Checkliste)
|
||||
1. **Teilziel 1:** A-Record für IPv4 Hostadresse erstellen
|
||||
2. **Teilziel 2:** AAAA-Record mit komprimierter IPv6-Adresse anlegen
|
||||
3. **Teilziel 3:** DNS-Round-Robin für mehrere Webserver konfigurieren
|
||||
4. **Teilziel 4:** Forward-Lookup-Zonendatei syntaktisch validieren
|
||||
---
|
||||
|
||||
## 💡 1. Das Wichtigste in Kürze
|
||||
|
||||
### A-Record (IPv4 Address)
|
||||
- Weist einem Namen eine 32-Bit IPv4-Adresse zu (z.B. `192.0.2.1`).
|
||||
- Syntax: `www 3600 IN A 192.0.2.1`
|
||||
|
||||
### AAAA-Record (IPv6 Address)
|
||||
- Weist einem Namen eine 128-Bit IPv6-Adresse zu (z.B. `2001:db8::1`).
|
||||
- Heißt „Quad-A“, weil 128 Bit genau 4-mal so lang sind wie 32 Bit.
|
||||
- Syntax: `www 3600 IN AAAA 2001:db8::1`
|
||||
|
||||
### Round-Robin DNS
|
||||
Wenn du mehrere Server für Lastverteilung besitzt, erstellst du mehrere A-Records unter demselben Namen:
|
||||
```dns
|
||||
web 300 IN A 192.0.2.10
|
||||
web 300 IN A 192.0.2.11
|
||||
web 300 IN A 192.0.2.12
|
||||
```
|
||||
Der Nameserver liefert bei jeder Anfrage die Liste in rotierter Reihenfolge zurück.
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Deine Aufgaben in `aufgabe.sh`
|
||||
|
||||
1. **TODO 1 (`format_a_record`):** Validiere eine IPv4-Adresse und formatiere den BIND A-Record.
|
||||
2. **TODO 2 (`format_aaaa_record`):** Validiere eine IPv6-Adresse und formatiere den BIND AAAA-Record.
|
||||
3. **TODO 3 (`generate_round_robin_pool`):** Erzeuge mehrere A-Records für denselben Hostnamen.
|
||||
4. **TODO 4 (`audit_dual_stack`):** Prüfe eine Zonendatei darauf, ob alle IPv4-Hosts auch einen AAAA-Record besitzen.
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Tests ausführen
|
||||
|
||||
Führe im Terminal folgenden Befehl aus:
|
||||
```bash
|
||||
bash test_aufgabe.sh
|
||||
```
|
||||
|
|
|
|||
|
|
@ -1,21 +1,100 @@
|
|||
#!/usr/bin/env bash
|
||||
# ==============================================================================
|
||||
# ⚡ Resource Records - DNS 05: A & AAAA Records (IPv4 / IPv6)
|
||||
# 🌐 DNS 05: A- & AAAA-RECORDS (IPV4 & IPV6 DUAL-STACK)
|
||||
# ==============================================================================
|
||||
#
|
||||
# FORWARD LOOKUP: VON NAMEN ZUR IP
|
||||
# A- und AAAA-Records sind die Arbeitspferde des Internets:
|
||||
# - A-Record (Address):
|
||||
# * Bildet einen Hostnamen auf eine 32-Bit IPv4-Adresse ab (z.B. 192.0.2.1).
|
||||
# - AAAA-Record (Quad-A):
|
||||
# * Bildet einen Hostnamen auf eine 128-Bit IPv6-Adresse ab (z.B. 2001:db8::1).
|
||||
#
|
||||
# DUAL-STACK HOSTING:
|
||||
# Zeitgemäße Server veröffentlichen IMMER beide Record-Typen unter demselben Namen.
|
||||
# Clients nutzen "Happy Eyeballs" (RFC 6555 / 8305), um bevorzugt IPv6 zu nutzen.
|
||||
#
|
||||
# ROUND-ROBIN DNS:
|
||||
# Mehrere A- oder AAAA-Records für denselben Hostnamen (z.B. www) ermöglichen
|
||||
# einfache Lastverteilung. Nameserver rotieren die Reihenfolge bei jeder Antwort.
|
||||
# ==============================================================================
|
||||
|
||||
# 🎯 TEILZIEL 1: A-Record für IPv4 Hostadresse erstellen
|
||||
# 🎯 TEILZIEL 2: AAAA-Record mit komprimierter IPv6-Adresse anlegen
|
||||
# 🎯 TEILZIEL 3: DNS-Round-Robin für mehrere Webserver konfigurieren
|
||||
# 🎯 TEILZIEL 4: Forward-Lookup-Zonendatei syntaktisch validieren
|
||||
# 🎯 TEILZIEL 1 (TODO 1): Syntaktisch validen A-Record erzeugen
|
||||
# Funktion: format_a_record "$hostname" "$ipv4_address" "$ttl"
|
||||
# Parameter $1: Hostname (z.B. "www" oder "mail.firma.de.")
|
||||
# Parameter $2: IPv4-Adresse (z.B. "192.0.2.10")
|
||||
# Parameter $3: TTL in Sekunden (Standard: 3600)
|
||||
# Anforderungen:
|
||||
# - Prüfe, ob die IPv4-Adresse gültig ist (4 Zahlen 0-255 getrennt durch Punkte).
|
||||
# Wenn ungültig: return 1 mit Fehlermeldung "ERROR: Ungültige IPv4-Adresse".
|
||||
# - Formatiere den BIND-Record tabellarisch mit Tabulator oder Leerzeichen:
|
||||
# <hostname> <ttl> IN A <ipv4_address>
|
||||
format_a_record() {
|
||||
local host="$1"
|
||||
local ip="$2"
|
||||
local ttl="${3:-3600}"
|
||||
# TODO 1: Validiere IPv4 und formatiere den A-Record
|
||||
return 0
|
||||
}
|
||||
|
||||
configure_a_aaaa_records() {
|
||||
local domain="${1:-it-praxisportal.de}"
|
||||
echo "▶ Analysiere DNS-Konfiguration für $domain..."
|
||||
# 🎯 TEILZIEL 2 (TODO 2): Syntaktisch validen AAAA-Record erzeugen
|
||||
# Funktion: format_aaaa_record "$hostname" "$ipv6_address" "$ttl"
|
||||
# Parameter $1: Hostname (z.B. "www")
|
||||
# Parameter $2: IPv6-Adresse (z.B. "2001:db8::10")
|
||||
# Parameter $3: TTL in Sekunden (Standard: 3600)
|
||||
# Anforderungen:
|
||||
# - Prüfe grob, ob die IPv6 Doppelpunkte enthält und keine ungültigen Zeichen hat.
|
||||
# - Formatiere den Record:
|
||||
# <hostname> <ttl> IN AAAA <ipv6_address>
|
||||
format_aaaa_record() {
|
||||
local host="$1"
|
||||
local ipv6="$2"
|
||||
local ttl="${3:-3600}"
|
||||
# TODO 2: Validiere IPv6 und formatiere den AAAA-Record
|
||||
return 0
|
||||
}
|
||||
|
||||
# TODO: Implementiere die geforderten DNS-Prüfungen oder Record-Erstellungen
|
||||
return 1
|
||||
# 🎯 TEILZIEL 3 (TODO 3): Round-Robin Pool generieren
|
||||
# Funktion: generate_round_robin_pool "$hostname" "$ttl" "$ip1" "$ip2" ...
|
||||
# Parameter $1: Hostname (z.B. "app")
|
||||
# Parameter $2: TTL (z.B. 300)
|
||||
# Parameter $3..N: Liste von IPv4-Adressen
|
||||
# Anforderungen:
|
||||
# - Erzeuge für jede übergebene IP-Adresse einen eigenen A-Record für denselben Hostnamen.
|
||||
generate_round_robin_pool() {
|
||||
local host="$1"
|
||||
local ttl="$2"
|
||||
shift 2
|
||||
local ips=("$@")
|
||||
# TODO 3: Erzeuge für jede IP einen Record
|
||||
return 0
|
||||
}
|
||||
|
||||
# 🎯 TEILZIEL 4 (TODO 4): Dual-Stack Audit für eine Zonendatei
|
||||
# Funktion: audit_dual_stack "$zone_file"
|
||||
# Parameter $1: Pfad zu einer Datei mit Zoneneinträgen
|
||||
# Anforderungen:
|
||||
# - Finde alle Hostnamen, die einen A-Record besitzen.
|
||||
# - Prüfe, ob für denselben Hostnamen auch mindestens ein AAAA-Record existiert.
|
||||
# - Wenn alle A-Records auch AAAA haben:
|
||||
# Gib aus: "DUAL_STACK_AUDIT: OK (Alle IPv4-Hosts besitzen IPv6 AAAA)"
|
||||
# return 0
|
||||
# - Wenn mindestens ein Host nur A hat:
|
||||
# Gib aus: "DUAL_STACK_WARNUNG: Fehlendes IPv6 für Host(s): <liste_der_hosts>"
|
||||
# return 1
|
||||
audit_dual_stack() {
|
||||
local zfile="$1"
|
||||
# TODO 4: Analysiere die Zonendatei auf IPv6-Vollständigkeit
|
||||
return 0
|
||||
}
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
configure_a_aaaa_records "$@"
|
||||
echo "=== Test Teilziel 1 ==="
|
||||
format_a_record "www" "192.0.2.1" 3600
|
||||
echo ""
|
||||
echo "=== Test Teilziel 2 ==="
|
||||
format_aaaa_record "www" "2001:db8::1" 3600
|
||||
echo ""
|
||||
echo "=== Test Teilziel 3 ==="
|
||||
generate_round_robin_pool "web" 300 "192.0.2.11" "192.0.2.12" "192.0.2.13"
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
<link rel="icon" type="image/svg+xml" href="../../../assets/favicon.svg">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>DNS 05: A & AAAA Records (IPv4 / IPv6)</title>
|
||||
<title>DNS 05: A & AAAA Records</title>
|
||||
<link rel="stylesheet" href="../../../assets/style.css">
|
||||
</head>
|
||||
<body>
|
||||
|
|
@ -13,45 +13,99 @@
|
|||
<header>
|
||||
<div class="header-container">
|
||||
<div class="header-badge">⚡ Resource Records</div>
|
||||
<h1>DNS 05: A & AAAA Records (IPv4 / IPv6)</h1>
|
||||
<p>Das Fundament des Web: Hostname zu IPv4 (A) und IPv6 (AAAA) sowie DNS Round-Robin Lastverteilung.</p>
|
||||
<h1>DNS 05: A- & AAAA-Records (IPv4 & IPv6 Dual-Stack)</h1>
|
||||
<p>Beherrsche die fundamentalen Forward-Lookup Records: IPv4 32-Bit (A) und IPv6 128-Bit (AAAA), Round-Robin Load Balancing und Dual-Stack Auditing.</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="container">
|
||||
<!-- 1. THEORIE: A & AAAA -->
|
||||
<section class="card">
|
||||
<h2>💡 Theorie & Praxiskonzept</h2>
|
||||
<p>Das Fundament des Web: Hostname zu IPv4 (A) und IPv6 (AAAA) sowie DNS Round-Robin Lastverteilung.</p>
|
||||
<div class="code-container">
|
||||
<div class="code-header">
|
||||
<span>DNS Command & Syntax Reference</span>
|
||||
<span>Tool: named-checkzone</span>
|
||||
</div>
|
||||
<pre><code># Wichtige Befehle & Zonensyntax:
|
||||
dig named-checkzone
|
||||
named-checkzone example.com /etc/bind/zones/db.example.com</code></pre>
|
||||
<h2>🌐 1. Vorwärtsauflösung: A vs. AAAA</h2>
|
||||
<p>Über 95% aller DNS-Anfragen im Web betreffen A- und AAAA-Records. Moderne Webdienste müssen zwingend **Dual-Stack** erreichbar sein:</p>
|
||||
|
||||
<pre><code># BIND9 Zonendatei (db.it-praxisportal.de):
|
||||
$ORIGIN it-praxisportal.de.
|
||||
$TTL 3600
|
||||
|
||||
; IPv4 & IPv6 Dual-Stack für die Hauptdomain:
|
||||
@ IN A 188.245.100.5
|
||||
@ IN AAAA 2a01:4f8:c010:d::1
|
||||
|
||||
; Hostname www mit denselben Adressen:
|
||||
www IN A 188.245.100.5
|
||||
www IN AAAA 2a01:4f8:c010:d::1</code></pre>
|
||||
</section>
|
||||
|
||||
<!-- 2. THEORIE: ROUND ROBIN -->
|
||||
<section class="card">
|
||||
<h2>⚖️ 2. Round-Robin DNS Lastverteilung</h2>
|
||||
<p>Durch Hinterlegen mehrerer A-Records unter demselben Namen rotiert der Nameserver die Antwortreihenfolge (Round-Robin):</p>
|
||||
<pre><code># 3 Webserver-Knoten im Pool:
|
||||
app 300 IN A 192.0.2.11
|
||||
app 300 IN A 192.0.2.12
|
||||
app 300 IN A 192.0.2.13</code></pre>
|
||||
|
||||
<div class="box box-warnung">
|
||||
<div class="box-title">⚠️ Wichtig für die IHK-Prüfung: Grenzen von Round-Robin DNS</div>
|
||||
<p>DNS Round-Robin prüft <strong>keinen Server-Health-Status</strong>! Fällt Server 2 aus, sendet DNS weiterhin 33% der Nutzer auf die tote IP. Für Hochverfügbarkeit ist ein Hardware-/Cloud-Loadbalancer (Reverse Proxy) erforderlich.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 3. CHEAT-SHEET -->
|
||||
<section class="card">
|
||||
<h2>🎯 Aufgaben & Checkliste</h2>
|
||||
<h2>📑 3. Cheat-Sheet: A- & AAAA-Syntax</h2>
|
||||
<div style="overflow-x: auto;">
|
||||
<table style="width: 100%; border-collapse: collapse; font-size: 0.9rem; text-align: left;">
|
||||
<thead>
|
||||
<tr style="border-bottom: 2px solid var(--border-color); background: var(--bg-subtle);">
|
||||
<th style="padding: 10px;">Record</th>
|
||||
<th style="padding: 10px;">Adressformat</th>
|
||||
<th style="padding: 10px;">Bitbreite</th>
|
||||
<th style="padding: 10px;">Beispiel</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr style="border-bottom: 1px solid var(--border-color);">
|
||||
<td style="padding: 10px;"><code>A</code></td>
|
||||
<td style="padding: 10px;">Dotted-Decimal (IPv4)</td>
|
||||
<td style="padding: 10px;">32 Bit (4 Oktette)</td>
|
||||
<td style="padding: 10px;"><code>web IN A 192.0.2.1</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 10px;"><code>AAAA</code></td>
|
||||
<td style="padding: 10px;">Hexadezimal / Doppelpunkt (IPv6)</td>
|
||||
<td style="padding: 10px;">128 Bit (16 Oktette)</td>
|
||||
<td style="padding: 10px;"><code>web IN AAAA 2001:db8::1</code></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 4. AUFGABEN CHECKLISTE -->
|
||||
<section class="card">
|
||||
<h2>🎯 Aufgaben & Checkliste in <code>aufgabe.sh</code></h2>
|
||||
<div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t1">
|
||||
<label class="task-label" for="t1"><strong>TODO 1:</strong> A-Record für IPv4 Hostadresse erstellen</label>
|
||||
</div><div class="task-item">
|
||||
<label class="task-label" for="t1"><strong>TODO 1:</strong> <code>format_a_record(host, ip, ttl)</code>: IPv4 validieren und A-Record formatieren.</label>
|
||||
</div>
|
||||
<div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t2">
|
||||
<label class="task-label" for="t2"><strong>TODO 2:</strong> AAAA-Record mit komprimierter IPv6-Adresse anlegen</label>
|
||||
</div><div class="task-item">
|
||||
<label class="task-label" for="t2"><strong>TODO 2:</strong> <code>format_aaaa_record(host, ipv6, ttl)</code>: IPv6 AAAA-Record formatieren.</label>
|
||||
</div>
|
||||
<div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t3">
|
||||
<label class="task-label" for="t3"><strong>TODO 3:</strong> DNS-Round-Robin für mehrere Webserver konfigurieren</label>
|
||||
</div><div class="task-item">
|
||||
<label class="task-label" for="t3"><strong>TODO 3:</strong> <code>generate_round_robin_pool(...)</code>: Multi-A Record Pool generieren.</label>
|
||||
</div>
|
||||
<div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t4">
|
||||
<label class="task-label" for="t4"><strong>TODO 4:</strong> Forward-Lookup-Zonendatei syntaktisch validieren</label>
|
||||
<label class="task-label" for="t4"><strong>TODO 4:</strong> <code>audit_dual_stack(zone_file)</code>: Dual-Stack Abdeckung aller Hosts prüfen.</label>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 25px; display: flex; gap: 12px; flex-wrap: wrap;">
|
||||
<a href="../../../workspace.html?course=dns_records&track=track_2_core_records&chapter=05_a_und_aaaa_records" class="btn" style="background: #0284c7; color: #fff; font-weight: 700;">💻 In Web-IDE öffnen →</a>
|
||||
<a href="../06_cname_canonical_name_und_alias_regeln/index.html" class="btn btn-secondary">Weiter zu Modul →</a>
|
||||
<a href="../../../workspace.html?course=dns_records&track=track_2_core_records&chapter=05_a_und_aaaa_records" class="btn" style="background: #1d4ed8; color: #fff; font-weight: 700;">In Web-IDE öffnen →</a>
|
||||
<a href="../06_cname_canonical_name_und_alias_regeln/index.html" class="btn btn-secondary">Weiter zu Modul 06 →</a>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
|
|
|||
|
|
@ -1,97 +1,84 @@
|
|||
#!/usr/bin/env bash
|
||||
# ==============================================================================
|
||||
# MUSTERLÖSUNG: DNS 05: A & AAAA Records (IPv4 / IPv6)
|
||||
# MUSTERLÖSUNG: DNS 05: A- & AAAA-RECORDS (IPV4 & IPV6 DUAL-STACK)
|
||||
# ==============================================================================
|
||||
|
||||
configure_a_aaaa_records() {
|
||||
local domain="${1:-it-praxisportal.de}"
|
||||
local target_dir="${2:-.}"
|
||||
format_a_record() {
|
||||
local host="$1"
|
||||
local ip="$2"
|
||||
local ttl="${3:-3600}"
|
||||
|
||||
echo "▶ Starte professionelle DNS-Operation für $domain..."
|
||||
# IPv4 Validierung
|
||||
local valid_ip=false
|
||||
if [[ "$ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then
|
||||
valid_ip=true
|
||||
IFS='.' read -ra octets <<< "$ip"
|
||||
for oct in "${octets[@]}"; do
|
||||
if [ "$oct" -gt 255 ]; then
|
||||
valid_ip=false
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
case "05_a_und_aaaa_records" in
|
||||
*01_dns_hierarchie*)
|
||||
echo "Host: www | SLD: it-praxisportal | TLD: de | Root: ."
|
||||
;;
|
||||
*02_rekursive*)
|
||||
echo "Simulation Iteration: Root (.) -> de. -> ns1.it-praxisportal.de -> 188.245.100.5"
|
||||
;;
|
||||
*03_ports_udp*)
|
||||
echo "Protocol: UDP/53 (Standard) | Fallback: TCP/53 | EDNS0 Buffer: 4096 bytes"
|
||||
;;
|
||||
*04_ttl*)
|
||||
echo "TTL Current: 86400s | Migration Planned TTL: 300s | Neg Cache: 3600s"
|
||||
;;
|
||||
*05_a_und_aaaa*)
|
||||
cat << 'ZONE' > "$target_dir/zone_records.txt"
|
||||
@ IN A 188.245.100.5
|
||||
@ IN AAAA 2a01:4f8:c010:d::1
|
||||
www IN A 188.245.100.5
|
||||
ZONE
|
||||
;;
|
||||
*06_cname*)
|
||||
echo "api IN CNAME app.it-praxisportal.de." > "$target_dir/cname_record.txt"
|
||||
;;
|
||||
*07_soa*)
|
||||
cat << 'ZONE' > "$target_dir/soa_record.txt"
|
||||
@ IN SOA ns1.it-praxisportal.de. hostmaster.it-praxisportal.de. (
|
||||
2026091201 ; Serial YYYYMMDDNN
|
||||
7200 ; Refresh (2h)
|
||||
3600 ; Retry (1h)
|
||||
1209600 ; Expire (2w)
|
||||
3600 ; Negative Cache TTL (1h)
|
||||
)
|
||||
ZONE
|
||||
;;
|
||||
*08_ns_und_ptr*)
|
||||
echo "100.245.188.in-addr.arpa. IN PTR mail.it-praxisportal.de." > "$target_dir/ptr_record.txt"
|
||||
;;
|
||||
*09_mx*)
|
||||
echo "@ IN MX 10 mail.it-praxisportal.de." > "$target_dir/mx_record.txt"
|
||||
echo "@ IN MX 20 backup-mail.it-praxisportal.de." >> "$target_dir/mx_record.txt"
|
||||
;;
|
||||
*10_txt_records*)
|
||||
echo '@ IN TXT "v=spf1 mx ip4:188.245.100.5 include:_spf.google.com -all"' > "$target_dir/spf_record.txt"
|
||||
;;
|
||||
*11_dkim*)
|
||||
echo 's1._domainkey IN TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz..."' > "$target_dir/dkim_record.txt"
|
||||
;;
|
||||
*12_dmarc*)
|
||||
echo '_dmarc IN TXT "v=DMARC1; p=reject; rua=mailto:dmarc-reports@it-praxisportal.de; pct=100; aspf=s"' > "$target_dir/dmarc_record.txt"
|
||||
;;
|
||||
*13_srv*)
|
||||
echo '_ldap._tcp.dc._msdcs.corp IN SRV 0 100 389 dc01.corp.it-praxisportal.de.' > "$target_dir/srv_record.txt"
|
||||
;;
|
||||
*14_caa*)
|
||||
echo '@ IN CAA 0 issue "letsencrypt.org"' > "$target_dir/caa_record.txt"
|
||||
echo '@ IN CAA 0 iodef "mailto:security@it-praxisportal.de"' >> "$target_dir/caa_record.txt"
|
||||
;;
|
||||
*15_dns_troubleshooting*)
|
||||
echo "STATUS: NOERROR | ANSWER: 1 | FLAGS: qr aa rd ra"
|
||||
;;
|
||||
*16_master*)
|
||||
cat << 'REPORT' > "$target_dir/dns_audit_report.json"
|
||||
{
|
||||
"domain": "it-praxisportal.de",
|
||||
"audit_status": "PASSED",
|
||||
"score": 100,
|
||||
"soa_serial_valid": true,
|
||||
"ns_redundancy": 2,
|
||||
"email_security": {
|
||||
"spf": "valid (-all)",
|
||||
"dkim": "present",
|
||||
"dmarc": "enforced (p=reject)"
|
||||
},
|
||||
"dnssec": "active"
|
||||
}
|
||||
REPORT
|
||||
;;
|
||||
esac
|
||||
if [ "$valid_ip" = false ]; then
|
||||
echo "ERROR: Ungültige IPv4-Adresse"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "✅ DNS-Operation erfolgreich abgeschlossen."
|
||||
printf "%-15s %-7s IN A %s\n" "$host" "$ttl" "$ip"
|
||||
return 0
|
||||
}
|
||||
|
||||
format_aaaa_record() {
|
||||
local host="$1"
|
||||
local ipv6="$2"
|
||||
local ttl="${3:-3600}"
|
||||
|
||||
if [[ ! "$ipv6" =~ : ]] || [[ "$ipv6" =~ [^0-9a-fA-F:] ]]; then
|
||||
echo "ERROR: Ungültige IPv6-Adresse"
|
||||
return 1
|
||||
fi
|
||||
|
||||
printf "%-15s %-7s IN AAAA %s\n" "$host" "$ttl" "$ipv6"
|
||||
return 0
|
||||
}
|
||||
|
||||
generate_round_robin_pool() {
|
||||
local host="$1"
|
||||
local ttl="$2"
|
||||
shift 2
|
||||
local ips=("$@")
|
||||
|
||||
for ip in "${ips[@]}"; do
|
||||
format_a_record "$host" "$ip" "$ttl"
|
||||
done
|
||||
return 0
|
||||
}
|
||||
|
||||
audit_dual_stack() {
|
||||
local zfile="$1"
|
||||
[ ! -f "$zfile" ] && return 1
|
||||
|
||||
# Finde alle A-Hosts (Spalte 1)
|
||||
local a_hosts=($(awk '$3 == "A" || $4 == "A" { print $1 }' "$zfile" | sort -u))
|
||||
local missing=()
|
||||
|
||||
for h in "${a_hosts[@]}"; do
|
||||
if ! awk -v host="$h" '($1 == host) && ($3 == "AAAA" || $4 == "AAAA") { found=1 } END { exit !found }' "$zfile"; then
|
||||
missing+=("$h")
|
||||
fi
|
||||
done
|
||||
|
||||
if [ ${#missing[@]} -eq 0 ]; then
|
||||
echo "DUAL_STACK_AUDIT: OK (Alle IPv4-Hosts besitzen IPv6 AAAA)"
|
||||
return 0
|
||||
else
|
||||
echo "DUAL_STACK_WARNUNG: Fehlendes IPv6 für Host(s): ${missing[*]}"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
configure_a_aaaa_records "$@"
|
||||
format_a_record "$@"
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -1,36 +1,113 @@
|
|||
#!/usr/bin/env bash
|
||||
# ==============================================================================
|
||||
# TESTSUITE: DNS 05: A & AAAA Records (IPv4 / IPv6)
|
||||
# 🧪 TESTSUITE: DNS 05 - A- & AAAA-RECORDS
|
||||
# ==============================================================================
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
BLUE='\033[0;34m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
TARGET_SCRIPT="${1:-aufgabe.sh}"
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
echo -e "${BLUE}🧪 TESTSUITE: DNS 05: A & AAAA Records (IPv4 / IPv6)${NC}"
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
TOTAL_TESTS=0
|
||||
PASSED_TESTS=0
|
||||
FAILED_TESTS=0
|
||||
|
||||
TEST_DIR=$(mktemp -d /tmp/dns_test_XXXXXX)
|
||||
trap 'rm -rf "$TEST_DIR"' EXIT
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
echo -e "${BLUE}🧪 TESTSUITE: DNS 05: A- & AAAA-Records (IPv4 / IPv6)${NC}"
|
||||
echo -e "${BLUE} Ziel-Skript: ${YELLOW}${TARGET_SCRIPT}${NC}"
|
||||
echo -e "${BLUE}================================================================${NC}\n"
|
||||
|
||||
if [ ! -f "$SCRIPT_DIR/$TARGET_SCRIPT" ]; then
|
||||
echo -e "${RED}❌ Fehler: Datei '$TARGET_SCRIPT' nicht gefunden!${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
source "$SCRIPT_DIR/$TARGET_SCRIPT"
|
||||
|
||||
# Führe Funktion aus
|
||||
configure_a_aaaa_records "example.com" "$TEST_DIR"
|
||||
RET=$?
|
||||
run_test() {
|
||||
local name="$1"
|
||||
local cmd="$2"
|
||||
((TOTAL_TESTS++))
|
||||
echo -e "▶ Test ${TOTAL_TESTS}: ${name}"
|
||||
if eval "$cmd"; then
|
||||
echo -e " ${GREEN}✓ BESTANDEN${NC}\n"
|
||||
((PASSED_TESTS++))
|
||||
else
|
||||
echo -e " ${RED}✗ FEHLGESCHLAGEN${NC}\n"
|
||||
((FAILED_TESTS++))
|
||||
fi
|
||||
}
|
||||
|
||||
if [ $RET -eq 0 ]; then
|
||||
echo -e "${GREEN}✓ Test 1: Funktion configure_a_aaaa_records existiert und gibt 0 zurück.${NC}"
|
||||
echo -e "${GREEN}✓ Test 2: DNS Resource Records / Syntax validiert.${NC}"
|
||||
echo -e "${GREEN}✓ Test 3: Teilziele 1 bis 4 erfolgreich abgeschlossen.${NC}"
|
||||
echo -e "${GREEN}🎉 ERFOLG: Alle DNS-Prüfungen in DNS 05: A & AAAA Records (IPv4 / IPv6) bestanden!${NC}"
|
||||
# TEST 1: Valider A-Record
|
||||
run_test "format_a_record erzeugt standardkonformen A-Record" '
|
||||
OUT=$(format_a_record "www" "192.0.2.10" 3600)
|
||||
echo "$OUT" | grep -q "www" && \
|
||||
echo "$OUT" | grep -q "3600" && \
|
||||
echo "$OUT" | grep -q "IN" && \
|
||||
echo "$OUT" | grep -q "A" && \
|
||||
echo "$OUT" | grep -q "192.0.2.10"
|
||||
'
|
||||
|
||||
# TEST 2: Invalide IPv4 (Oktett > 255)
|
||||
run_test "format_a_record lehnt ungültige IPv4 (256.0.0.1) ab" '
|
||||
format_a_record "host" "256.0.0.1" 3600 | grep -q "ERROR: Ungültige IPv4"
|
||||
'
|
||||
|
||||
# TEST 3: Valider AAAA-Record
|
||||
run_test "format_aaaa_record erzeugt AAAA-Record" '
|
||||
OUT=$(format_aaaa_record "app" "2001:db8::cafe" 7200)
|
||||
echo "$OUT" | grep -q "app" && \
|
||||
echo "$OUT" | grep -q "7200" && \
|
||||
echo "$OUT" | grep -q "AAAA" && \
|
||||
echo "$OUT" | grep -q "2001:db8::cafe"
|
||||
'
|
||||
|
||||
# TEST 4: Round Robin Pool
|
||||
run_test "generate_round_robin_pool erzeugt 3 A-Records für www" '
|
||||
OUT=$(generate_round_robin_pool "www" 300 "192.0.2.1" "192.0.2.2" "192.0.2.3")
|
||||
COUNT=$(echo "$OUT" | grep -c "IN A")
|
||||
[ "$COUNT" -eq 3 ]
|
||||
'
|
||||
|
||||
# TEST 5: Dual Stack Audit (Vollständig)
|
||||
run_test "audit_dual_stack meldet OK wenn alle A-Hosts auch AAAA haben" '
|
||||
TMP_ZONE=$(mktemp /tmp/ds_zone_XXXXXX)
|
||||
cat << "EOF" > "$TMP_ZONE"
|
||||
www 3600 IN A 192.0.2.10
|
||||
www 3600 IN AAAA 2001:db8::10
|
||||
api 3600 IN A 192.0.2.20
|
||||
api 3600 IN AAAA 2001:db8::20
|
||||
EOF
|
||||
OUT=$(audit_dual_stack "$TMP_ZONE")
|
||||
rm -f "$TMP_ZONE"
|
||||
echo "$OUT" | grep -q "DUAL_STACK_AUDIT: OK"
|
||||
'
|
||||
|
||||
# TEST 6: Dual Stack Audit (Warnung bei fehlendem AAAA)
|
||||
run_test "audit_dual_stack warnt wenn mail nur A hat" '
|
||||
TMP_ZONE=$(mktemp /tmp/ds_zone_XXXXXX)
|
||||
cat << "EOF" > "$TMP_ZONE"
|
||||
www 3600 IN A 192.0.2.10
|
||||
www 3600 IN AAAA 2001:db8::10
|
||||
mail 3600 IN A 192.0.2.25
|
||||
EOF
|
||||
OUT=$(audit_dual_stack "$TMP_ZONE")
|
||||
rm -f "$TMP_ZONE"
|
||||
echo "$OUT" | grep -q "DUAL_STACK_WARNUNG" && \
|
||||
echo "$OUT" | grep -q "mail"
|
||||
'
|
||||
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
if [ "$FAILED_TESTS" -eq 0 ]; then
|
||||
echo -e "${GREEN}🎉 ERFOLG: Alle ${PASSED_TESTS}/${TOTAL_TESTS} Tests in DNS 05 erfolgreich bestanden!${NC}"
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
exit 0
|
||||
else
|
||||
echo -e "${RED}❌ FEHLER: Skript fehlgeschlagen mit Status $RET${NC}"
|
||||
echo -e "${RED}❌ FEHLER: ${FAILED_TESTS} von ${TOTAL_TESTS} Tests fehlgeschlagen.${NC}"
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -1,18 +1,50 @@
|
|||
# DNS 06: CNAME Alias & das Apex-Problem
|
||||
# DNS 06: CNAME (Canonical Name) & das Zone-Apex Problem
|
||||
|
||||
Willkommen zum Kurs **DNS-Records, Domain Name System & E-Mail-Sicherheit** im IT-Praxisportal!
|
||||
Willkommen zu **Modul 06** des Kurses **DNS-Records, Domain Name System & E-Mail-Sicherheit**!
|
||||
|
||||
## 💡 Das Wichtigste in Kürze
|
||||
- **Ziel:** Kanonische Namen (CNAME), Zeiger auf Hostnames, Alias-Kollisionen und das Apex-/Root-Domain-Verbot (RFC 1912).
|
||||
- **Wichtigstes Tool:** `dig CNAME`
|
||||
In diesem Modul meisterst du eines der anspruchsvollsten Themen der DNS-Konfiguration: **CNAME-Aliase**, die strikte **Koexistenz-Regel** nach RFC 1912/2181, das berüchtigte **Zone-Apex-Problem (`@`)** und das Auflösen von CNAME-Ketten.
|
||||
|
||||
## 🎯 Deine Aufgaben (Checkliste)
|
||||
1. **Teilziel 1:** CNAME-Record für Subdomain (api.domain.de -> elb.aws.com) erstellen
|
||||
2. **Teilziel 2:** RFC-Kollision prüfen (CNAME darf nicht mit anderen Records koexistieren)
|
||||
3. **Teilziel 3:** Apex/Root-Domain Problem (example.de vs. MX/SOA) verstehen
|
||||
4. **Teilziel 4:** Moderne ALIAS / ANAME Provider-Lösungen analysieren
|
||||
---
|
||||
|
||||
## 💡 1. Das Wichtigste in Kürze
|
||||
|
||||
### Die RFC 1912 / 2181 CNAME-Regel
|
||||
> **„Wenn für einen Node ein CNAME existiert, darf kein anderer Record-Typ für denselben Node existieren!“**
|
||||
|
||||
Das bedeutet:
|
||||
- `blog IN CNAME myhost.com.` ist erlaubt.
|
||||
- Wenn du aber zusätzlich `blog IN TXT "info"` oder `blog IN MX 10 mail.de.` hinzufügst, verletzt du den DNS-Standard! Resolver verwerfen entweder die zusätzlichen Records oder den CNAME komplett.
|
||||
|
||||
### Das Zone-Apex Problem (`@`)
|
||||
Der Zone-Apex ist der Ursprung deiner Domain (z.B. `it-praxisportal.de` ohne Subdomain).
|
||||
- Jede Zone MUSS am Apex zwingend **SOA** und mindestens zwei **NS-Records** besitzen.
|
||||
- Wegen der CNAME-Regel darf am Zone-Apex **NIEMALS ein CNAME** angelegt werden!
|
||||
- *Lösung für moderne Cloud-Infrastrukturen:* Verwende statische A/AAAA-Records oder sogenannte **ALIAS / ANAME** Pseudo-Records deines DNS-Providers (z.B. Cloudflare CNAME Flattening, AWS Route 53 Alias).
|
||||
|
||||
### Der Schlusspunkt bei Zielen
|
||||
```dns
|
||||
; KORREKT (Absoluter FQDN):
|
||||
app IN CNAME lb-123.amazonaws.com.
|
||||
|
||||
; FALSCH (Relativer Hostname):
|
||||
app IN CNAME lb-123.amazonaws.com
|
||||
; BIND ergänzt $ORIGIN: lb-123.amazonaws.com.it-praxisportal.de. (Dead Link!)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Deine Aufgaben in `aufgabe.sh`
|
||||
|
||||
1. **TODO 1 (`format_cname_record`):** Formatiere einen CNAME-Record und stelle sicher, dass das Ziel mit einem Punkt `.` endet.
|
||||
2. **TODO 2 (`validate_apex_cname_rule`):** Verhindere das Erstellen von CNAME-Records auf dem Zone-Apex (`@`).
|
||||
3. **TODO 3 (`detect_cname_collisions`):** Durchsuche eine Zonendatei nach illegalen Koexistenz-Konflikten.
|
||||
4. **TODO 4 (`trace_cname_chain`):** Verfolge eine mehrstufige CNAME-Kette bis zum finalen Ziel und erkenne Endlosschleifen.
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Tests ausführen
|
||||
|
||||
Führe im Terminal folgenden Befehl aus:
|
||||
```bash
|
||||
bash test_aufgabe.sh
|
||||
```
|
||||
|
|
|
|||
|
|
@ -1,21 +1,101 @@
|
|||
#!/usr/bin/env bash
|
||||
# ==============================================================================
|
||||
# ⚡ CNAME & Aliase - DNS 06: CNAME Alias & das Apex-Problem
|
||||
# 🌐 DNS 06: CNAME (CANONICAL NAME) & DAS ZONE-APEX PROBLEM
|
||||
# ==============================================================================
|
||||
#
|
||||
# WAS IST EIN CNAME?
|
||||
# Ein CNAME (Canonical Name) ist ein Alias für einen bereits existierenden,
|
||||
# echten Hostnamen (A- oder AAAA-Ziel).
|
||||
# Beispiel:
|
||||
# blog.firma.de. IN CNAME server01.cloudhoster.com.
|
||||
#
|
||||
# ⚠️ DIE GOLDENE CNAME-REGEL (RFC 1912 & RFC 2181):
|
||||
# "If a CNAME record exists for a node, no other data may exist for that node."
|
||||
# Das bedeutet: Ein Hostname, der ein CNAME ist, darf KEINE anderen Records
|
||||
# (weder A, AAAA, MX, TXT noch SOA oder NS) besitzen!
|
||||
#
|
||||
# 💥 DAS ZONE-APEX PROBLEM:
|
||||
# Auf dem Ursprung der Zone (Zone Apex "@" bzw. firma.de) müssen zwingend
|
||||
# ein SOA-Record und mindestens zwei NS-Records liegen.
|
||||
# Wegen der CNAME-Regel darf auf dem Zone-Apex NIEMALS ein CNAME liegen!
|
||||
# FALSCH: @ IN CNAME my-loadbalancer.aws.com. <- ILLEGAL!
|
||||
# RICHTIG: Verwende A/AAAA oder provider-spezifische ALIAS/ANAME Records.
|
||||
# ==============================================================================
|
||||
|
||||
# 🎯 TEILZIEL 1: CNAME-Record für Subdomain (api.domain.de -> elb.aws.com) erstellen
|
||||
# 🎯 TEILZIEL 2: RFC-Kollision prüfen (CNAME darf nicht mit anderen Records koexistieren)
|
||||
# 🎯 TEILZIEL 3: Apex/Root-Domain Problem (example.de vs. MX/SOA) verstehen
|
||||
# 🎯 TEILZIEL 4: Moderne ALIAS / ANAME Provider-Lösungen analysieren
|
||||
# 🎯 TEILZIEL 1 (TODO 1): Validen CNAME-Record erzeugen
|
||||
# Funktion: format_cname_record "$alias" "$canonical_target" "$ttl"
|
||||
# Parameter $1: Alias-Name (z.B. "shop" oder "shop.firma.de.")
|
||||
# Parameter $2: Echtes Ziel (z.B. "lb-production.cloud.de")
|
||||
# Parameter $3: TTL (Standard: 3600)
|
||||
# Anforderungen:
|
||||
# - Das Ziel (canonical_target) MUSS einen abschließenden Punkt "." besitzen.
|
||||
# Falls der Punkt fehlt, hänge ihn automatisch an!
|
||||
# - Formatiere den Record:
|
||||
# <alias> <ttl> IN CNAME <canonical_target>.
|
||||
format_cname_record() {
|
||||
local alias="$1"
|
||||
local target="$2"
|
||||
local ttl="${3:-3600}"
|
||||
# TODO 1: Schlusspunkt sicherstellen und CNAME formatieren
|
||||
return 0
|
||||
}
|
||||
|
||||
validate_cname_rules() {
|
||||
local domain="${1:-it-praxisportal.de}"
|
||||
echo "▶ Analysiere DNS-Konfiguration für $domain..."
|
||||
# 🎯 TEILZIEL 2 (TODO 2): Zone-Apex Regel prüfen
|
||||
# Funktion: validate_apex_cname_rule "$node_name" "$record_type"
|
||||
# Parameter $1: Node-Name (z.B. "@", "firma.de.", "www", "blog")
|
||||
# Parameter $2: Record-Typ (z.B. "CNAME", "A", "MX")
|
||||
# Anforderungen:
|
||||
# - Wenn record_type == "CNAME" UND (node_name == "@" oder node_name ist die Root-Domain ohne Subdomain):
|
||||
# Gib aus: "ERROR_APEX_VIOLATION: CNAME am Zone-Apex verboten! Kollidiert mit SOA und NS Records."
|
||||
# return 1
|
||||
# - Andernfalls:
|
||||
# Gib aus: "VALID: CNAME auf Subdomain erlaubt."
|
||||
# return 0
|
||||
validate_apex_cname_rule() {
|
||||
local node="$1"
|
||||
local rtype=$(echo "$2" | tr '[:lower:]' '[:upper:]')
|
||||
# TODO 2: Prüfe die Apex-Restriktion
|
||||
return 0
|
||||
}
|
||||
|
||||
# TODO: Implementiere die geforderten DNS-Prüfungen oder Record-Erstellungen
|
||||
return 1
|
||||
# 🎯 TEILZIEL 3 (TODO 3): CNAME-Kollisionen in Zonendatei aufspüren
|
||||
# Funktion: detect_cname_collisions "$zone_file"
|
||||
# Parameter $1: Pfad zu einer Zonendatei
|
||||
# Anforderungen:
|
||||
# - Suche nach Zeilen mit "CNAME".
|
||||
# - Prüfe, ob für denselben Hostnamen in der Datei noch ein anderer Record (A, AAAA, MX, TXT) existiert.
|
||||
# - Wenn Kollisionen gefunden werden:
|
||||
# Gib aus: "CNAME_COLLISION_FOUND: Node '<host>' besitzt unerlaubt CNAME und weitere Records!"
|
||||
# return 1
|
||||
# - Wenn keine Kollisionen:
|
||||
# Gib aus: "CNAME_INTEGRITY_OK: Keine Koexistenz-Konflikte gefunden."
|
||||
# return 0
|
||||
detect_cname_collisions() {
|
||||
local zfile="$1"
|
||||
# TODO 3: Prüfe Koexistenz-Verletzungen
|
||||
return 0
|
||||
}
|
||||
|
||||
# 🎯 TEILZIEL 4 (TODO 4): CNAME-Kette verfolgen & Schleifen erkennen
|
||||
# Funktion: trace_cname_chain "$start_alias" "$cname_map_file"
|
||||
# Parameter $1: Start-Alias (z.B. "web")
|
||||
# Parameter $2: Datei mit Zuordnungen im Format "alias target" (z.B. "web app", "app cdn", "cdn srv01.de.")
|
||||
# Anforderungen:
|
||||
# - Folge der Kette schrittweise bis zum finalen Ziel (das kein weiterer Alias ist).
|
||||
# - Erkenne Endlosschleifen (Loops wie a -> b -> a).
|
||||
# - Ausgabe bei Erfolg: "RESOLVED: <start> -> ... -> <final_target>"
|
||||
# - Ausgabe bei Loop: "ERROR_LOOP_DETECTED: Zirkuläre CNAME-Referenz bei '<node>'!" und return 1
|
||||
trace_cname_chain() {
|
||||
local current="$1"
|
||||
local map_file="$2"
|
||||
# TODO 4: Verfolge die CNAME-Kette
|
||||
return 0
|
||||
}
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
validate_cname_rules "$@"
|
||||
echo "=== Test Teilziel 1 ==="
|
||||
format_cname_record "shop" "lb.cloud.de" 3600
|
||||
echo ""
|
||||
echo "=== Test Teilziel 2 ==="
|
||||
validate_apex_cname_rule "@" "CNAME"
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
<link rel="icon" type="image/svg+xml" href="../../../assets/favicon.svg">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>DNS 06: CNAME Alias & das Apex-Problem</title>
|
||||
<title>DNS 06: CNAME & Zone-Apex Problem</title>
|
||||
<link rel="stylesheet" href="../../../assets/style.css">
|
||||
</head>
|
||||
<body>
|
||||
|
|
@ -12,46 +12,102 @@
|
|||
<div class="app-main">
|
||||
<header>
|
||||
<div class="header-container">
|
||||
<div class="header-badge">⚡ CNAME & Aliase</div>
|
||||
<h1>DNS 06: CNAME Alias & das Apex-Problem</h1>
|
||||
<p>Kanonische Namen (CNAME), Zeiger auf Hostnames, Alias-Kollisionen und das Apex-/Root-Domain-Verbot (RFC 1912).</p>
|
||||
<div class="header-badge">⚡ Resource Records</div>
|
||||
<h1>DNS 06: CNAME Alias & das Zone-Apex Problem</h1>
|
||||
<p>Verstehe die strikte RFC-Koexistenzregel für CNAMEs, warum CNAME auf der Root-Domain (@) verboten ist und wie ALIAS-Records das Problem lösen.</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="container">
|
||||
<!-- 1. THEORIE: CNAME GRUNDLAGEN -->
|
||||
<section class="card">
|
||||
<h2>💡 Theorie & Praxiskonzept</h2>
|
||||
<p>Kanonische Namen (CNAME), Zeiger auf Hostnames, Alias-Kollisionen und das Apex-/Root-Domain-Verbot (RFC 1912).</p>
|
||||
<div class="code-container">
|
||||
<div class="code-header">
|
||||
<span>DNS Command & Syntax Reference</span>
|
||||
<span>Tool: dig CNAME</span>
|
||||
</div>
|
||||
<pre><code># Wichtige Befehle & Zonensyntax:
|
||||
dig dig CNAME
|
||||
named-checkzone example.com /etc/bind/zones/db.example.com</code></pre>
|
||||
<h2>🔗 1. Wie ein CNAME funktioniert</h2>
|
||||
<p>Ein <code>CNAME</code> (Canonical Name) ist ein reiner Verweiszeiger. Er enthält selbst keine IP-Adresse, sondern leitet den Resolver an ein anderes Ziel weiter:</p>
|
||||
|
||||
<pre><code># BIND Zonendatei:
|
||||
shop IN CNAME stores.shopify.com. # shop.firma.de -> stores.shopify.com
|
||||
webmail IN CNAME mail.google.com. # webmail.firma.de -> mail.google.com</code></pre>
|
||||
</section>
|
||||
|
||||
<!-- 2. THEORIE: DAS ZONE-APEX PROBLEM -->
|
||||
<section class="card">
|
||||
<h2>💥 2. Das Zone-Apex Problem & RFC 1912</h2>
|
||||
<div class="box box-warnung">
|
||||
<div class="box-title">⚠️ Die eiserne Regel: Keine Koexistenz für CNAMEs</div>
|
||||
<p>Ein CNAME darf <strong>niemals</strong> mit anderen Record-Typen auf demselben Node koexistieren! Da auf der Hauptdomain <code>@</code> aber zwingend SOA und NS liegen müssen, ist ein CNAME auf <code>@</code> standardwidrig und zerstört die Namensauflösung der gesamten Zone.</p>
|
||||
</div>
|
||||
|
||||
<pre><code>; ❌ SCHWERER FEHLER (Zonendatei lädt nicht in BIND):
|
||||
@ IN SOA ns1.firma.de. admin.firma.de. (...)
|
||||
@ IN NS ns1.firma.de.
|
||||
@ IN CNAME d12345.cloudfront.net. ; KOLLISION MIT SOA/NS!
|
||||
|
||||
; ✅ KORREKTE PRAXIS:
|
||||
@ IN A 192.0.2.1 ; Feste IP am Apex
|
||||
www IN CNAME d12345.cloudfront.net. ; CNAME auf Subdomain www</code></pre>
|
||||
</section>
|
||||
|
||||
<!-- 3. CHEAT-SHEET -->
|
||||
<section class="card">
|
||||
<h2>📑 3. Cheat-Sheet: CNAME Dos & Don'ts</h2>
|
||||
<div style="overflow-x: auto;">
|
||||
<table style="width: 100%; border-collapse: collapse; font-size: 0.9rem; text-align: left;">
|
||||
<thead>
|
||||
<tr style="border-bottom: 2px solid var(--border-color); background: var(--bg-subtle);">
|
||||
<th style="padding: 10px;">Regel</th>
|
||||
<th style="padding: 10px;">Status</th>
|
||||
<th style="padding: 10px;">Erklärung & RFC-Grund</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr style="border-bottom: 1px solid var(--border-color);">
|
||||
<td style="padding: 10px;"><code>blog IN CNAME ...</code></td>
|
||||
<td style="padding: 10px; color: #166534; font-weight: 700;">ERLAUBT</td>
|
||||
<td style="padding: 10px;">Reine Subdomain ohne andere Records</td>
|
||||
</tr>
|
||||
<tr style="border-bottom: 1px solid var(--border-color);">
|
||||
<td style="padding: 10px;"><code>@ IN CNAME ...</code></td>
|
||||
<td style="padding: 10px; color: #991b1b; font-weight: 700;">VERBOTEN</td>
|
||||
<td style="padding: 10px;">Zone-Apex kollidiert mit SOA und NS Records (RFC 1912)</td>
|
||||
</tr>
|
||||
<tr style="border-bottom: 1px solid var(--border-color);">
|
||||
<td style="padding: 10px;"><code>sub IN CNAME</code> + <code>sub IN TXT</code></td>
|
||||
<td style="padding: 10px; color: #991b1b; font-weight: 700;">VERBOTEN</td>
|
||||
<td style="padding: 10px;">Keine Koexistenz von CNAME mit anderen Daten</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 10px;">Ziel ohne Schlusspunkt <code>.</code></td>
|
||||
<td style="padding: 10px; color: #92400e; font-weight: 700;">GEFÄHRLICH</td>
|
||||
<td style="padding: 10px;">BIND hängt Zonennamen an: <code>target.com.firma.de.</code></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 4. AUFGABEN CHECKLISTE -->
|
||||
<section class="card">
|
||||
<h2>🎯 Aufgaben & Checkliste</h2>
|
||||
<h2>🎯 Aufgaben & Checkliste in <code>aufgabe.sh</code></h2>
|
||||
<div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t1">
|
||||
<label class="task-label" for="t1"><strong>TODO 1:</strong> CNAME-Record für Subdomain (api.domain.de -> elb.aws.com) erstellen</label>
|
||||
</div><div class="task-item">
|
||||
<label class="task-label" for="t1"><strong>TODO 1:</strong> <code>format_cname_record(...)</code>: CNAME mit Punkt-Garantie erzeugen.</label>
|
||||
</div>
|
||||
<div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t2">
|
||||
<label class="task-label" for="t2"><strong>TODO 2:</strong> RFC-Kollision prüfen (CNAME darf nicht mit anderen Records koexistieren)</label>
|
||||
</div><div class="task-item">
|
||||
<label class="task-label" for="t2"><strong>TODO 2:</strong> <code>validate_apex_cname_rule(...)</code>: Zone-Apex Kollision blockieren.</label>
|
||||
</div>
|
||||
<div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t3">
|
||||
<label class="task-label" for="t3"><strong>TODO 3:</strong> Apex/Root-Domain Problem (example.de vs. MX/SOA) verstehen</label>
|
||||
</div><div class="task-item">
|
||||
<label class="task-label" for="t3"><strong>TODO 3:</strong> <code>detect_cname_collisions(...)</code>: Koexistenz-Konflikte in Zonendatei aufspüren.</label>
|
||||
</div>
|
||||
<div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t4">
|
||||
<label class="task-label" for="t4"><strong>TODO 4:</strong> Moderne ALIAS / ANAME Provider-Lösungen analysieren</label>
|
||||
<label class="task-label" for="t4"><strong>TODO 4:</strong> <code>trace_cname_chain(...)</code>: Alias-Ketten verfolgen und Schleifen abfangen.</label>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 25px; display: flex; gap: 12px; flex-wrap: wrap;">
|
||||
<a href="../../../workspace.html?course=dns_records&track=track_2_core_records&chapter=06_cname_canonical_name_und_alias_regeln" class="btn" style="background: #0284c7; color: #fff; font-weight: 700;">💻 In Web-IDE öffnen →</a>
|
||||
<a href="../07_soa_start_of_authority_und_serial/index.html" class="btn btn-secondary">Weiter zu Modul →</a>
|
||||
<a href="../../../workspace.html?course=dns_records&track=track_2_core_records&chapter=06_cname_canonical_name_und_alias_regeln" class="btn" style="background: #1d4ed8; color: #fff; font-weight: 700;">In Web-IDE öffnen →</a>
|
||||
<a href="../07_soa_start_of_authority_und_serial/index.html" class="btn btn-secondary">Weiter zu Modul 07 →</a>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
|
|
|||
|
|
@ -1,97 +1,96 @@
|
|||
#!/usr/bin/env bash
|
||||
# ==============================================================================
|
||||
# MUSTERLÖSUNG: DNS 06: CNAME Alias & das Apex-Problem
|
||||
# MUSTERLÖSUNG: DNS 06: CNAME (CANONICAL NAME) & DAS ZONE-APEX PROBLEM
|
||||
# ==============================================================================
|
||||
|
||||
validate_cname_rules() {
|
||||
local domain="${1:-it-praxisportal.de}"
|
||||
local target_dir="${2:-.}"
|
||||
format_cname_record() {
|
||||
local alias="$1"
|
||||
local target="$2"
|
||||
local ttl="${3:-3600}"
|
||||
|
||||
echo "▶ Starte professionelle DNS-Operation für $domain..."
|
||||
[ -z "$alias" ] || [ -z "$target" ] && return 1
|
||||
|
||||
case "06_cname_canonical_name_und_alias_regeln" in
|
||||
*01_dns_hierarchie*)
|
||||
echo "Host: www | SLD: it-praxisportal | TLD: de | Root: ."
|
||||
;;
|
||||
*02_rekursive*)
|
||||
echo "Simulation Iteration: Root (.) -> de. -> ns1.it-praxisportal.de -> 188.245.100.5"
|
||||
;;
|
||||
*03_ports_udp*)
|
||||
echo "Protocol: UDP/53 (Standard) | Fallback: TCP/53 | EDNS0 Buffer: 4096 bytes"
|
||||
;;
|
||||
*04_ttl*)
|
||||
echo "TTL Current: 86400s | Migration Planned TTL: 300s | Neg Cache: 3600s"
|
||||
;;
|
||||
*05_a_und_aaaa*)
|
||||
cat << 'ZONE' > "$target_dir/zone_records.txt"
|
||||
@ IN A 188.245.100.5
|
||||
@ IN AAAA 2a01:4f8:c010:d::1
|
||||
www IN A 188.245.100.5
|
||||
ZONE
|
||||
;;
|
||||
*06_cname*)
|
||||
echo "api IN CNAME app.it-praxisportal.de." > "$target_dir/cname_record.txt"
|
||||
;;
|
||||
*07_soa*)
|
||||
cat << 'ZONE' > "$target_dir/soa_record.txt"
|
||||
@ IN SOA ns1.it-praxisportal.de. hostmaster.it-praxisportal.de. (
|
||||
2026091201 ; Serial YYYYMMDDNN
|
||||
7200 ; Refresh (2h)
|
||||
3600 ; Retry (1h)
|
||||
1209600 ; Expire (2w)
|
||||
3600 ; Negative Cache TTL (1h)
|
||||
)
|
||||
ZONE
|
||||
;;
|
||||
*08_ns_und_ptr*)
|
||||
echo "100.245.188.in-addr.arpa. IN PTR mail.it-praxisportal.de." > "$target_dir/ptr_record.txt"
|
||||
;;
|
||||
*09_mx*)
|
||||
echo "@ IN MX 10 mail.it-praxisportal.de." > "$target_dir/mx_record.txt"
|
||||
echo "@ IN MX 20 backup-mail.it-praxisportal.de." >> "$target_dir/mx_record.txt"
|
||||
;;
|
||||
*10_txt_records*)
|
||||
echo '@ IN TXT "v=spf1 mx ip4:188.245.100.5 include:_spf.google.com -all"' > "$target_dir/spf_record.txt"
|
||||
;;
|
||||
*11_dkim*)
|
||||
echo 's1._domainkey IN TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz..."' > "$target_dir/dkim_record.txt"
|
||||
;;
|
||||
*12_dmarc*)
|
||||
echo '_dmarc IN TXT "v=DMARC1; p=reject; rua=mailto:dmarc-reports@it-praxisportal.de; pct=100; aspf=s"' > "$target_dir/dmarc_record.txt"
|
||||
;;
|
||||
*13_srv*)
|
||||
echo '_ldap._tcp.dc._msdcs.corp IN SRV 0 100 389 dc01.corp.it-praxisportal.de.' > "$target_dir/srv_record.txt"
|
||||
;;
|
||||
*14_caa*)
|
||||
echo '@ IN CAA 0 issue "letsencrypt.org"' > "$target_dir/caa_record.txt"
|
||||
echo '@ IN CAA 0 iodef "mailto:security@it-praxisportal.de"' >> "$target_dir/caa_record.txt"
|
||||
;;
|
||||
*15_dns_troubleshooting*)
|
||||
echo "STATUS: NOERROR | ANSWER: 1 | FLAGS: qr aa rd ra"
|
||||
;;
|
||||
*16_master*)
|
||||
cat << 'REPORT' > "$target_dir/dns_audit_report.json"
|
||||
{
|
||||
"domain": "it-praxisportal.de",
|
||||
"audit_status": "PASSED",
|
||||
"score": 100,
|
||||
"soa_serial_valid": true,
|
||||
"ns_redundancy": 2,
|
||||
"email_security": {
|
||||
"spf": "valid (-all)",
|
||||
"dkim": "present",
|
||||
"dmarc": "enforced (p=reject)"
|
||||
},
|
||||
"dnssec": "active"
|
||||
}
|
||||
REPORT
|
||||
;;
|
||||
esac
|
||||
# Schlusspunkt garantieren
|
||||
if [[ "$target" != *. ]]; then
|
||||
target="${target}."
|
||||
fi
|
||||
|
||||
echo "✅ DNS-Operation erfolgreich abgeschlossen."
|
||||
printf "%-15s %-7s IN CNAME %s\n" "$alias" "$ttl" "$target"
|
||||
return 0
|
||||
}
|
||||
|
||||
validate_apex_cname_rule() {
|
||||
local node="$1"
|
||||
local rtype=$(echo "$2" | tr '[:lower:]' '[:upper:]')
|
||||
|
||||
if [ "$rtype" = "CNAME" ]; then
|
||||
if [ "$node" = "@" ] || [ "$node" = "" ]; then
|
||||
echo "ERROR_APEX_VIOLATION: CNAME am Zone-Apex verboten! Kollidiert mit SOA und NS Records."
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "VALID: CNAME auf Subdomain erlaubt."
|
||||
return 0
|
||||
}
|
||||
|
||||
detect_cname_collisions() {
|
||||
local zfile="$1"
|
||||
[ ! -f "$zfile" ] && return 1
|
||||
|
||||
local cnames=($(awk '$3 == "CNAME" || $4 == "CNAME" { print $1 }' "$zfile"))
|
||||
local collisions=()
|
||||
|
||||
for c in "${cnames[@]}"; do
|
||||
local other_count=$(awk -v host="$c" '($1 == host) && ($3 != "CNAME" && $4 != "CNAME") { count++ } END { print count+0 }' "$zfile")
|
||||
if [ "$other_count" -gt 0 ]; then
|
||||
collisions+=("$c")
|
||||
fi
|
||||
done
|
||||
|
||||
if [ ${#collisions[@]} -gt 0 ]; then
|
||||
echo "CNAME_COLLISION_FOUND: Node '${collisions[0]}' besitzt unerlaubt CNAME und weitere Records!"
|
||||
return 1
|
||||
else
|
||||
echo "CNAME_INTEGRITY_OK: Keine Koexistenz-Konflikte gefunden."
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
trace_cname_chain() {
|
||||
local curr="$1"
|
||||
local map="$2"
|
||||
[ ! -f "$map" ] && return 1
|
||||
|
||||
local path=("$curr")
|
||||
local visited=("$curr")
|
||||
|
||||
while true; do
|
||||
local next=$(awk -v k="$curr" '$1 == k { print $2 }' "$map")
|
||||
if [ -z "$next" ]; then
|
||||
# Ende der Kette erreicht
|
||||
local out=""
|
||||
for p in "${path[@]}"; do
|
||||
if [ -z "$out" ]; then out="$p"; else out="$out -> $p"; fi
|
||||
done
|
||||
echo "RESOLVED: $out"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Prüfe auf Loop
|
||||
for v in "${visited[@]}"; do
|
||||
if [ "$v" = "$next" ]; then
|
||||
echo "ERROR_LOOP_DETECTED: Zirkuläre CNAME-Referenz bei '$next'!"
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
|
||||
visited+=("$next")
|
||||
path+=("$next")
|
||||
curr="$next"
|
||||
done
|
||||
}
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
validate_cname_rules "$@"
|
||||
format_cname_record "$@"
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -1,36 +1,123 @@
|
|||
#!/usr/bin/env bash
|
||||
# ==============================================================================
|
||||
# TESTSUITE: DNS 06: CNAME Alias & das Apex-Problem
|
||||
# 🧪 TESTSUITE: DNS 06 - CNAME & ZONE-APEX
|
||||
# ==============================================================================
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
BLUE='\033[0;34m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
TARGET_SCRIPT="${1:-aufgabe.sh}"
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
echo -e "${BLUE}🧪 TESTSUITE: DNS 06: CNAME Alias & das Apex-Problem${NC}"
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
TOTAL_TESTS=0
|
||||
PASSED_TESTS=0
|
||||
FAILED_TESTS=0
|
||||
|
||||
TEST_DIR=$(mktemp -d /tmp/dns_test_XXXXXX)
|
||||
trap 'rm -rf "$TEST_DIR"' EXIT
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
echo -e "${BLUE}🧪 TESTSUITE: DNS 06: CNAME & Zone-Apex-Regeln${NC}"
|
||||
echo -e "${BLUE} Ziel-Skript: ${YELLOW}${TARGET_SCRIPT}${NC}"
|
||||
echo -e "${BLUE}================================================================${NC}\n"
|
||||
|
||||
if [ ! -f "$SCRIPT_DIR/$TARGET_SCRIPT" ]; then
|
||||
echo -e "${RED}❌ Fehler: Datei '$TARGET_SCRIPT' nicht gefunden!${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
source "$SCRIPT_DIR/$TARGET_SCRIPT"
|
||||
|
||||
# Führe Funktion aus
|
||||
validate_cname_rules "example.com" "$TEST_DIR"
|
||||
RET=$?
|
||||
run_test() {
|
||||
local name="$1"
|
||||
local cmd="$2"
|
||||
((TOTAL_TESTS++))
|
||||
echo -e "▶ Test ${TOTAL_TESTS}: ${name}"
|
||||
if eval "$cmd"; then
|
||||
echo -e " ${GREEN}✓ BESTANDEN${NC}\n"
|
||||
((PASSED_TESTS++))
|
||||
else
|
||||
echo -e " ${RED}✗ FEHLGESCHLAGEN${NC}\n"
|
||||
((FAILED_TESTS++))
|
||||
fi
|
||||
}
|
||||
|
||||
if [ $RET -eq 0 ]; then
|
||||
echo -e "${GREEN}✓ Test 1: Funktion validate_cname_rules existiert und gibt 0 zurück.${NC}"
|
||||
echo -e "${GREEN}✓ Test 2: DNS Resource Records / Syntax validiert.${NC}"
|
||||
echo -e "${GREEN}✓ Test 3: Teilziele 1 bis 4 erfolgreich abgeschlossen.${NC}"
|
||||
echo -e "${GREEN}🎉 ERFOLG: Alle DNS-Prüfungen in DNS 06: CNAME Alias & das Apex-Problem bestanden!${NC}"
|
||||
# TEST 1: CNAME Formatierung mit Schlusspunkt-Garantie
|
||||
run_test "format_cname_record ergänzt Schlusspunkt am Ziel" '
|
||||
OUT=$(format_cname_record "shop" "lb.cloudhoster.de" 3600)
|
||||
echo "$OUT" | grep -q "shop" && \
|
||||
echo "$OUT" | grep -q "CNAME" && \
|
||||
echo "$OUT" | grep -q "lb.cloudhoster.de."
|
||||
'
|
||||
|
||||
# TEST 2: Zone Apex CNAME Violation
|
||||
run_test "validate_apex_cname_rule blockiert CNAME auf @" '
|
||||
validate_apex_cname_rule "@" "CNAME" | grep -q "ERROR_APEX_VIOLATION"
|
||||
'
|
||||
|
||||
# TEST 3: CNAME auf normaler Subdomain erlaubt
|
||||
run_test "validate_apex_cname_rule erlaubt CNAME auf blog" '
|
||||
validate_apex_cname_rule "blog" "CNAME" | grep -q "VALID: CNAME auf Subdomain"
|
||||
'
|
||||
|
||||
# TEST 4: Kollisions-Erkennung (CNAME + TXT auf demselben Node)
|
||||
run_test "detect_cname_collisions findet illegalen Koexistenz-Konflikt" '
|
||||
TMP_ZONE=$(mktemp /tmp/cname_col_XXXXXX)
|
||||
cat << "EOF" > "$TMP_ZONE"
|
||||
blog IN CNAME myblog.wordpress.com.
|
||||
blog IN TXT "verification=12345"
|
||||
EOF
|
||||
OUT=$(detect_cname_collisions "$TMP_ZONE")
|
||||
rm -f "$TMP_ZONE"
|
||||
echo "$OUT" | grep -q "CNAME_COLLISION_FOUND"
|
||||
'
|
||||
|
||||
# TEST 5: Saubere Zonendatei ohne CNAME-Konflikte
|
||||
run_test "detect_cname_collisions bestätigt saubere Zone" '
|
||||
TMP_ZONE=$(mktemp /tmp/cname_col_XXXXXX)
|
||||
cat << "EOF" > "$TMP_ZONE"
|
||||
@ IN A 192.0.2.1
|
||||
blog IN CNAME myblog.wordpress.com.
|
||||
shop IN CNAME shop.shopify.com.
|
||||
EOF
|
||||
OUT=$(detect_cname_collisions "$TMP_ZONE")
|
||||
rm -f "$TMP_ZONE"
|
||||
echo "$OUT" | grep -q "CNAME_INTEGRITY_OK"
|
||||
'
|
||||
|
||||
# TEST 6: CNAME Kette auflösen
|
||||
run_test "trace_cname_chain verfolgt mehrstufige Alias-Kette" '
|
||||
TMP_MAP=$(mktemp /tmp/cname_map_XXXXXX)
|
||||
cat << "EOF" > "$TMP_MAP"
|
||||
web app
|
||||
app cdn
|
||||
cdn srv01.cloud.de.
|
||||
EOF
|
||||
OUT=$(trace_cname_chain "web" "$TMP_MAP")
|
||||
rm -f "$TMP_MAP"
|
||||
echo "$OUT" | grep -q "RESOLVED: web -> app -> cdn -> srv01.cloud.de."
|
||||
'
|
||||
|
||||
# TEST 7: CNAME Loop Erkennung
|
||||
run_test "trace_cname_chain erkennt zirkuläre Referenzen (Loop)" '
|
||||
TMP_MAP=$(mktemp /tmp/cname_map_XXXXXX)
|
||||
cat << "EOF" > "$TMP_MAP"
|
||||
a b
|
||||
b c
|
||||
c a
|
||||
EOF
|
||||
OUT=$(trace_cname_chain "a" "$TMP_MAP")
|
||||
rm -f "$TMP_MAP"
|
||||
echo "$OUT" | grep -q "ERROR_LOOP_DETECTED"
|
||||
'
|
||||
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
if [ "$FAILED_TESTS" -eq 0 ]; then
|
||||
echo -e "${GREEN}🎉 ERFOLG: Alle ${PASSED_TESTS}/${TOTAL_TESTS} Tests in DNS 06 erfolgreich bestanden!${NC}"
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
exit 0
|
||||
else
|
||||
echo -e "${RED}❌ FEHLER: Skript fehlgeschlagen mit Status $RET${NC}"
|
||||
echo -e "${RED}❌ FEHLER: ${FAILED_TESTS} von ${TOTAL_TESTS} Tests fehlgeschlagen.${NC}"
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -1,18 +1,47 @@
|
|||
# DNS 07: SOA Start of Authority & Serial
|
||||
# DNS 07: SOA (Start of Authority) & Serial-Management
|
||||
|
||||
Willkommen zum Kurs **DNS-Records, Domain Name System & E-Mail-Sicherheit** im IT-Praxisportal!
|
||||
Willkommen zu **Modul 07** des Kurses **DNS-Records, Domain Name System & E-Mail-Sicherheit**!
|
||||
|
||||
## 💡 Das Wichtigste in Kürze
|
||||
- **Ziel:** Der wichtigste Record jeder Zone: Primary Master, Hostmaster-E-Mail, Seriennummer (YYYYMMDDNN) und Timer.
|
||||
- **Wichtigstes Tool:** `named-compilezone`
|
||||
In diesem Modul lernst du den wichtigsten administrativen Record einer jeden Zonendatei kennen: den **SOA-Record (Start of Authority)**. Du erfährst, wie die **10-stellige Serial-Nummer** funktioniert, wie E-Mail-Adressen in DNS-Notation umgewandelt werden und welche Bedeutung die Timer **Refresh**, **Retry**, **Expire** und **Minimum TTL** haben.
|
||||
|
||||
## 🎯 Deine Aufgaben (Checkliste)
|
||||
1. **Teilziel 1:** SOA-Record mit RFC-konformer Seriennummer (YYYYMMDDNN) anlegen
|
||||
2. **Teilziel 2:** Hostmaster E-Mail mit Punkt-Notation (admin.domain.de) formatieren
|
||||
3. **Teilziel 3:** Timer: Refresh, Retry, Expire und Minimum-TTL abstimmen
|
||||
4. **Teilziel 4:** Zonenaktualisierung über Seriennummer-Inkrement triggern
|
||||
---
|
||||
|
||||
## 💡 1. Das Wichtigste in Kürze
|
||||
|
||||
### Aufbau des SOA-Records
|
||||
```dns
|
||||
@ IN SOA ns1.firma.de. hostmaster.firma.de. (
|
||||
2026091501 ; Serial: Versionsnummer YYYYMMDDNN
|
||||
7200 ; Refresh (2h): Secondary fragt alle 2 Stunden nach Updates
|
||||
3600 ; Retry (1h): Wiederholung nach 1 Stunde bei Nichterreichbarkeit
|
||||
1209600 ; Expire (2 Wochen): Secondary stoppt Antworten nach 2 Wochen ohne Master
|
||||
3600 ; Minimum TTL: Gültigkeitsdauer für negatives Caching (NXDOMAIN)
|
||||
)
|
||||
```
|
||||
|
||||
### Die E-Mail-Adresse im SOA
|
||||
Weil das Zeichen `@` im DNS-Format den Zonenursprung (`$ORIGIN`) repräsentiert, wird in der Administrator-E-Mail das `@` durch einen Punkt `.` ersetzt:
|
||||
- `hostmaster@firma.de` → `hostmaster.firma.de.`
|
||||
|
||||
### Die Serial-Nummer (`YYYYMMDDNN`)
|
||||
- `YYYYMMDD`: 8-stelliges Datum (z.B. `20260915`).
|
||||
- `NN`: 2-stellige Revisionszählung des Tages (z.B. `01`, `02`, `03`).
|
||||
- **Secondary-Replikation:** Ein Slave-Nameserver führt nur dann einen Zonentransfer durch, wenn die Serial des Masters **größer** ist als seine eigene lokale Serial!
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Deine Aufgaben in `aufgabe.sh`
|
||||
|
||||
1. **TODO 1 (`format_hostmaster_email`):** Konvertiere eine normale E-Mail-Adresse in DNS-kompatible SOA-Notation.
|
||||
2. **TODO 2 (`generate_soa_serial`):** Generiere eine 10-stellige Serial `YYYYMMDDNN`.
|
||||
3. **TODO 3 (`increment_soa_serial`):** Inkrementiere die Serial für denselben Tag oder setze sie an einem neuen Tag auf `01`.
|
||||
4. **TODO 4 (`generate_complete_soa`):** Schreibe einen vollständigen BIND9-kompatiblen SOA-Header-Block.
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Tests ausführen
|
||||
|
||||
Führe im Terminal folgenden Befehl aus:
|
||||
```bash
|
||||
bash test_aufgabe.sh
|
||||
```
|
||||
|
|
|
|||
|
|
@ -1,21 +1,108 @@
|
|||
#!/usr/bin/env bash
|
||||
# ==============================================================================
|
||||
# ⚡ Zonen-Header - DNS 07: SOA Start of Authority & Serial
|
||||
# 🌐 DNS 07: SOA (START OF AUTHORITY) & SERIAL-MANAGEMENT
|
||||
# ==============================================================================
|
||||
#
|
||||
# DER SOA-HEADER: DAS HERZ JEDER ZONENDATEI
|
||||
# Der SOA-Record definiert die grundlegenden administrativen Eigenschaften einer Zone:
|
||||
#
|
||||
# @ IN SOA ns1.firma.de. admin.firma.de. (
|
||||
# 2026091501 ; Serial: Versionsnummer YYYYMMDDNN
|
||||
# 7200 ; Refresh (2h): Polling-Intervall der Secondary Nameserver
|
||||
# 3600 ; Retry (1h): Wiederholungsversuch nach fehlgeschlagenem Refresh
|
||||
# 1209600 ; Expire (2w): Maximale Gültigkeitsdauer ohne Primary Kontakt
|
||||
# 3600 ; Minimum TTL: Gültigkeitsdauer für negatives Caching (RFC 2308)
|
||||
# )
|
||||
#
|
||||
# DIE REVISIONS-FALLE (SERIAL):
|
||||
# Secondary Nameserver übertragen die Zone (AXFR/IXFR) NUR DANN, wenn die empfangene
|
||||
# Serial-Nummer GRÖSSER ist als ihre eigene! Wird die Serial beim Ändern von IPs
|
||||
# nicht erhöht, bleiben die Secondary-Server auf alten Daten sitzen.
|
||||
#
|
||||
# DIE E-MAIL-SYNTAX:
|
||||
# Im DNS-Protokoll steht das "@" für die aktuelle Domain ($ORIGIN). Daher wird in
|
||||
# E-Mail-Adressen das "@" durch einen Punkt "." ersetzt!
|
||||
# Aus "hostmaster@firma.de" wird "hostmaster.firma.de."
|
||||
# ==============================================================================
|
||||
|
||||
# 🎯 TEILZIEL 1: SOA-Record mit RFC-konformer Seriennummer (YYYYMMDDNN) anlegen
|
||||
# 🎯 TEILZIEL 2: Hostmaster E-Mail mit Punkt-Notation (admin.domain.de) formatieren
|
||||
# 🎯 TEILZIEL 3: Timer: Refresh, Retry, Expire und Minimum-TTL abstimmen
|
||||
# 🎯 TEILZIEL 4: Zonenaktualisierung über Seriennummer-Inkrement triggern
|
||||
# 🎯 TEILZIEL 1 (TODO 1): E-Mail in DNS-kompatible SOA-Notation umwandeln
|
||||
# Funktion: format_hostmaster_email "$email"
|
||||
# Parameter $1: E-Mail-Adresse (z.B. "hostmaster@firma.de" oder "noc.admin@firma.de")
|
||||
# Anforderungen:
|
||||
# - Ersetze das erste "@" durch einen Punkt ".".
|
||||
# - Hänge am Ende einen Schlusspunkt "." an, falls er fehlt.
|
||||
# - Bei Punkten im lokalen Teil vor dem "@" (z.B. "noc.admin@firma.de") muss der Punkt
|
||||
# im DNS quotiert werden ("noc\.admin.firma.de.") oder vereinfacht durch "." ersetzt werden.
|
||||
# - Ausgabe: Die fertige DNS-Notation (z.B. "hostmaster.firma.de.")
|
||||
format_hostmaster_email() {
|
||||
local email="$1"
|
||||
# TODO 1: Konvertiere E-Mail in DNS SOA Notation
|
||||
return 0
|
||||
}
|
||||
|
||||
generate_soa_header() {
|
||||
local domain="${1:-it-praxisportal.de}"
|
||||
echo "▶ Analysiere DNS-Konfiguration für $domain..."
|
||||
# 🎯 TEILZIEL 2 (TODO 2): 10-stellige SOA-Serial generieren
|
||||
# Funktion: generate_soa_serial "$date_str" "$revision"
|
||||
# Parameter $1: Datum im Format "YYYY-MM-DD" (oder leer für heutiges Datum)
|
||||
# Parameter $2: 2-stellige Revision ("01" bis "99", Standard: "01")
|
||||
# Format: YYYYMMDDNN (exakt 10 Ziffern)
|
||||
# Beispiel: "2026-09-15" mit Revision "02" -> "2026091502"
|
||||
generate_soa_serial() {
|
||||
local dstr="$1"
|
||||
local rev="${2:-01}"
|
||||
# TODO 2: Erzeuge die 10-stellige Serial
|
||||
return 0
|
||||
}
|
||||
|
||||
# TODO: Implementiere die geforderten DNS-Prüfungen oder Record-Erstellungen
|
||||
return 1
|
||||
# 🎯 TEILZIEL 3 (TODO 3): SOA-Serial bei Zonenänderung inkrementieren
|
||||
# Funktion: increment_soa_serial "$current_serial" "$today_yyyymmdd"
|
||||
# Parameter $1: Aktuelle Serial (z.B. "2026091501")
|
||||
# Parameter $2: Heutiges Datum als YYYYMMDD (z.B. "20260915" oder "20260916")
|
||||
# Regeln:
|
||||
# - Wenn der Datumsanteil (erste 8 Ziffern) == today:
|
||||
# Erhöhe die letzten 2 Ziffern um 1 (z.B. 01 -> 02, 09 -> 10).
|
||||
# - Wenn today > Datumsanteil der alten Serial:
|
||||
# Setze neue Serial auf "${today}01".
|
||||
# - Gib die neue 10-stellige Serial aus.
|
||||
increment_soa_serial() {
|
||||
local cur="$1"
|
||||
local today="$2"
|
||||
# TODO 3: Inkrementiere die Serial logisch
|
||||
return 0
|
||||
}
|
||||
|
||||
# 🎯 TEILZIEL 4 (TODO 4): Vollständigen SOA-Record generieren
|
||||
# Funktion: generate_soa_record "$zone" "$primary_ns" "$email" "$serial" "$refresh" "$retry" "$expire" "$min_ttl"
|
||||
# Parameter: Zonenname, Primary Master NS, E-Mail, Serial, Refresh, Retry, Expire, MinTTL
|
||||
# Anforderungen:
|
||||
# - Konvertiere die E-Mail via format_hostmaster_email.
|
||||
# - Formatiere den vollständigen mehrzeiligen SOA-Block nach BIND-Standard:
|
||||
# @ IN SOA <primary_ns>. <email_dns>. (
|
||||
# <serial> ; Serial
|
||||
# <refresh> ; Refresh
|
||||
# <retry> ; Retry
|
||||
# <expire> ; Expire
|
||||
# <min_ttl> ; Negative Cache TTL
|
||||
# )
|
||||
generate_soa_record() {
|
||||
local zone="$1"
|
||||
local ns="$2"
|
||||
local email="$3"
|
||||
local serial="$4"
|
||||
local refresh="${5:-7200}"
|
||||
local retry="${6:-3600}"
|
||||
local expire="${7:-1209600}"
|
||||
local min_ttl="${8:-3600}"
|
||||
# TODO 4: Erzeuge den kompletten SOA-Record
|
||||
return 0
|
||||
}
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
generate_soa_header "$@"
|
||||
echo "=== Test Teilziel 1 ==="
|
||||
format_hostmaster_email "hostmaster@it-praxisportal.de"
|
||||
echo ""
|
||||
echo "=== Test Teilziel 2 ==="
|
||||
generate_soa_serial "2026-09-15" "01"
|
||||
echo ""
|
||||
echo "=== Test Teilziel 3 ==="
|
||||
increment_soa_serial "2026091501" "20260915"
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
<link rel="icon" type="image/svg+xml" href="../../../assets/favicon.svg">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>DNS 07: SOA Start of Authority & Serial</title>
|
||||
<title>DNS 07: SOA Record & Serial</title>
|
||||
<link rel="stylesheet" href="../../../assets/style.css">
|
||||
</head>
|
||||
<body>
|
||||
|
|
@ -12,46 +12,102 @@
|
|||
<div class="app-main">
|
||||
<header>
|
||||
<div class="header-container">
|
||||
<div class="header-badge">⚡ Zonen-Header</div>
|
||||
<h1>DNS 07: SOA Start of Authority & Serial</h1>
|
||||
<p>Der wichtigste Record jeder Zone: Primary Master, Hostmaster-E-Mail, Seriennummer (YYYYMMDDNN) und Timer.</p>
|
||||
<div class="header-badge">⚡ Resource Records</div>
|
||||
<h1>DNS 07: SOA (Start of Authority) & Serial</h1>
|
||||
<p>Verstehe den administrativen Kopf jeder Zonendatei: Primary Master, Hostmaster-E-Mail, Revisions-Serials und Zonentransfer-Timer.</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="container">
|
||||
<!-- 1. THEORIE: SOA ANATOMIE -->
|
||||
<section class="card">
|
||||
<h2>💡 Theorie & Praxiskonzept</h2>
|
||||
<p>Der wichtigste Record jeder Zone: Primary Master, Hostmaster-E-Mail, Seriennummer (YYYYMMDDNN) und Timer.</p>
|
||||
<div class="code-container">
|
||||
<div class="code-header">
|
||||
<span>DNS Command & Syntax Reference</span>
|
||||
<span>Tool: named-compilezone</span>
|
||||
</div>
|
||||
<pre><code># Wichtige Befehle & Zonensyntax:
|
||||
dig named-compilezone
|
||||
named-checkzone example.com /etc/bind/zones/db.example.com</code></pre>
|
||||
<h2>👑 1. Die Anatomie des SOA-Records</h2>
|
||||
<p>Der SOA-Record steht ausnahmslos an der Spitze jeder Forward- und Reverse-Zonendatei. Er legt fest, wer autoritativer Master ist und wie Slaves synchronisieren:</p>
|
||||
|
||||
<pre><code>; Der SOA-Record in BIND9:
|
||||
@ IN SOA ns1.it-praxisportal.de. hostmaster.it-praxisportal.de. (
|
||||
2026091501 ; Serial (Format: YYYYMMDDNN)
|
||||
7200 ; Refresh (2h): Polling-Intervall der Secondary NS
|
||||
3600 ; Retry (1h): Wartezeit nach Refresh-Fehler
|
||||
1209600 ; Expire (14d): Verfallsdatum bei Ausfall des Masters
|
||||
3600 ) ; Negative Caching TTL (NXDOMAIN Cache-Dauer)</code></pre>
|
||||
</section>
|
||||
|
||||
<!-- 2. THEORIE: DIE SERIAL FALLE -->
|
||||
<section class="card">
|
||||
<h2>⚠️ 2. Die Serial-Falle in der Systemadministration</h2>
|
||||
<div class="box box-warnung">
|
||||
<div class="box-title">IHK-Prüfungsfalle: Warum aktualisieren Slaves nicht?</div>
|
||||
<p>Ein Administrator ändert auf dem Master-Server die IP-Adresse von <code>mail.firma.de</code>, vergisst aber, die <strong>Serial-Nummer im SOA zu erhöhen</strong>. Die Secondary-Nameserver vergleichen ihre Serial mit dem Master, stellen keinen Versionsunterschied fest und liefern wochenlang weiterhin die alte IP aus!</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 3. CHEAT-SHEET -->
|
||||
<section class="card">
|
||||
<h2>🎯 Aufgaben & Checkliste</h2>
|
||||
<h2>📑 3. Cheat-Sheet: Die 5 SOA-Parameter</h2>
|
||||
<div style="overflow-x: auto;">
|
||||
<table style="width: 100%; border-collapse: collapse; font-size: 0.9rem; text-align: left;">
|
||||
<thead>
|
||||
<tr style="border-bottom: 2px solid var(--border-color); background: var(--bg-subtle);">
|
||||
<th style="padding: 10px;">Feld</th>
|
||||
<th style="padding: 10px;">Typischer Wert</th>
|
||||
<th style="padding: 10px;">Funktion & Bedeutung</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr style="border-bottom: 1px solid var(--border-color);">
|
||||
<td style="padding: 10px;"><strong>Serial</strong></td>
|
||||
<td style="padding: 10px;"><code>YYYYMMDDNN</code></td>
|
||||
<td style="padding: 10px;">Muss bei jeder Zonenänderung monoton steigen</td>
|
||||
</tr>
|
||||
<tr style="border-bottom: 1px solid var(--border-color);">
|
||||
<td style="padding: 10px;"><strong>Refresh</strong></td>
|
||||
<td style="padding: 10px;"><code>7200s (2h)</code></td>
|
||||
<td style="padding: 10px;">Intervall, in dem Slaves den Master nach neuer Serial abfragen</td>
|
||||
</tr>
|
||||
<tr style="border-bottom: 1px solid var(--border-color);">
|
||||
<td style="padding: 10px;"><strong>Retry</strong></td>
|
||||
<td style="padding: 10px;"><code>3600s (1h)</code></td>
|
||||
<td style="padding: 10px;">Wartezeit nach fehlgeschlagener Kontaktaufnahme</td>
|
||||
</tr>
|
||||
<tr style="border-bottom: 1px solid var(--border-color);">
|
||||
<td style="padding: 10px;"><strong>Expire</strong></td>
|
||||
<td style="padding: 10px;"><code>1209600s (14d)</code></td>
|
||||
<td style="padding: 10px;">Zeit, nach der Slaves aufhören zu antworten, wenn Master tot bleibt</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 10px;"><strong>Min. TTL</strong></td>
|
||||
<td style="padding: 10px;"><code>3600s (1h)</code></td>
|
||||
<td style="padding: 10px;">Negative Caching TTL für NXDOMAIN nach RFC 2308</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 4. AUFGABEN CHECKLISTE -->
|
||||
<section class="card">
|
||||
<h2>🎯 Aufgaben & Checkliste in <code>aufgabe.sh</code></h2>
|
||||
<div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t1">
|
||||
<label class="task-label" for="t1"><strong>TODO 1:</strong> SOA-Record mit RFC-konformer Seriennummer (YYYYMMDDNN) anlegen</label>
|
||||
</div><div class="task-item">
|
||||
<label class="task-label" for="t1"><strong>TODO 1:</strong> <code>format_hostmaster_email(email)</code>: E-Mail in DNS-SOA-Notation umwandeln.</label>
|
||||
</div>
|
||||
<div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t2">
|
||||
<label class="task-label" for="t2"><strong>TODO 2:</strong> Hostmaster E-Mail mit Punkt-Notation (admin.domain.de) formatieren</label>
|
||||
</div><div class="task-item">
|
||||
<label class="task-label" for="t2"><strong>TODO 2:</strong> <code>generate_soa_serial(date, rev)</code>: 10-stellige Versions-Serial generieren.</label>
|
||||
</div>
|
||||
<div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t3">
|
||||
<label class="task-label" for="t3"><strong>TODO 3:</strong> Timer: Refresh, Retry, Expire und Minimum-TTL abstimmen</label>
|
||||
</div><div class="task-item">
|
||||
<label class="task-label" for="t3"><strong>TODO 3:</strong> <code>increment_soa_serial(cur, today)</code>: Serial bei Änderungen logisch hochzählen.</label>
|
||||
</div>
|
||||
<div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t4">
|
||||
<label class="task-label" for="t4"><strong>TODO 4:</strong> Zonenaktualisierung über Seriennummer-Inkrement triggern</label>
|
||||
<label class="task-label" for="t4"><strong>TODO 4:</strong> <code>generate_complete_soa(...)</code>: Vollständigen BIND SOA-Record schreiben.</label>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 25px; display: flex; gap: 12px; flex-wrap: wrap;">
|
||||
<a href="../../../workspace.html?course=dns_records&track=track_2_core_records&chapter=07_soa_start_of_authority_und_serial" class="btn" style="background: #0284c7; color: #fff; font-weight: 700;">💻 In Web-IDE öffnen →</a>
|
||||
<a href="../08_ns_und_ptr_reverse_dns_lookups/index.html" class="btn btn-secondary">Weiter zu Modul →</a>
|
||||
<a href="../../../workspace.html?course=dns_records&track=track_2_core_records&chapter=07_soa_start_of_authority_und_serial" class="btn" style="background: #1d4ed8; color: #fff; font-weight: 700;">In Web-IDE öffnen →</a>
|
||||
<a href="../08_ns_und_ptr_reverse_dns_lookups/index.html" class="btn btn-secondary">Weiter zu Modul 08 →</a>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
|
|
|||
|
|
@ -1,97 +1,88 @@
|
|||
#!/usr/bin/env bash
|
||||
# ==============================================================================
|
||||
# MUSTERLÖSUNG: DNS 07: SOA Start of Authority & Serial
|
||||
# MUSTERLÖSUNG: DNS 07: SOA (START OF AUTHORITY) & SERIAL-MANAGEMENT
|
||||
# ==============================================================================
|
||||
|
||||
generate_soa_header() {
|
||||
local domain="${1:-it-praxisportal.de}"
|
||||
local target_dir="${2:-.}"
|
||||
format_hostmaster_email() {
|
||||
local raw="$1"
|
||||
[ -z "$raw" ] && return 1
|
||||
|
||||
echo "▶ Starte professionelle DNS-Operation für $domain..."
|
||||
# Trenne an @
|
||||
local user="${raw%%@*}"
|
||||
local domain="${raw#*@}"
|
||||
|
||||
case "07_soa_start_of_authority_und_serial" in
|
||||
*01_dns_hierarchie*)
|
||||
echo "Host: www | SLD: it-praxisportal | TLD: de | Root: ."
|
||||
;;
|
||||
*02_rekursive*)
|
||||
echo "Simulation Iteration: Root (.) -> de. -> ns1.it-praxisportal.de -> 188.245.100.5"
|
||||
;;
|
||||
*03_ports_udp*)
|
||||
echo "Protocol: UDP/53 (Standard) | Fallback: TCP/53 | EDNS0 Buffer: 4096 bytes"
|
||||
;;
|
||||
*04_ttl*)
|
||||
echo "TTL Current: 86400s | Migration Planned TTL: 300s | Neg Cache: 3600s"
|
||||
;;
|
||||
*05_a_und_aaaa*)
|
||||
cat << 'ZONE' > "$target_dir/zone_records.txt"
|
||||
@ IN A 188.245.100.5
|
||||
@ IN AAAA 2a01:4f8:c010:d::1
|
||||
www IN A 188.245.100.5
|
||||
ZONE
|
||||
;;
|
||||
*06_cname*)
|
||||
echo "api IN CNAME app.it-praxisportal.de." > "$target_dir/cname_record.txt"
|
||||
;;
|
||||
*07_soa*)
|
||||
cat << 'ZONE' > "$target_dir/soa_record.txt"
|
||||
@ IN SOA ns1.it-praxisportal.de. hostmaster.it-praxisportal.de. (
|
||||
2026091201 ; Serial YYYYMMDDNN
|
||||
7200 ; Refresh (2h)
|
||||
3600 ; Retry (1h)
|
||||
1209600 ; Expire (2w)
|
||||
3600 ; Negative Cache TTL (1h)
|
||||
)
|
||||
ZONE
|
||||
;;
|
||||
*08_ns_und_ptr*)
|
||||
echo "100.245.188.in-addr.arpa. IN PTR mail.it-praxisportal.de." > "$target_dir/ptr_record.txt"
|
||||
;;
|
||||
*09_mx*)
|
||||
echo "@ IN MX 10 mail.it-praxisportal.de." > "$target_dir/mx_record.txt"
|
||||
echo "@ IN MX 20 backup-mail.it-praxisportal.de." >> "$target_dir/mx_record.txt"
|
||||
;;
|
||||
*10_txt_records*)
|
||||
echo '@ IN TXT "v=spf1 mx ip4:188.245.100.5 include:_spf.google.com -all"' > "$target_dir/spf_record.txt"
|
||||
;;
|
||||
*11_dkim*)
|
||||
echo 's1._domainkey IN TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz..."' > "$target_dir/dkim_record.txt"
|
||||
;;
|
||||
*12_dmarc*)
|
||||
echo '_dmarc IN TXT "v=DMARC1; p=reject; rua=mailto:dmarc-reports@it-praxisportal.de; pct=100; aspf=s"' > "$target_dir/dmarc_record.txt"
|
||||
;;
|
||||
*13_srv*)
|
||||
echo '_ldap._tcp.dc._msdcs.corp IN SRV 0 100 389 dc01.corp.it-praxisportal.de.' > "$target_dir/srv_record.txt"
|
||||
;;
|
||||
*14_caa*)
|
||||
echo '@ IN CAA 0 issue "letsencrypt.org"' > "$target_dir/caa_record.txt"
|
||||
echo '@ IN CAA 0 iodef "mailto:security@it-praxisportal.de"' >> "$target_dir/caa_record.txt"
|
||||
;;
|
||||
*15_dns_troubleshooting*)
|
||||
echo "STATUS: NOERROR | ANSWER: 1 | FLAGS: qr aa rd ra"
|
||||
;;
|
||||
*16_master*)
|
||||
cat << 'REPORT' > "$target_dir/dns_audit_report.json"
|
||||
{
|
||||
"domain": "it-praxisportal.de",
|
||||
"audit_status": "PASSED",
|
||||
"score": 100,
|
||||
"soa_serial_valid": true,
|
||||
"ns_redundancy": 2,
|
||||
"email_security": {
|
||||
"spf": "valid (-all)",
|
||||
"dkim": "present",
|
||||
"dmarc": "enforced (p=reject)"
|
||||
},
|
||||
"dnssec": "active"
|
||||
# Quotiere Punkte im Userteil
|
||||
local esc_user="${user//./\.}"
|
||||
local res="${esc_user}.${domain}"
|
||||
|
||||
# Schlusspunkt garantieren
|
||||
if [[ "$res" != *. ]]; then
|
||||
res="${res}."
|
||||
fi
|
||||
|
||||
echo "$res"
|
||||
return 0
|
||||
}
|
||||
REPORT
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "✅ DNS-Operation erfolgreich abgeschlossen."
|
||||
generate_soa_serial() {
|
||||
local dstr="$1"
|
||||
local rev="${2:-01}"
|
||||
|
||||
local ymd=""
|
||||
if [ -n "$dstr" ]; then
|
||||
ymd=$(echo "$dstr" | tr -d '-')
|
||||
else
|
||||
ymd=$(date +%Y%m%d)
|
||||
fi
|
||||
|
||||
# Revision 2-stellig formatieren
|
||||
rev=$(printf "%02d" "$((10#$rev))")
|
||||
echo "${ymd}${rev}"
|
||||
return 0
|
||||
}
|
||||
|
||||
increment_soa_serial() {
|
||||
local cur="$1"
|
||||
local today="$2"
|
||||
|
||||
[ -z "$today" ] && today=$(date +%Y%m%d)
|
||||
local cur_date="${cur:0:8}"
|
||||
local cur_rev="${cur:8:2}"
|
||||
|
||||
if [ "$cur_date" = "$today" ]; then
|
||||
local next_rev=$((10#$cur_rev + 1))
|
||||
printf "%s%02d\n" "$today" "$next_rev"
|
||||
else
|
||||
echo "${today}01"
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
generate_complete_soa() {
|
||||
local zone="$1"
|
||||
local ns="$2"
|
||||
local email="$3"
|
||||
local serial="$4"
|
||||
local refresh="${5:-7200}"
|
||||
local retry="${6:-3600}"
|
||||
local expire="${7:-1209600}"
|
||||
local min_ttl="${8:-3600}"
|
||||
|
||||
[[ "$ns" != *. ]] && ns="${ns}."
|
||||
local email_dns=$(format_hostmaster_email "$email")
|
||||
|
||||
cat << EOF
|
||||
@ IN SOA ${ns} ${email_dns} (
|
||||
${serial} ; Serial YYYYMMDDNN
|
||||
${refresh} ; Refresh
|
||||
${retry} ; Retry
|
||||
${expire} ; Expire
|
||||
${min_ttl} ; Minimum / Negative Cache TTL
|
||||
)
|
||||
EOF
|
||||
return 0
|
||||
}
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
generate_soa_header "$@"
|
||||
format_hostmaster_email "$@"
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -1,36 +1,87 @@
|
|||
#!/usr/bin/env bash
|
||||
# ==============================================================================
|
||||
# TESTSUITE: DNS 07: SOA Start of Authority & Serial
|
||||
# 🧪 TESTSUITE: DNS 07 - SOA & SERIAL-MANAGEMENT
|
||||
# ==============================================================================
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
BLUE='\033[0;34m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
TARGET_SCRIPT="${1:-aufgabe.sh}"
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
echo -e "${BLUE}🧪 TESTSUITE: DNS 07: SOA Start of Authority & Serial${NC}"
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
TOTAL_TESTS=0
|
||||
PASSED_TESTS=0
|
||||
FAILED_TESTS=0
|
||||
|
||||
TEST_DIR=$(mktemp -d /tmp/dns_test_XXXXXX)
|
||||
trap 'rm -rf "$TEST_DIR"' EXIT
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
echo -e "${BLUE}🧪 TESTSUITE: DNS 07: SOA & Serial-Management${NC}"
|
||||
echo -e "${BLUE} Ziel-Skript: ${YELLOW}${TARGET_SCRIPT}${NC}"
|
||||
echo -e "${BLUE}================================================================${NC}\n"
|
||||
|
||||
if [ ! -f "$SCRIPT_DIR/$TARGET_SCRIPT" ]; then
|
||||
echo -e "${RED}❌ Fehler: Datei '$TARGET_SCRIPT' nicht gefunden!${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
source "$SCRIPT_DIR/$TARGET_SCRIPT"
|
||||
|
||||
# Führe Funktion aus
|
||||
generate_soa_header "example.com" "$TEST_DIR"
|
||||
RET=$?
|
||||
run_test() {
|
||||
local name="$1"
|
||||
local cmd="$2"
|
||||
((TOTAL_TESTS++))
|
||||
echo -e "▶ Test ${TOTAL_TESTS}: ${name}"
|
||||
if eval "$cmd"; then
|
||||
echo -e " ${GREEN}✓ BESTANDEN${NC}\n"
|
||||
((PASSED_TESTS++))
|
||||
else
|
||||
echo -e " ${RED}✗ FEHLGESCHLAGEN${NC}\n"
|
||||
((FAILED_TESTS++))
|
||||
fi
|
||||
}
|
||||
|
||||
if [ $RET -eq 0 ]; then
|
||||
echo -e "${GREEN}✓ Test 1: Funktion generate_soa_header existiert und gibt 0 zurück.${NC}"
|
||||
echo -e "${GREEN}✓ Test 2: DNS Resource Records / Syntax validiert.${NC}"
|
||||
echo -e "${GREEN}✓ Test 3: Teilziele 1 bis 4 erfolgreich abgeschlossen.${NC}"
|
||||
echo -e "${GREEN}🎉 ERFOLG: Alle DNS-Prüfungen in DNS 07: SOA Start of Authority & Serial bestanden!${NC}"
|
||||
# TEST 1: E-Mail zu SOA Notation
|
||||
run_test "format_hostmaster_email wandelt @ in Punkt und ergänzt FQDN-Punkt" '
|
||||
OUT=$(format_hostmaster_email "hostmaster@it-praxisportal.de")
|
||||
[ "$OUT" = "hostmaster.it-praxisportal.de." ]
|
||||
'
|
||||
|
||||
# TEST 2: Serial Generator
|
||||
run_test "generate_soa_serial erzeugt 10-stellige Zahl YYYYMMDD01" '
|
||||
OUT=$(generate_soa_serial "2026-09-15" "01")
|
||||
[ "$OUT" = "2026091501" ]
|
||||
'
|
||||
|
||||
# TEST 3: Serial Inkrement am selben Tag (01 -> 02)
|
||||
run_test "increment_soa_serial erhöht Revision am selben Tag" '
|
||||
OUT=$(increment_soa_serial "2026091501" "20260915")
|
||||
[ "$OUT" = "2026091502" ]
|
||||
'
|
||||
|
||||
# TEST 4: Serial Inkrement an neuem Tag setzt auf 01
|
||||
run_test "increment_soa_serial setzt an neuem Tag auf YYYYMMDD01" '
|
||||
OUT=$(increment_soa_serial "2026091405" "20260915")
|
||||
[ "$OUT" = "2026091501" ]
|
||||
'
|
||||
|
||||
# TEST 5: Vollständigen SOA Record erzeugen
|
||||
run_test "generate_complete_soa erzeugt BIND-konformen SOA-Block" '
|
||||
OUT=$(generate_complete_soa "firma.de" "ns1.firma.de" "admin@firma.de" "2026091501" 7200 3600 1209600 3600)
|
||||
echo "$OUT" | grep -q "@ IN SOA ns1.firma.de. admin.firma.de. (" && \
|
||||
echo "$OUT" | grep -q "2026091501 ; Serial" && \
|
||||
echo "$OUT" | grep -q "7200" && \
|
||||
echo "$OUT" | grep -q "3600"
|
||||
'
|
||||
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
if [ "$FAILED_TESTS" -eq 0 ]; then
|
||||
echo -e "${GREEN}🎉 ERFOLG: Alle ${PASSED_TESTS}/${TOTAL_TESTS} Tests in DNS 07 erfolgreich bestanden!${NC}"
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
exit 0
|
||||
else
|
||||
echo -e "${RED}❌ FEHLER: Skript fehlgeschlagen mit Status $RET${NC}"
|
||||
echo -e "${RED}❌ FEHLER: ${FAILED_TESTS} von ${TOTAL_TESTS} Tests fehlgeschlagen.${NC}"
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -1,18 +1,48 @@
|
|||
# DNS 08: NS Delegation & PTR Reverse DNS
|
||||
# DNS 08: NS-Delegation, Reverse DNS (PTR) & FCrDNS
|
||||
|
||||
Willkommen zum Kurs **DNS-Records, Domain Name System & E-Mail-Sicherheit** im IT-Praxisportal!
|
||||
Willkommen zu **Modul 08** des Kurses **DNS-Records, Domain Name System & E-Mail-Sicherheit**!
|
||||
|
||||
## 💡 Das Wichtigste in Kürze
|
||||
- **Ziel:** Delegierung an Nameserver (NS) und Reverse-Lookup-Zonen (in-addr.arpa / ip6.arpa) für FCrDNS.
|
||||
- **Wichtigstes Tool:** `dig -x`
|
||||
In diesem Modul lernst du die Verwaltung autoritativer **Nameserver-Delegationen (NS)** und die Funktionsweise von **Reverse DNS (PTR-Records)** kennen. Du erfährst, wie IP-Adressen für die `.in-addr.arpa`-Zone umgekehrt werden und warum **Forward-Confirmed Reverse DNS (FCrDNS)** für produktive Mail-Server zwingend erforderlich ist.
|
||||
|
||||
## 🎯 Deine Aufgaben (Checkliste)
|
||||
1. **Teilziel 1:** Mindestens zwei autoritative Nameserver (NS) delegieren
|
||||
2. **Teilziel 2:** Reverse-Lookup-Zone für /24 Subnetz (1.168.192.in-addr.arpa) anlegen
|
||||
3. **Teilziel 3:** PTR-Record für Mailserver zur SPAM-Vermeidung eintragen
|
||||
4. **Teilziel 4:** Forward-Confirmed Reverse DNS (FCrDNS) validieren
|
||||
---
|
||||
|
||||
## 💡 1. Das Wichtigste in Kürze
|
||||
|
||||
### NS-Records (Delegation)
|
||||
NS-Records delegieren eine Zone oder Subdomain an zuständige Server:
|
||||
```dns
|
||||
firma.de. 86400 IN NS ns1.provider.de.
|
||||
firma.de. 86400 IN NS ns2.provider.de.
|
||||
```
|
||||
> [!IMPORTANT]
|
||||
> **Redundanz-Anforderung:** Nach RFC 2182 müssen mindestens zwei autoritative Nameserver existieren, die sich in unterschiedlichen Subnetzen (vorzugsweise unterschiedlichen Autonomen Systemen / AS) befinden.
|
||||
|
||||
### Reverse DNS & PTR-Records (`in-addr.arpa`)
|
||||
Normales DNS übersetzt `Name -> IP`. Reverse DNS übersetzt `IP -> Name`.
|
||||
Da DNS-Bäume von links nach rechts spezifischer werden, müssen die 4 Oktette einer IPv4-Adresse für die `in-addr.arpa`-Hierarchie **umgekehrt** werden:
|
||||
- IP: `198.51.100.42`
|
||||
- PTR-Eintrag: `42.100.51.198.in-addr.arpa. IN PTR mail.firma.de.`
|
||||
|
||||
### FCrDNS (Forward-Confirmed Reverse DNS)
|
||||
Damit Spammer keine gefälschten Hostnamen vortäuschen können, prüfen empfangende Mailserver das **FCrDNS-Prinzip**:
|
||||
1. Empfänger schlägt die IP des sendenden Servers per Reverse-Lookup nach: `198.51.100.42` → `mail.firma.de`.
|
||||
2. Empfänger schlägt nun sofort diesen Hostnamen per A-Record nach: `mail.firma.de` → `198.51.100.42`.
|
||||
3. Nur wenn beide IP-Adressen exakt übereinstimmen, wird die Verbindung akzeptiert!
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Deine Aufgaben in `aufgabe.sh`
|
||||
|
||||
1. **TODO 1 (`ipv4_to_ptr_name`):** Kehre die 4 Oktette einer IPv4-Adresse um und bilde den `in-addr.arpa.`-Namen.
|
||||
2. **TODO 2 (`format_ptr_record`):** Formatiere einen vollständigen BIND9 PTR-Record.
|
||||
3. **TODO 3 (`format_ns_delegation`):** Erstelle zwei redundante NS-Records für eine Zonen-Delegation.
|
||||
4. **TODO 4 (`verify_fcrdns`):** Validiere das Forward-Confirmed Reverse DNS Prinzip.
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Tests ausführen
|
||||
|
||||
Führe im Terminal folgenden Befehl aus:
|
||||
```bash
|
||||
bash test_aufgabe.sh
|
||||
```
|
||||
|
|
|
|||
|
|
@ -1,21 +1,106 @@
|
|||
#!/usr/bin/env bash
|
||||
# ==============================================================================
|
||||
# ⚡ Nameserver & PTR - DNS 08: NS Delegation & PTR Reverse DNS
|
||||
# 🌐 DNS 08: NS-DELEGATION, REVERSE DNS (PTR) & FCRDNS
|
||||
# ==============================================================================
|
||||
#
|
||||
# ZWEI ELEMENTARE INFRASTRUKTUR-RECORDS:
|
||||
# 1. NS-Record (Name Server):
|
||||
# - Delegiert eine Subdomain oder Zone an autoritative Nameserver.
|
||||
# - BSI- & IETF-Vorgabe: Mindestens 2 unabhängige Nameserver (Primary & Secondary)
|
||||
# in unterschiedlichen Subnetzen / Rechenzentren (Redundanz).
|
||||
#
|
||||
# 2. PTR-Record (Pointer / Reverse DNS):
|
||||
# - Löst eine IP-Adresse rückwärts in einen Hostnamen auf (IP -> Name).
|
||||
# - Verwendet die reservierte Top-Level-Domain "arpa":
|
||||
# * IPv4: in-addr.arpa (Oktette in UMGEKEHRTER Reihenfolge!)
|
||||
# Beispiel: IP 188.245.100.5
|
||||
# Reverse-Zone: 100.245.188.in-addr.arpa.
|
||||
# PTR-Record: 5.100.245.188.in-addr.arpa. IN PTR mail.it-praxisportal.de.
|
||||
# * IPv6: ip6.arpa (Jedes 4-Bit Nibble einzeln rückwärts mit Punkten).
|
||||
#
|
||||
# FORWARD-CONFIRMED REVERSE DNS (FCrDNS):
|
||||
# Fast alle modernen Mail-Server (Google, Microsoft, GMX) verwerfen Mails ohne
|
||||
# passenden PTR-Record! FCrDNS verlangt:
|
||||
# 1. IP 1.2.3.4 löst per PTR auf mail.firma.de auf.
|
||||
# 2. mail.firma.de löst per A-Record wieder auf 1.2.3.4 auf!
|
||||
# ==============================================================================
|
||||
|
||||
# 🎯 TEILZIEL 1: Mindestens zwei autoritative Nameserver (NS) delegieren
|
||||
# 🎯 TEILZIEL 2: Reverse-Lookup-Zone für /24 Subnetz (1.168.192.in-addr.arpa) anlegen
|
||||
# 🎯 TEILZIEL 3: PTR-Record für Mailserver zur SPAM-Vermeidung eintragen
|
||||
# 🎯 TEILZIEL 4: Forward-Confirmed Reverse DNS (FCrDNS) validieren
|
||||
# 🎯 TEILZIEL 1 (TODO 1): IPv4 in PTR-Record-Namen umrechnen
|
||||
# Funktion: ipv4_to_ptr_name "$ipv4_address"
|
||||
# Parameter $1: IPv4-Adresse (z.B. "192.0.2.42")
|
||||
# Anforderungen:
|
||||
# - Kehre die 4 Oktette um: 42.2.0.192
|
||||
# - Hänge ".in-addr.arpa." an.
|
||||
# - Ausgabe: "42.2.0.192.in-addr.arpa."
|
||||
ipv4_to_ptr_name() {
|
||||
local ip="$1"
|
||||
# TODO 1: Oktette umkehren und in-addr.arpa anhängen
|
||||
return 0
|
||||
}
|
||||
|
||||
configure_ns_and_ptr() {
|
||||
local domain="${1:-it-praxisportal.de}"
|
||||
echo "▶ Analysiere DNS-Konfiguration für $domain..."
|
||||
# 🎯 TEILZIEL 2 (TODO 2): Vollständigen PTR-Record erzeugen
|
||||
# Funktion: format_ptr_record "$ipv4_address" "$target_hostname" "$ttl"
|
||||
# Parameter $1: IPv4-Adresse (z.B. "198.51.100.25")
|
||||
# Parameter $2: Ziel-Hostname (z.B. "mail.firma.de" oder "mail.firma.de.")
|
||||
# Parameter $3: TTL (Standard: 86400)
|
||||
# Anforderungen:
|
||||
# - Erzeuge den PTR-Namen via ipv4_to_ptr_name.
|
||||
# - Stelle sicher, dass der Ziel-Hostname mit einem Punkt "." endet.
|
||||
# - Ausgabe im BIND-Format:
|
||||
# <ptr_name> <ttl> IN PTR <target_hostname>.
|
||||
format_ptr_record() {
|
||||
local ip="$1"
|
||||
local target="$2"
|
||||
local ttl="${3:-86400}"
|
||||
# TODO 2: PTR-Record formatieren
|
||||
return 0
|
||||
}
|
||||
|
||||
# TODO: Implementiere die geforderten DNS-Prüfungen oder Record-Erstellungen
|
||||
return 1
|
||||
# 🎯 TEILZIEL 3 (TODO 3): Redundante NS-Delegation erzeugen
|
||||
# Funktion: format_ns_delegation "$zone_or_subdomain" "$ns1" "$ns2" "$ttl"
|
||||
# Parameter $1: Zonen- oder Subdomain-Name (z.B. "cloud.firma.de.")
|
||||
# Parameter $2: Primärer Nameserver (z.B. "ns1.provider.de.")
|
||||
# Parameter $3: Sekundärer Nameserver (z.B. "ns2.provider.de.")
|
||||
# Parameter $4: TTL (Standard: 86400)
|
||||
# Anforderungen:
|
||||
# - Formatiere zwei saubere BIND NS-Records.
|
||||
# - Stelle Schlusspunkte bei allen FQDNs sicher.
|
||||
format_ns_delegation() {
|
||||
local zone="$1"
|
||||
local ns1="$2"
|
||||
local ns2="$3"
|
||||
local ttl="${4:-86400}"
|
||||
# TODO 3: Erzeuge zwei NS-Records
|
||||
return 0
|
||||
}
|
||||
|
||||
# 🎯 TEILZIEL 4 (TODO 4): FCrDNS (Forward-Confirmed Reverse DNS) Validierung
|
||||
# Funktion: verify_fcrdns "$client_ip" "$ptr_hostname" "$forward_resolved_ip"
|
||||
# Parameter $1: Verbindende Client-IP (z.B. "192.0.2.50")
|
||||
# Parameter $2: Per Reverse-Lookup gefundener Hostname (z.B. "mail.firma.de")
|
||||
# Parameter $3: Per Forward-Lookup gefundene A-Record IP dieses Hostnamens
|
||||
# Anforderungen:
|
||||
# - Wenn client_ip == forward_resolved_ip:
|
||||
# Gib aus: "FCRDNS_PASS: Validiert! Forward- und Reverse-Lookup stimmen überein (${client_ip} <-> ${ptr_hostname})."
|
||||
# return 0
|
||||
# - Wenn client_ip != forward_resolved_ip:
|
||||
# Gib aus: "FCRDNS_FAIL: Spoofing-Verdacht! PTR '${ptr_hostname}' löst auf '${forward_resolved_ip}' auf, nicht '${client_ip}'!"
|
||||
# return 1
|
||||
verify_fcrdns() {
|
||||
local cip="$1"
|
||||
local ptr_host="$2"
|
||||
local fip="$3"
|
||||
# TODO 4: Validiere die FCrDNS-Übereinstimmung
|
||||
return 0
|
||||
}
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
configure_ns_and_ptr "$@"
|
||||
echo "=== Test Teilziel 1 ==="
|
||||
ipv4_to_ptr_name "192.0.2.42"
|
||||
echo ""
|
||||
echo "=== Test Teilziel 2 ==="
|
||||
format_ptr_record "192.0.2.42" "mail.firma.de" 86400
|
||||
echo ""
|
||||
echo "=== Test Teilziel 4 ==="
|
||||
verify_fcrdns "192.0.2.10" "mail.firma.de" "192.0.2.10"
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
<link rel="icon" type="image/svg+xml" href="../../../assets/favicon.svg">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>DNS 08: NS Delegation & PTR Reverse DNS</title>
|
||||
<title>DNS 08: NS & PTR Reverse DNS Lookups</title>
|
||||
<link rel="stylesheet" href="../../../assets/style.css">
|
||||
</head>
|
||||
<body>
|
||||
|
|
@ -12,46 +12,92 @@
|
|||
<div class="app-main">
|
||||
<header>
|
||||
<div class="header-container">
|
||||
<div class="header-badge">⚡ Nameserver & PTR</div>
|
||||
<h1>DNS 08: NS Delegation & PTR Reverse DNS</h1>
|
||||
<p>Delegierung an Nameserver (NS) und Reverse-Lookup-Zonen (in-addr.arpa / ip6.arpa) für FCrDNS.</p>
|
||||
<div class="header-badge">⚡ Resource Records</div>
|
||||
<h1>DNS 08: NS-Delegation, PTR Reverse DNS & FCrDNS</h1>
|
||||
<p>Verstehe Zonen-Delegation mit redundanten Nameservern, die Oktett-Umkehrung in in-addr.arpa und Mailserver-Validierung via FCrDNS.</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="container">
|
||||
<!-- 1. THEORIE: REVERSE DNS -->
|
||||
<section class="card">
|
||||
<h2>💡 Theorie & Praxiskonzept</h2>
|
||||
<p>Delegierung an Nameserver (NS) und Reverse-Lookup-Zonen (in-addr.arpa / ip6.arpa) für FCrDNS.</p>
|
||||
<div class="code-container">
|
||||
<div class="code-header">
|
||||
<span>DNS Command & Syntax Reference</span>
|
||||
<span>Tool: dig -x</span>
|
||||
</div>
|
||||
<pre><code># Wichtige Befehle & Zonensyntax:
|
||||
dig dig -x
|
||||
named-checkzone example.com /etc/bind/zones/db.example.com</code></pre>
|
||||
<h2>🔄 1. Reverse DNS & in-addr.arpa</h2>
|
||||
<p>Wie findet man heraus, welcher Server hinter der IP <code>188.245.100.5</code> steckt? Über die spezielle Reverse-Lookup-Zone <code>in-addr.arpa</code>:</p>
|
||||
|
||||
<pre><code># Reverse Lookup mit dig:
|
||||
dig -x 188.245.100.5 +short
|
||||
# Ausgabe: mail.it-praxisportal.de.
|
||||
|
||||
# Was dig im Hintergrund abfragt (Oktette umgekehrt!):
|
||||
dig 5.100.245.188.in-addr.arpa. PTR</code></pre>
|
||||
</section>
|
||||
|
||||
<!-- 2. THEORIE: FCRDNS -->
|
||||
<section class="card">
|
||||
<h2>🛡️ 2. Forward-Confirmed Reverse DNS (FCrDNS)</h2>
|
||||
<p>FCrDNS ist der De-facto-Standard für Spam-Schutz im E-Mail-Verkehr:</p>
|
||||
<div class="box box-tipp">
|
||||
<div class="box-title">💡 Warum fast alle Mails ohne PTR im Spam landen</div>
|
||||
<p>Jeder Serverbetreiber kann im SMTP-Handshake behaupten: <code>HELO mail.google.com</code>. Der empfangende Server prüft die reale IP via PTR-Record und testet anschließend den A-Record. Stimmt die Kette nicht überein, wird die Verbindung sofort abgewiesen!</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 3. CHEAT-SHEET -->
|
||||
<section class="card">
|
||||
<h2>🎯 Aufgaben & Checkliste</h2>
|
||||
<h2>📑 3. Cheat-Sheet: NS & PTR Syntax</h2>
|
||||
<div style="overflow-x: auto;">
|
||||
<table style="width: 100%; border-collapse: collapse; font-size: 0.9rem; text-align: left;">
|
||||
<thead>
|
||||
<tr style="border-bottom: 2px solid var(--border-color); background: var(--bg-subtle);">
|
||||
<th style="padding: 10px;">Record-Typ</th>
|
||||
<th style="padding: 10px;">Zweck</th>
|
||||
<th style="padding: 10px;">Syntax-Beispiel</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr style="border-bottom: 1px solid var(--border-color);">
|
||||
<td style="padding: 10px;"><code>NS</code></td>
|
||||
<td style="padding: 10px;">Zonen-Delegation</td>
|
||||
<td style="padding: 10px;"><code>firma.de. IN NS ns1.provider.de.</code></td>
|
||||
</tr>
|
||||
<tr style="border-bottom: 1px solid var(--border-color);">
|
||||
<td style="padding: 10px;"><code>PTR</code> (IPv4)</td>
|
||||
<td style="padding: 10px;">Reverse DNS (IP → Name)</td>
|
||||
<td style="padding: 10px;"><code>42.2.0.192.in-addr.arpa. IN PTR srv.de.</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 10px;"><code>PTR</code> (IPv6)</td>
|
||||
<td style="padding: 10px;">Reverse DNS (IPv6)</td>
|
||||
<td style="padding: 10px;"><code>1.0.0.0...b.d.0.1.0.0.2.ip6.arpa. IN PTR srv.de.</code></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 4. AUFGABEN CHECKLISTE -->
|
||||
<section class="card">
|
||||
<h2>🎯 Aufgaben & Checkliste in <code>aufgabe.sh</code></h2>
|
||||
<div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t1">
|
||||
<label class="task-label" for="t1"><strong>TODO 1:</strong> Mindestens zwei autoritative Nameserver (NS) delegieren</label>
|
||||
</div><div class="task-item">
|
||||
<label class="task-label" for="t1"><strong>TODO 1:</strong> <code>ipv4_to_ptr_name(ip)</code>: Oktette umkehren und in-addr.arpa bilden.</label>
|
||||
</div>
|
||||
<div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t2">
|
||||
<label class="task-label" for="t2"><strong>TODO 2:</strong> Reverse-Lookup-Zone für /24 Subnetz (1.168.192.in-addr.arpa) anlegen</label>
|
||||
</div><div class="task-item">
|
||||
<label class="task-label" for="t2"><strong>TODO 2:</strong> <code>format_ptr_record(ip, host, ttl)</code>: BIND PTR-Record formatieren.</label>
|
||||
</div>
|
||||
<div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t3">
|
||||
<label class="task-label" for="t3"><strong>TODO 3:</strong> PTR-Record für Mailserver zur SPAM-Vermeidung eintragen</label>
|
||||
</div><div class="task-item">
|
||||
<label class="task-label" for="t3"><strong>TODO 3:</strong> <code>format_ns_delegation(...)</code>: Redundante NS-Records erzeugen.</label>
|
||||
</div>
|
||||
<div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t4">
|
||||
<label class="task-label" for="t4"><strong>TODO 4:</strong> Forward-Confirmed Reverse DNS (FCrDNS) validieren</label>
|
||||
<label class="task-label" for="t4"><strong>TODO 4:</strong> <code>verify_fcrdns(cip, ptr, fip)</code>: FCrDNS Konsistenzprüfung umsetzen.</label>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 25px; display: flex; gap: 12px; flex-wrap: wrap;">
|
||||
<a href="../../../workspace.html?course=dns_records&track=track_2_core_records&chapter=08_ns_und_ptr_reverse_dns_lookups" class="btn" style="background: #0284c7; color: #fff; font-weight: 700;">💻 In Web-IDE öffnen →</a>
|
||||
<a href="../09_mx_mail_exchange_und_prioritaeten/index.html" class="btn btn-secondary">Weiter zu Modul →</a>
|
||||
<a href="../../../workspace.html?course=dns_records&track=track_2_core_records&chapter=08_ns_und_ptr_reverse_dns_lookups" class="btn" style="background: #1d4ed8; color: #fff; font-weight: 700;">In Web-IDE öffnen →</a>
|
||||
<a href="../09_mx_mail_exchange_und_prioritaeten/index.html" class="btn btn-secondary">Weiter zu Lehrpfad 3 (Modul 09) →</a>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
|
|
|||
|
|
@ -1,97 +1,63 @@
|
|||
#!/usr/bin/env bash
|
||||
# ==============================================================================
|
||||
# MUSTERLÖSUNG: DNS 08: NS Delegation & PTR Reverse DNS
|
||||
# MUSTERLÖSUNG: DNS 08: NS-DELEGATION, REVERSE DNS (PTR) & FCRDNS
|
||||
# ==============================================================================
|
||||
|
||||
configure_ns_and_ptr() {
|
||||
local domain="${1:-it-praxisportal.de}"
|
||||
local target_dir="${2:-.}"
|
||||
ipv4_to_ptr_name() {
|
||||
local ip="$1"
|
||||
[ -z "$ip" ] && return 1
|
||||
|
||||
echo "▶ Starte professionelle DNS-Operation für $domain..."
|
||||
IFS='.' read -ra octs <<< "$ip"
|
||||
if [ ${#octs[@]} -ne 4 ]; then
|
||||
echo "ERROR: Ungültige IPv4"
|
||||
return 1
|
||||
fi
|
||||
|
||||
case "08_ns_und_ptr_reverse_dns_lookups" in
|
||||
*01_dns_hierarchie*)
|
||||
echo "Host: www | SLD: it-praxisportal | TLD: de | Root: ."
|
||||
;;
|
||||
*02_rekursive*)
|
||||
echo "Simulation Iteration: Root (.) -> de. -> ns1.it-praxisportal.de -> 188.245.100.5"
|
||||
;;
|
||||
*03_ports_udp*)
|
||||
echo "Protocol: UDP/53 (Standard) | Fallback: TCP/53 | EDNS0 Buffer: 4096 bytes"
|
||||
;;
|
||||
*04_ttl*)
|
||||
echo "TTL Current: 86400s | Migration Planned TTL: 300s | Neg Cache: 3600s"
|
||||
;;
|
||||
*05_a_und_aaaa*)
|
||||
cat << 'ZONE' > "$target_dir/zone_records.txt"
|
||||
@ IN A 188.245.100.5
|
||||
@ IN AAAA 2a01:4f8:c010:d::1
|
||||
www IN A 188.245.100.5
|
||||
ZONE
|
||||
;;
|
||||
*06_cname*)
|
||||
echo "api IN CNAME app.it-praxisportal.de." > "$target_dir/cname_record.txt"
|
||||
;;
|
||||
*07_soa*)
|
||||
cat << 'ZONE' > "$target_dir/soa_record.txt"
|
||||
@ IN SOA ns1.it-praxisportal.de. hostmaster.it-praxisportal.de. (
|
||||
2026091201 ; Serial YYYYMMDDNN
|
||||
7200 ; Refresh (2h)
|
||||
3600 ; Retry (1h)
|
||||
1209600 ; Expire (2w)
|
||||
3600 ; Negative Cache TTL (1h)
|
||||
)
|
||||
ZONE
|
||||
;;
|
||||
*08_ns_und_ptr*)
|
||||
echo "100.245.188.in-addr.arpa. IN PTR mail.it-praxisportal.de." > "$target_dir/ptr_record.txt"
|
||||
;;
|
||||
*09_mx*)
|
||||
echo "@ IN MX 10 mail.it-praxisportal.de." > "$target_dir/mx_record.txt"
|
||||
echo "@ IN MX 20 backup-mail.it-praxisportal.de." >> "$target_dir/mx_record.txt"
|
||||
;;
|
||||
*10_txt_records*)
|
||||
echo '@ IN TXT "v=spf1 mx ip4:188.245.100.5 include:_spf.google.com -all"' > "$target_dir/spf_record.txt"
|
||||
;;
|
||||
*11_dkim*)
|
||||
echo 's1._domainkey IN TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz..."' > "$target_dir/dkim_record.txt"
|
||||
;;
|
||||
*12_dmarc*)
|
||||
echo '_dmarc IN TXT "v=DMARC1; p=reject; rua=mailto:dmarc-reports@it-praxisportal.de; pct=100; aspf=s"' > "$target_dir/dmarc_record.txt"
|
||||
;;
|
||||
*13_srv*)
|
||||
echo '_ldap._tcp.dc._msdcs.corp IN SRV 0 100 389 dc01.corp.it-praxisportal.de.' > "$target_dir/srv_record.txt"
|
||||
;;
|
||||
*14_caa*)
|
||||
echo '@ IN CAA 0 issue "letsencrypt.org"' > "$target_dir/caa_record.txt"
|
||||
echo '@ IN CAA 0 iodef "mailto:security@it-praxisportal.de"' >> "$target_dir/caa_record.txt"
|
||||
;;
|
||||
*15_dns_troubleshooting*)
|
||||
echo "STATUS: NOERROR | ANSWER: 1 | FLAGS: qr aa rd ra"
|
||||
;;
|
||||
*16_master*)
|
||||
cat << 'REPORT' > "$target_dir/dns_audit_report.json"
|
||||
{
|
||||
"domain": "it-praxisportal.de",
|
||||
"audit_status": "PASSED",
|
||||
"score": 100,
|
||||
"soa_serial_valid": true,
|
||||
"ns_redundancy": 2,
|
||||
"email_security": {
|
||||
"spf": "valid (-all)",
|
||||
"dkim": "present",
|
||||
"dmarc": "enforced (p=reject)"
|
||||
},
|
||||
"dnssec": "active"
|
||||
}
|
||||
REPORT
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "✅ DNS-Operation erfolgreich abgeschlossen."
|
||||
echo "${octs[3]}.${octs[2]}.${octs[1]}.${octs[0]}.in-addr.arpa."
|
||||
return 0
|
||||
}
|
||||
|
||||
format_ptr_record() {
|
||||
local ip="$1"
|
||||
local target="$2"
|
||||
local ttl="${3:-86400}"
|
||||
|
||||
local ptr_name=$(ipv4_to_ptr_name "$ip")
|
||||
[[ "$target" != *. ]] && target="${target}."
|
||||
|
||||
printf "%-30s %-7s IN PTR %s\n" "$ptr_name" "$ttl" "$target"
|
||||
return 0
|
||||
}
|
||||
|
||||
format_ns_delegation() {
|
||||
local zone="$1"
|
||||
local ns1="$2"
|
||||
local ns2="$3"
|
||||
local ttl="${4:-86400}"
|
||||
|
||||
[[ "$zone" != *. ]] && zone="${zone}."
|
||||
[[ "$ns1" != *. ]] && ns1="${ns1}."
|
||||
[[ "$ns2" != *. ]] && ns2="${ns2}."
|
||||
|
||||
printf "%-25s %-7s IN NS %s\n" "$zone" "$ttl" "$ns1"
|
||||
printf "%-25s %-7s IN NS %s\n" "$zone" "$ttl" "$ns2"
|
||||
return 0
|
||||
}
|
||||
|
||||
verify_fcrdns() {
|
||||
local cip="$1"
|
||||
local ptr_host="$2"
|
||||
local fip="$3"
|
||||
|
||||
if [ "$cip" = "$fip" ]; then
|
||||
echo "FCRDNS_PASS: Validiert! Forward- und Reverse-Lookup stimmen überein (${cip} <-> ${ptr_host})."
|
||||
return 0
|
||||
else
|
||||
echo "FCRDNS_FAIL: Spoofing-Verdacht! PTR '${ptr_host}' löst auf '${fip}' auf, nicht '${cip}'!"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
configure_ns_and_ptr "$@"
|
||||
ipv4_to_ptr_name "$@"
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -1,36 +1,88 @@
|
|||
#!/usr/bin/env bash
|
||||
# ==============================================================================
|
||||
# TESTSUITE: DNS 08: NS Delegation & PTR Reverse DNS
|
||||
# 🧪 TESTSUITE: DNS 08 - NS-DELEGATION & REVERSE DNS (PTR)
|
||||
# ==============================================================================
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
BLUE='\033[0;34m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
TARGET_SCRIPT="${1:-aufgabe.sh}"
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
echo -e "${BLUE}🧪 TESTSUITE: DNS 08: NS Delegation & PTR Reverse DNS${NC}"
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
TOTAL_TESTS=0
|
||||
PASSED_TESTS=0
|
||||
FAILED_TESTS=0
|
||||
|
||||
TEST_DIR=$(mktemp -d /tmp/dns_test_XXXXXX)
|
||||
trap 'rm -rf "$TEST_DIR"' EXIT
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
echo -e "${BLUE}🧪 TESTSUITE: DNS 08: NS-Delegation & Reverse DNS (PTR)${NC}"
|
||||
echo -e "${BLUE} Ziel-Skript: ${YELLOW}${TARGET_SCRIPT}${NC}"
|
||||
echo -e "${BLUE}================================================================${NC}\n"
|
||||
|
||||
if [ ! -f "$SCRIPT_DIR/$TARGET_SCRIPT" ]; then
|
||||
echo -e "${RED}❌ Fehler: Datei '$TARGET_SCRIPT' nicht gefunden!${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
source "$SCRIPT_DIR/$TARGET_SCRIPT"
|
||||
|
||||
# Führe Funktion aus
|
||||
configure_ns_and_ptr "example.com" "$TEST_DIR"
|
||||
RET=$?
|
||||
run_test() {
|
||||
local name="$1"
|
||||
local cmd="$2"
|
||||
((TOTAL_TESTS++))
|
||||
echo -e "▶ Test ${TOTAL_TESTS}: ${name}"
|
||||
if eval "$cmd"; then
|
||||
echo -e " ${GREEN}✓ BESTANDEN${NC}\n"
|
||||
((PASSED_TESTS++))
|
||||
else
|
||||
echo -e " ${RED}✗ FEHLGESCHLAGEN${NC}\n"
|
||||
((FAILED_TESTS++))
|
||||
fi
|
||||
}
|
||||
|
||||
if [ $RET -eq 0 ]; then
|
||||
echo -e "${GREEN}✓ Test 1: Funktion configure_ns_and_ptr existiert und gibt 0 zurück.${NC}"
|
||||
echo -e "${GREEN}✓ Test 2: DNS Resource Records / Syntax validiert.${NC}"
|
||||
echo -e "${GREEN}✓ Test 3: Teilziele 1 bis 4 erfolgreich abgeschlossen.${NC}"
|
||||
echo -e "${GREEN}🎉 ERFOLG: Alle DNS-Prüfungen in DNS 08: NS Delegation & PTR Reverse DNS bestanden!${NC}"
|
||||
# TEST 1: IPv4 zu PTR Name (Umkehrung der Oktette)
|
||||
run_test "ipv4_to_ptr_name kehrt 192.0.2.42 korrekt zu 42.2.0.192.in-addr.arpa. um" '
|
||||
OUT=$(ipv4_to_ptr_name "192.0.2.42")
|
||||
[ "$OUT" = "42.2.0.192.in-addr.arpa." ]
|
||||
'
|
||||
|
||||
# TEST 2: Vollständiger PTR-Record
|
||||
run_test "format_ptr_record erzeugt standardkonformen BIND PTR-Record" '
|
||||
OUT=$(format_ptr_record "188.245.100.5" "mail.it-praxisportal.de" 86400)
|
||||
echo "$OUT" | grep -q "5.100.245.188.in-addr.arpa." && \
|
||||
echo "$OUT" | grep -q "PTR" && \
|
||||
echo "$OUT" | grep -q "mail.it-praxisportal.de."
|
||||
'
|
||||
|
||||
# TEST 3: NS Delegation mit 2 redundanten Servern
|
||||
run_test "format_ns_delegation erzeugt 2 redundante NS Records" '
|
||||
OUT=$(format_ns_delegation "intern.firma.de" "ns1.provider.de" "ns2.provider.de" 86400)
|
||||
echo "$OUT" | grep -q "intern.firma.de." && \
|
||||
echo "$OUT" | grep -q "ns1.provider.de." && \
|
||||
echo "$OUT" | grep -q "ns2.provider.de."
|
||||
'
|
||||
|
||||
# TEST 4: FCrDNS Bestanden
|
||||
run_test "verify_fcrdns meldet FCRDNS_PASS bei IP-Gleichheit" '
|
||||
OUT=$(verify_fcrdns "188.245.100.5" "mail.it-praxisportal.de" "188.245.100.5")
|
||||
echo "$OUT" | grep -q "FCRDNS_PASS: Validiert"
|
||||
'
|
||||
|
||||
# TEST 5: FCrDNS Fehlgeschlagen (Spoofing)
|
||||
run_test "verify_fcrdns meldet FCRDNS_FAIL bei IP-Diskrepanz" '
|
||||
OUT=$(verify_fcrdns "188.245.100.5" "mail.evil.com" "10.0.0.99")
|
||||
echo "$OUT" | grep -q "FCRDNS_FAIL: Spoofing-Verdacht"
|
||||
'
|
||||
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
if [ "$FAILED_TESTS" -eq 0 ]; then
|
||||
echo -e "${GREEN}🎉 ERFOLG: Alle ${PASSED_TESTS}/${TOTAL_TESTS} Tests in DNS 08 erfolgreich bestanden!${NC}"
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
exit 0
|
||||
else
|
||||
echo -e "${RED}❌ FEHLER: Skript fehlgeschlagen mit Status $RET${NC}"
|
||||
echo -e "${RED}❌ FEHLER: ${FAILED_TESTS} von ${TOTAL_TESTS} Tests fehlgeschlagen.${NC}"
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -1,18 +1,43 @@
|
|||
# DNS 09: MX Records & Mail-Prioritäten
|
||||
# DNS 09: MX-Records & Mail-Prioritäten
|
||||
|
||||
Willkommen zum Kurs **DNS-Records, Domain Name System & E-Mail-Sicherheit** im IT-Praxisportal!
|
||||
Willkommen zu **Modul 09** des Kurses **DNS-Records, Domain Name System & E-Mail-Sicherheit**!
|
||||
|
||||
## 💡 Das Wichtigste in Kürze
|
||||
- **Ziel:** Mail Exchange (MX) Records, Prioritätswerte (10, 20), Backup-Mailserver und Fallback-Routing.
|
||||
- **Wichtigstes Tool:** `dig MX`
|
||||
In diesem Modul lernst du, wie E-Mail-Zustellung über das Domain Name System gesteuert wird: **MX-Records (Mail Exchange)**, **Prioritäts-Routing** für Hochverfügbarkeit und Store-and-Forward sowie die strikte RFC-Regel, warum MX-Ziele niemals auf CNAME-Records verweisen dürfen.
|
||||
|
||||
## 🎯 Deine Aufgaben (Checkliste)
|
||||
1. **Teilziel 1:** Primären MX-Record mit Priorität 10 anlegen
|
||||
2. **Teilziel 2:** Sekundären Backup-MX mit Priorität 20 für Ausfallsicherheit eintragen
|
||||
3. **Teilziel 3:** Verbot von IP-Adressen und CNAMEs im MX-Ziel beachten
|
||||
4. **Teilziel 4:** Mail-Zustellungsroute mit dig MX prüfen
|
||||
---
|
||||
|
||||
## 💡 1. Das Wichtigste in Kürze
|
||||
|
||||
### Was ist ein MX-Record?
|
||||
Ein MX-Record bestimmt den Mail-Server, der für den Empfang von E-Mails einer Domäne zuständig ist:
|
||||
```dns
|
||||
it-praxisportal.de. 86400 IN MX 10 mail.it-praxisportal.de.
|
||||
it-praxisportal.de. 86400 IN MX 20 backup-mail.relay.de.
|
||||
```
|
||||
|
||||
### Die Prioritäts-Regel
|
||||
- **Niedrigere Zahl = Höhere Priorität!**
|
||||
- Der sendende MTA kontaktiert zwingend zuerst Server mit Priorität `10`.
|
||||
- Nur wenn dieser Server offline oder nicht erreichbar ist, weicht der Sender auf Priorität `20` aus.
|
||||
|
||||
### Die CNAME-Falle nach RFC 2181
|
||||
> **RFC 2181 / RFC 5321:** *„The domain name in an MX record MUST NOT be an alias (CNAME).“*
|
||||
Ein MX-Ziel muss immer direkt auf einen A- oder AAAA-Record verweisen. Ein CNAME führt bei vielen Mail-Gateways zu Schleifen und Zustellabbrüchen.
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Deine Aufgaben in `aufgabe.sh`
|
||||
|
||||
1. **TODO 1 (`format_mx_record`):** Validiere die numerische Priorität und formatiere einen BIND MX-Record.
|
||||
2. **TODO 2 (`sort_mx_records`):** Lies eine Liste von MX-Records ein und sortiere sie aufsteigend nach Priorität.
|
||||
3. **TODO 3 (`validate_mx_target_type`):** Verhindere CNAME-Ziele nach RFC 2181.
|
||||
4. **TODO 4 (`generate_redundant_mx_setup`):** Erstelle ein redundantes MX-Paar (Primary Prio 10, Backup Prio 20).
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Tests ausführen
|
||||
|
||||
Führe im Terminal folgenden Befehl aus:
|
||||
```bash
|
||||
bash test_aufgabe.sh
|
||||
```
|
||||
|
|
|
|||
|
|
@ -1,21 +1,97 @@
|
|||
#!/usr/bin/env bash
|
||||
# ==============================================================================
|
||||
# 🛡️ Mail Routing - DNS 09: MX Records & Mail-Prioritäten
|
||||
# 🌐 DNS 09: MX-RECORDS (MAIL EXCHANGE) & PRIORITÄTS-ROUTING
|
||||
# ==============================================================================
|
||||
#
|
||||
# DAS ROUTING VON E-MAILS:
|
||||
# Wenn jemand eine E-Mail an "user@firma.de" sendet, schlägt der sendende
|
||||
# Mailserver (MTA) im DNS nach dem MX-Record der Domain "firma.de" nach.
|
||||
#
|
||||
# PRIORITÄT (PREFERENCE):
|
||||
# - 16-Bit Integer (Zahlenwert z.B. 10, 20, 50).
|
||||
# - DIE GOLDENE REGEL: Niedrigere Zahl bedeutet HÖHERE Priorität!
|
||||
# - Der sendende Server MUSS zuerst den Server mit der kleinsten Prioritätszahl ansteuern.
|
||||
# - Antwortet dieser nicht (Timeout), weicht der Sender auf die nächsthöhere Zahl aus.
|
||||
#
|
||||
# DIE EISERNE MX-ZIEL-REGEL (RFC 2181 & RFC 5321):
|
||||
# - Das Ziel eines MX-Records MUSS ein Hostname sein, der direkt auf einen A- oder
|
||||
# AAAA-Record verweist!
|
||||
# - Ein MX-Ziel darf NIEMALS ein CNAME-Alias sein!
|
||||
# FALSCH: firma.de. IN MX 10 relay.cloudhoster.com. (wo relay ein CNAME ist)
|
||||
# RICHTIG: firma.de. IN MX 10 mail.firma.de. (mail hat A-Record)
|
||||
# ==============================================================================
|
||||
|
||||
# 🎯 TEILZIEL 1: Primären MX-Record mit Priorität 10 anlegen
|
||||
# 🎯 TEILZIEL 2: Sekundären Backup-MX mit Priorität 20 für Ausfallsicherheit eintragen
|
||||
# 🎯 TEILZIEL 3: Verbot von IP-Adressen und CNAMEs im MX-Ziel beachten
|
||||
# 🎯 TEILZIEL 4: Mail-Zustellungsroute mit dig MX prüfen
|
||||
# 🎯 TEILZIEL 1 (TODO 1): Syntaktisch validen MX-Record erzeugen
|
||||
# Funktion: format_mx_record "$zone" "$priority" "$mail_host" "$ttl"
|
||||
# Parameter $1: Domäne/Zone (z.B. "it-praxisportal.de" oder "@")
|
||||
# Parameter $2: Priorität als Ganzzahl >= 0 (z.B. 10)
|
||||
# Parameter $3: Hostname des Mailservers (z.B. "mail.it-praxisportal.de")
|
||||
# Parameter $4: TTL (Standard: 86400)
|
||||
# Anforderungen:
|
||||
# - Prüfe, ob priority eine Zahl >= 0 ist (sonst Fehler "ERROR: Priorität muss Ganzzahl >= 0 sein").
|
||||
# - Stelle sicher, dass mail_host mit einem Punkt "." endet.
|
||||
# - Formatiere den Record:
|
||||
# <zone> <ttl> IN MX <priority> <mail_host>.
|
||||
format_mx_record() {
|
||||
local zone="$1"
|
||||
local prio="$2"
|
||||
local mhost="$3"
|
||||
local ttl="${4:-86400}"
|
||||
# TODO 1: Validiere Priorität und formatiere MX-Record
|
||||
return 0
|
||||
}
|
||||
|
||||
setup_mx_records() {
|
||||
local domain="${1:-it-praxisportal.de}"
|
||||
echo "▶ Analysiere DNS-Konfiguration für $domain..."
|
||||
# 🎯 TEILZIEL 2 (TODO 2): MX-Ziele nach Priorität sortieren
|
||||
# Funktion: sort_mx_records "$mx_file"
|
||||
# Parameter $1: Pfad zu einer Datei mit MX-Records
|
||||
# Anforderungen:
|
||||
# - Extrahiere die Priorität und den Hostnamen aus jeder Zeile.
|
||||
# - Sortiere die Mailserver aufsteigend nach Priorität (10 vor 20 vor 30).
|
||||
# - Gib für jeden Server eine Zeile aus im Format:
|
||||
# PRIO=<prio> | HOST=<mail_host>
|
||||
sort_mx_records() {
|
||||
local mfile="$1"
|
||||
# TODO 2: Sortiere MX-Ziele aufsteigend nach Priorität
|
||||
return 0
|
||||
}
|
||||
|
||||
# TODO: Implementiere die geforderten DNS-Prüfungen oder Record-Erstellungen
|
||||
return 1
|
||||
# 🎯 TEILZIEL 3 (TODO 3): MX-Ziel Validierung (RFC 2181 CNAME-Verbot)
|
||||
# Funktion: validate_mx_target_type "$target_record_type"
|
||||
# Parameter $1: Der Record-Typ, auf den das MX-Ziel verweist ("A", "AAAA", "CNAME", etc.)
|
||||
# Anforderungen:
|
||||
# - Wenn target_record_type == "CNAME":
|
||||
# Gib aus: "ERROR_RFC2181: MX-Ziel darf NIEMALS ein CNAME sein! Muss A oder AAAA sein."
|
||||
# return 1
|
||||
# - Wenn target_record_type == "A" oder "AAAA":
|
||||
# Gib aus: "VALID_MX_TARGET: Ziel verweist direkt auf A/AAAA IP-Adresse."
|
||||
# return 0
|
||||
validate_mx_target_type() {
|
||||
local rtype=$(echo "$1" | tr '[:lower:]' '[:upper:]')
|
||||
# TODO 3: Prüfe das CNAME-Verbot nach RFC 2181
|
||||
return 0
|
||||
}
|
||||
|
||||
# 🎯 TEILZIEL 4 (TODO 4): Redundantes MX-Paar (Primary & Backup) erzeugen
|
||||
# Funktion: generate_redundant_mx_setup "$zone" "$primary_host" "$backup_host" "$ttl"
|
||||
# Parameter $1: Zonenname
|
||||
# Parameter $2: Primärer Mailserver (Priorität 10)
|
||||
# Parameter $3: Backup / Fallback Mailserver (Priorität 20)
|
||||
# Parameter $4: TTL (Standard: 86400)
|
||||
# Anforderungen:
|
||||
# - Erzeuge zwei MX-Records: Primary mit Prio 10, Backup mit Prio 20.
|
||||
generate_redundant_mx_setup() {
|
||||
local zone="$1"
|
||||
local prim="$2"
|
||||
local bak="$3"
|
||||
local ttl="${4:-86400}"
|
||||
# TODO 4: Erzeuge Primary und Backup MX-Records
|
||||
return 0
|
||||
}
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
setup_mx_records "$@"
|
||||
echo "=== Test Teilziel 1 ==="
|
||||
format_mx_record "@" 10 "mail.firma.de" 86400
|
||||
echo ""
|
||||
echo "=== Test Teilziel 3 ==="
|
||||
validate_mx_target_type "CNAME"
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
<link rel="icon" type="image/svg+xml" href="../../../assets/favicon.svg">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>DNS 09: MX Records & Mail-Prioritäten</title>
|
||||
<title>DNS 09: MX Records & Mail-Prioritäten</title>
|
||||
<link rel="stylesheet" href="../../../assets/style.css">
|
||||
</head>
|
||||
<body>
|
||||
|
|
@ -12,46 +12,92 @@
|
|||
<div class="app-main">
|
||||
<header>
|
||||
<div class="header-container">
|
||||
<div class="header-badge">🛡️ Mail Routing</div>
|
||||
<h1>DNS 09: MX Records & Mail-Prioritäten</h1>
|
||||
<p>Mail Exchange (MX) Records, Prioritätswerte (10, 20), Backup-Mailserver und Fallback-Routing.</p>
|
||||
<div class="header-badge">🛡️ E-Mail-Sicherheit</div>
|
||||
<h1>DNS 09: MX-Records & Mail-Prioritäten</h1>
|
||||
<p>Lerne, wie der globale Mail-Austausch gesteuert wird: Prioritäts-Routing, Store-and-Forward Redundanz und die RFC-Regel zum CNAME-Verbot.</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="container">
|
||||
<!-- 1. THEORIE: MX RECORDAUFBAU -->
|
||||
<section class="card">
|
||||
<h2>💡 Theorie & Praxiskonzept</h2>
|
||||
<p>Mail Exchange (MX) Records, Prioritätswerte (10, 20), Backup-Mailserver und Fallback-Routing.</p>
|
||||
<div class="code-container">
|
||||
<div class="code-header">
|
||||
<span>DNS Command & Syntax Reference</span>
|
||||
<span>Tool: dig MX</span>
|
||||
</div>
|
||||
<pre><code># Wichtige Befehle & Zonensyntax:
|
||||
dig dig MX
|
||||
named-checkzone example.com /etc/bind/zones/db.example.com</code></pre>
|
||||
<h2>✉️ 1. Der MX-Record: E-Mail-Routing im Internet</h2>
|
||||
<p>Ohne MX-Record würden Mail-Server versuchen, Mails direkt an den A-Record der Domain zu senden. Mit MX trennst du Web-Traffic und E-Mail-Infrastruktur sauber voneinander:</p>
|
||||
|
||||
<pre><code># MX-Records für it-praxisportal.de:
|
||||
@ 86400 IN MX 10 mail.it-praxisportal.de. ; Primärer Mailserver (Prio 10)
|
||||
@ 86400 IN MX 20 backup.it-praxisportal.de. ; Backup-Relay (Prio 20)
|
||||
|
||||
; Die zugehörigen A-Records (ZWINGEND erforderlich!):
|
||||
mail IN A 188.245.100.5
|
||||
backup IN A 192.0.2.25</code></pre>
|
||||
</section>
|
||||
|
||||
<!-- 2. THEORIE: DIE CNAME FALLE -->
|
||||
<section class="card">
|
||||
<h2>⚠️ 2. Die CNAME-Falle bei MX-Zielen</h2>
|
||||
<div class="box box-warnung">
|
||||
<div class="box-title">Häufiger IHK-Prüfungsfehler: MX auf CNAME</div>
|
||||
<p>Ein Administrator trägt ein: <code>@ IN MX 10 mail.firma.de.</code> und legt gleichzeitig an: <code>mail IN CNAME office365.com.</code>. Das verstößt gegen <strong>RFC 2181</strong>! Viele Enterprise-Mailserver brechen die Zustellung sofort mit <em>554 Delivery Failure</em> ab.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 3. CHEAT-SHEET -->
|
||||
<section class="card">
|
||||
<h2>🎯 Aufgaben & Checkliste</h2>
|
||||
<h2>📑 3. Cheat-Sheet: MX-Prioritäten</h2>
|
||||
<div style="overflow-x: auto;">
|
||||
<table style="width: 100%; border-collapse: collapse; font-size: 0.9rem; text-align: left;">
|
||||
<thead>
|
||||
<tr style="border-bottom: 2px solid var(--border-color); background: var(--bg-subtle);">
|
||||
<th style="padding: 10px;">Prioritätswert</th>
|
||||
<th style="padding: 10px;">Rolle</th>
|
||||
<th style="padding: 10px;">Verhalten des sendenden Servers</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr style="border-bottom: 1px solid var(--border-color);">
|
||||
<td style="padding: 10px;"><code>10</code> (Niedrigste Zahl)</td>
|
||||
<td style="padding: 10px;"><strong>Primary MTA</strong></td>
|
||||
<td style="padding: 10px;">Wird immer zuerst kontaktiert</td>
|
||||
</tr>
|
||||
<tr style="border-bottom: 1px solid var(--border-color);">
|
||||
<td style="padding: 10px;"><code>20</code> (Höhere Zahl)</td>
|
||||
<td style="padding: 10px;"><strong>Secondary / Backup</strong></td>
|
||||
<td style="padding: 10px;">Wird kontaktiert, wenn Prio 10 nach Timeouts nicht antwortet</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 10px;">Gleiche Zahl (z.B. zweimal <code>10</code>)</td>
|
||||
<td style="padding: 10px;"><strong>Load Balancing</strong></td>
|
||||
<td style="padding: 10px;">Sender verteilt Mails zufällig / gleichmäßig auf beide Server</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 4. AUFGABEN CHECKLISTE -->
|
||||
<section class="card">
|
||||
<h2>🎯 Aufgaben & Checkliste in <code>aufgabe.sh</code></h2>
|
||||
<div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t1">
|
||||
<label class="task-label" for="t1"><strong>TODO 1:</strong> Primären MX-Record mit Priorität 10 anlegen</label>
|
||||
</div><div class="task-item">
|
||||
<label class="task-label" for="t1"><strong>TODO 1:</strong> <code>format_mx_record(...)</code>: MX-Record mit Prioritätsprüfung erzeugen.</label>
|
||||
</div>
|
||||
<div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t2">
|
||||
<label class="task-label" for="t2"><strong>TODO 2:</strong> Sekundären Backup-MX mit Priorität 20 für Ausfallsicherheit eintragen</label>
|
||||
</div><div class="task-item">
|
||||
<label class="task-label" for="t2"><strong>TODO 2:</strong> <code>sort_mx_records(file)</code>: Mail-Ziele nach Priorität aufsteigend ordnen.</label>
|
||||
</div>
|
||||
<div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t3">
|
||||
<label class="task-label" for="t3"><strong>TODO 3:</strong> Verbot von IP-Adressen und CNAMEs im MX-Ziel beachten</label>
|
||||
</div><div class="task-item">
|
||||
<label class="task-label" for="t3"><strong>TODO 3:</strong> <code>validate_mx_target_type(...)</code>: RFC 2181 CNAME-Verbot durchsetzen.</label>
|
||||
</div>
|
||||
<div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t4">
|
||||
<label class="task-label" for="t4"><strong>TODO 4:</strong> Mail-Zustellungsroute mit dig MX prüfen</label>
|
||||
<label class="task-label" for="t4"><strong>TODO 4:</strong> <code>generate_redundant_mx_setup(...)</code>: Redundantes MX-Paar (10/20) generieren.</label>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 25px; display: flex; gap: 12px; flex-wrap: wrap;">
|
||||
<a href="../../../workspace.html?course=dns_records&track=track_3_email_security&chapter=09_mx_mail_exchange_und_prioritaeten" class="btn" style="background: #0284c7; color: #fff; font-weight: 700;">💻 In Web-IDE öffnen →</a>
|
||||
<a href="../10_txt_records_und_spf_sender_policy_framework/index.html" class="btn btn-secondary">Weiter zu Modul →</a>
|
||||
<a href="../../../workspace.html?course=dns_records&track=track_3_email_security&chapter=09_mx_mail_exchange_und_prioritaeten" class="btn" style="background: #1d4ed8; color: #fff; font-weight: 700;">In Web-IDE öffnen →</a>
|
||||
<a href="../10_txt_records_und_spf_sender_policy_framework/index.html" class="btn btn-secondary">Weiter zu Modul 10 →</a>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
|
|
|||
|
|
@ -1,97 +1,71 @@
|
|||
#!/usr/bin/env bash
|
||||
# ==============================================================================
|
||||
# MUSTERLÖSUNG: DNS 09: MX Records & Mail-Prioritäten
|
||||
# MUSTERLÖSUNG: DNS 09: MX-RECORDS & PRIORITÄTS-ROUTING
|
||||
# ==============================================================================
|
||||
|
||||
setup_mx_records() {
|
||||
local domain="${1:-it-praxisportal.de}"
|
||||
local target_dir="${2:-.}"
|
||||
format_mx_record() {
|
||||
local zone="$1"
|
||||
local prio="$2"
|
||||
local mhost="$3"
|
||||
local ttl="${4:-86400}"
|
||||
|
||||
echo "▶ Starte professionelle DNS-Operation für $domain..."
|
||||
if [[ ! "$prio" =~ ^[0-9]+$ ]]; then
|
||||
echo "ERROR: Priorität muss Ganzzahl >= 0 sein"
|
||||
return 1
|
||||
fi
|
||||
|
||||
case "09_mx_mail_exchange_und_prioritaeten" in
|
||||
*01_dns_hierarchie*)
|
||||
echo "Host: www | SLD: it-praxisportal | TLD: de | Root: ."
|
||||
;;
|
||||
*02_rekursive*)
|
||||
echo "Simulation Iteration: Root (.) -> de. -> ns1.it-praxisportal.de -> 188.245.100.5"
|
||||
;;
|
||||
*03_ports_udp*)
|
||||
echo "Protocol: UDP/53 (Standard) | Fallback: TCP/53 | EDNS0 Buffer: 4096 bytes"
|
||||
;;
|
||||
*04_ttl*)
|
||||
echo "TTL Current: 86400s | Migration Planned TTL: 300s | Neg Cache: 3600s"
|
||||
;;
|
||||
*05_a_und_aaaa*)
|
||||
cat << 'ZONE' > "$target_dir/zone_records.txt"
|
||||
@ IN A 188.245.100.5
|
||||
@ IN AAAA 2a01:4f8:c010:d::1
|
||||
www IN A 188.245.100.5
|
||||
ZONE
|
||||
;;
|
||||
*06_cname*)
|
||||
echo "api IN CNAME app.it-praxisportal.de." > "$target_dir/cname_record.txt"
|
||||
;;
|
||||
*07_soa*)
|
||||
cat << 'ZONE' > "$target_dir/soa_record.txt"
|
||||
@ IN SOA ns1.it-praxisportal.de. hostmaster.it-praxisportal.de. (
|
||||
2026091201 ; Serial YYYYMMDDNN
|
||||
7200 ; Refresh (2h)
|
||||
3600 ; Retry (1h)
|
||||
1209600 ; Expire (2w)
|
||||
3600 ; Negative Cache TTL (1h)
|
||||
)
|
||||
ZONE
|
||||
;;
|
||||
*08_ns_und_ptr*)
|
||||
echo "100.245.188.in-addr.arpa. IN PTR mail.it-praxisportal.de." > "$target_dir/ptr_record.txt"
|
||||
;;
|
||||
*09_mx*)
|
||||
echo "@ IN MX 10 mail.it-praxisportal.de." > "$target_dir/mx_record.txt"
|
||||
echo "@ IN MX 20 backup-mail.it-praxisportal.de." >> "$target_dir/mx_record.txt"
|
||||
;;
|
||||
*10_txt_records*)
|
||||
echo '@ IN TXT "v=spf1 mx ip4:188.245.100.5 include:_spf.google.com -all"' > "$target_dir/spf_record.txt"
|
||||
;;
|
||||
*11_dkim*)
|
||||
echo 's1._domainkey IN TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz..."' > "$target_dir/dkim_record.txt"
|
||||
;;
|
||||
*12_dmarc*)
|
||||
echo '_dmarc IN TXT "v=DMARC1; p=reject; rua=mailto:dmarc-reports@it-praxisportal.de; pct=100; aspf=s"' > "$target_dir/dmarc_record.txt"
|
||||
;;
|
||||
*13_srv*)
|
||||
echo '_ldap._tcp.dc._msdcs.corp IN SRV 0 100 389 dc01.corp.it-praxisportal.de.' > "$target_dir/srv_record.txt"
|
||||
;;
|
||||
*14_caa*)
|
||||
echo '@ IN CAA 0 issue "letsencrypt.org"' > "$target_dir/caa_record.txt"
|
||||
echo '@ IN CAA 0 iodef "mailto:security@it-praxisportal.de"' >> "$target_dir/caa_record.txt"
|
||||
;;
|
||||
*15_dns_troubleshooting*)
|
||||
echo "STATUS: NOERROR | ANSWER: 1 | FLAGS: qr aa rd ra"
|
||||
;;
|
||||
*16_master*)
|
||||
cat << 'REPORT' > "$target_dir/dns_audit_report.json"
|
||||
{
|
||||
"domain": "it-praxisportal.de",
|
||||
"audit_status": "PASSED",
|
||||
"score": 100,
|
||||
"soa_serial_valid": true,
|
||||
"ns_redundancy": 2,
|
||||
"email_security": {
|
||||
"spf": "valid (-all)",
|
||||
"dkim": "present",
|
||||
"dmarc": "enforced (p=reject)"
|
||||
},
|
||||
"dnssec": "active"
|
||||
[[ "$mhost" != *. ]] && mhost="${mhost}."
|
||||
|
||||
printf "%-15s %-7s IN MX %-5s %s\n" "$zone" "$ttl" "$prio" "$mhost"
|
||||
return 0
|
||||
}
|
||||
REPORT
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "✅ DNS-Operation erfolgreich abgeschlossen."
|
||||
sort_mx_records() {
|
||||
local mfile="$1"
|
||||
[ ! -f "$mfile" ] && return 1
|
||||
|
||||
# Finde Zeilen mit MX
|
||||
# Format typischerweise: zone ttl IN MX prio host
|
||||
awk '$3 == "MX" || $4 == "MX" {
|
||||
for (i=1; i<=NF; i++) {
|
||||
if ($i == "MX") {
|
||||
prio = $(i+1)
|
||||
host = $(i+2)
|
||||
print prio, host
|
||||
}
|
||||
}
|
||||
}' "$mfile" | sort -n -k1,1 | while read -r p h; do
|
||||
echo "PRIO=$p | HOST=$h"
|
||||
done
|
||||
return 0
|
||||
}
|
||||
|
||||
validate_mx_target_type() {
|
||||
local rtype=$(echo "$1" | tr '[:lower:]' '[:upper:]')
|
||||
|
||||
if [ "$rtype" = "CNAME" ]; then
|
||||
echo "ERROR_RFC2181: MX-Ziel darf NIEMALS ein CNAME sein! Muss A oder AAAA sein."
|
||||
return 1
|
||||
elif [ "$rtype" = "A" ] || [ "$rtype" = "AAAA" ]; then
|
||||
echo "VALID_MX_TARGET: Ziel verweist direkt auf A/AAAA IP-Adresse."
|
||||
return 0
|
||||
else
|
||||
echo "UNKNOWN_TYPE"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
generate_redundant_mx_setup() {
|
||||
local zone="$1"
|
||||
local prim="$2"
|
||||
local bak="$3"
|
||||
local ttl="${4:-86400}"
|
||||
|
||||
format_mx_record "$zone" 10 "$prim" "$ttl"
|
||||
format_mx_record "$zone" 20 "$bak" "$ttl"
|
||||
return 0
|
||||
}
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
setup_mx_records "$@"
|
||||
format_mx_record "$@"
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -1,36 +1,100 @@
|
|||
#!/usr/bin/env bash
|
||||
# ==============================================================================
|
||||
# TESTSUITE: DNS 09: MX Records & Mail-Prioritäten
|
||||
# 🧪 TESTSUITE: DNS 09 - MX-RECORDS & PRIORITÄTEN
|
||||
# ==============================================================================
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
BLUE='\033[0;34m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
TARGET_SCRIPT="${1:-aufgabe.sh}"
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
echo -e "${BLUE}🧪 TESTSUITE: DNS 09: MX Records & Mail-Prioritäten${NC}"
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
TOTAL_TESTS=0
|
||||
PASSED_TESTS=0
|
||||
FAILED_TESTS=0
|
||||
|
||||
TEST_DIR=$(mktemp -d /tmp/dns_test_XXXXXX)
|
||||
trap 'rm -rf "$TEST_DIR"' EXIT
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
echo -e "${BLUE}🧪 TESTSUITE: DNS 09: MX-Records & Prioritäten${NC}"
|
||||
echo -e "${BLUE} Ziel-Skript: ${YELLOW}${TARGET_SCRIPT}${NC}"
|
||||
echo -e "${BLUE}================================================================${NC}\n"
|
||||
|
||||
if [ ! -f "$SCRIPT_DIR/$TARGET_SCRIPT" ]; then
|
||||
echo -e "${RED}❌ Fehler: Datei '$TARGET_SCRIPT' nicht gefunden!${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
source "$SCRIPT_DIR/$TARGET_SCRIPT"
|
||||
|
||||
# Führe Funktion aus
|
||||
setup_mx_records "example.com" "$TEST_DIR"
|
||||
RET=$?
|
||||
run_test() {
|
||||
local name="$1"
|
||||
local cmd="$2"
|
||||
((TOTAL_TESTS++))
|
||||
echo -e "▶ Test ${TOTAL_TESTS}: ${name}"
|
||||
if eval "$cmd"; then
|
||||
echo -e " ${GREEN}✓ BESTANDEN${NC}\n"
|
||||
((PASSED_TESTS++))
|
||||
else
|
||||
echo -e " ${RED}✗ FEHLGESCHLAGEN${NC}\n"
|
||||
((FAILED_TESTS++))
|
||||
fi
|
||||
}
|
||||
|
||||
if [ $RET -eq 0 ]; then
|
||||
echo -e "${GREEN}✓ Test 1: Funktion setup_mx_records existiert und gibt 0 zurück.${NC}"
|
||||
echo -e "${GREEN}✓ Test 2: DNS Resource Records / Syntax validiert.${NC}"
|
||||
echo -e "${GREEN}✓ Test 3: Teilziele 1 bis 4 erfolgreich abgeschlossen.${NC}"
|
||||
echo -e "${GREEN}🎉 ERFOLG: Alle DNS-Prüfungen in DNS 09: MX Records & Mail-Prioritäten bestanden!${NC}"
|
||||
# TEST 1: Valider MX Record
|
||||
run_test "format_mx_record erzeugt standardkonformen MX Record" '
|
||||
OUT=$(format_mx_record "@" 10 "mail.it-praxisportal.de" 86400)
|
||||
echo "$OUT" | grep -q "@" && \
|
||||
echo "$OUT" | grep -q "MX" && \
|
||||
echo "$OUT" | grep -q "10" && \
|
||||
echo "$OUT" | grep -q "mail.it-praxisportal.de."
|
||||
'
|
||||
|
||||
# TEST 2: Prioritäts-Validierung (Zahlenprüfung)
|
||||
run_test "format_mx_record lehnt ungültige Priorität (abc) ab" '
|
||||
format_mx_record "@" "abc" "mail.de" | grep -q "ERROR: Priorität muss Ganzzahl"
|
||||
'
|
||||
|
||||
# TEST 3: CNAME Verbot nach RFC 2181
|
||||
run_test "validate_mx_target_type verbietet CNAME als MX-Ziel" '
|
||||
validate_mx_target_type "CNAME" | grep -q "ERROR_RFC2181"
|
||||
'
|
||||
|
||||
# TEST 4: A-Record als valides Ziel
|
||||
run_test "validate_mx_target_type erlaubt A-Record als MX-Ziel" '
|
||||
validate_mx_target_type "A" | grep -q "VALID_MX_TARGET"
|
||||
'
|
||||
|
||||
# TEST 5: MX Sortierung nach Priorität
|
||||
run_test "sort_mx_records sortiert 10 vor 20 vor 30" '
|
||||
TMP_MX=$(mktemp /tmp/mx_sort_XXXXXX)
|
||||
cat << "EOF" > "$TMP_MX"
|
||||
@ IN MX 30 fallback.mail.de.
|
||||
@ IN MX 10 primary.mail.de.
|
||||
@ IN MX 20 secondary.mail.de.
|
||||
EOF
|
||||
OUT=$(sort_mx_records "$TMP_MX")
|
||||
rm -f "$TMP_MX"
|
||||
FIRST=$(echo "$OUT" | head -n 1)
|
||||
echo "$FIRST" | grep -q "PRIO=10" && \
|
||||
echo "$FIRST" | grep -q "primary.mail.de."
|
||||
'
|
||||
|
||||
# TEST 6: Redundantes MX-Paar (10 & 20)
|
||||
run_test "generate_redundant_mx_setup erzeugt Prio 10 und 20" '
|
||||
OUT=$(generate_redundant_mx_setup "firma.de" "m1.firma.de" "m2.firma.de" 86400)
|
||||
echo "$OUT" | grep -q "10.*m1.firma.de." && \
|
||||
echo "$OUT" | grep -q "20.*m2.firma.de."
|
||||
'
|
||||
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
if [ "$FAILED_TESTS" -eq 0 ]; then
|
||||
echo -e "${GREEN}🎉 ERFOLG: Alle ${PASSED_TESTS}/${TOTAL_TESTS} Tests in DNS 09 erfolgreich bestanden!${NC}"
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
exit 0
|
||||
else
|
||||
echo -e "${RED}❌ FEHLER: Skript fehlgeschlagen mit Status $RET${NC}"
|
||||
echo -e "${RED}❌ FEHLER: ${FAILED_TESTS} von ${TOTAL_TESTS} Tests fehlgeschlagen.${NC}"
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -1,18 +1,50 @@
|
|||
# DNS 10: SPF Sender Policy Framework (TXT)
|
||||
# DNS 10: SPF (Sender Policy Framework) & das 10-Lookup Limit
|
||||
|
||||
Willkommen zum Kurs **DNS-Records, Domain Name System & E-Mail-Sicherheit** im IT-Praxisportal!
|
||||
Willkommen zu **Modul 10** des Kurses **DNS-Records, Domain Name System & E-Mail-Sicherheit**!
|
||||
|
||||
## 💡 Das Wichtigste in Kürze
|
||||
- **Ziel:** Schutz vor E-Mail-Spoofing: Autorisierte Absender-IPs im TXT-Record definieren (v=spf1 mx ip4:... -all).
|
||||
- **Wichtigstes Tool:** `dig TXT`
|
||||
In diesem Modul lernst du den ersten der drei Pfeiler moderner E-Mail-Sicherheit kennen: das **Sender Policy Framework (SPF, RFC 7208)**. Du erfährst, wie du per DNS-TXT-Record festlegst, welche Server Mails für deine Domain versenden dürfen, welche Risiken bei `+all` drohen und warum das **10-DNS-Lookup-Limit** viele Admins zur Verzweiflung bringt.
|
||||
|
||||
## 🎯 Deine Aufgaben (Checkliste)
|
||||
1. **Teilziel 1:** Gültigen SPF TXT-Record für die Domain erstellen
|
||||
2. **Teilziel 2:** Mechanismen (ip4, a, mx, include) kombinieren
|
||||
3. **Teilziel 3:** Unterschied Hardfail (-all) vs. Softfail (~all) implementieren
|
||||
4. **Teilziel 4:** Das 10-DNS-Lookup-Limit des SPF-Standards (RFC 7208) einhalten
|
||||
---
|
||||
|
||||
## 💡 1. Das Wichtigste in Kürze
|
||||
|
||||
### Wie SPF funktioniert
|
||||
Ein Absender schickt eine Mail mit `From: chef@firma.de`. Der empfangende Server fragt den TXT-Record von `firma.de` ab und vergleicht die IP-Adresse des Verbindungsaufbaus mit den im SPF-Record erlaubten IPs und Hostnamen.
|
||||
|
||||
```dns
|
||||
@ IN TXT "v=spf1 mx ip4:188.245.100.5 include:_spf.google.com -all"
|
||||
```
|
||||
|
||||
### Die Mechanismen
|
||||
- `mx`: Erlaubt alle eigenen MX-Server.
|
||||
- `ip4:<ip>`: Erlaubt konkrete IPv4-Adressen oder Subnetze.
|
||||
- `include:<domain>`: Übernimmt die Regeln eines Cloud-Providers (z.B. Google Workspace oder Microsoft 365).
|
||||
|
||||
### Die Qualifier am Ende
|
||||
| Qualifier | Name | Empfänger-Aktion |
|
||||
| :--- | :--- | :--- |
|
||||
| **`-all`** | HardFail | **Strikte Ablehnung:** Nicht gelistete Server werden geblockt (Best Practice!). |
|
||||
| **`~all`** | SoftFail | **Warnung:** Mails werden angenommen, aber als Spam verdächtigt. |
|
||||
| **`?all`** | Neutral | Keine Aussage / Testmodus. |
|
||||
| **`+all`** | Pass | **Sicherheitsdesaster!** Erlaubt buchstäblich jedem Rechner der Welt das Versenden in deinem Namen! |
|
||||
|
||||
### Das 10-Lookup Limit (RFC 7208)
|
||||
Um Denial-of-Service-Angriffe auf DNS-Server zu verhindern, bricht die SPF-Prüfung sofort mit `PermError` ab, wenn zur Evaluierung **mehr als 10 DNS-Abfragen** (durch `include`, `a`, `mx`, `redirect`) nötig sind.
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Deine Aufgaben in `aufgabe.sh`
|
||||
|
||||
1. **TODO 1 (`build_spf_record`):** Bilde einen vollständigen SPF-String aus Parametern zusammen.
|
||||
2. **TODO 2 (`count_spf_lookups`):** Zähle die DNS-Lookups und schlage Alarm bei Überschreitung des 10er-Limits.
|
||||
3. **TODO 3 (`audit_spf_qualifier`):** Prüfe die Sicherheitsstufe des All-Qualifiers (`-all` vs. `+all`).
|
||||
4. **TODO 4 (`format_spf_txt_record`):** Erstelle den finalen BIND TXT-Record.
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Tests ausführen
|
||||
|
||||
Führe im Terminal folgenden Befehl aus:
|
||||
```bash
|
||||
bash test_aufgabe.sh
|
||||
```
|
||||
|
|
|
|||
|
|
@ -1,21 +1,120 @@
|
|||
#!/usr/bin/env bash
|
||||
# ==============================================================================
|
||||
# 🛡️ SPF Schutz - DNS 10: SPF Sender Policy Framework (TXT)
|
||||
# 🌐 DNS 10: SPF (SENDER POLICY FRAMEWORK) & DAS 10-LOOKUP LIMIT
|
||||
# ==============================================================================
|
||||
#
|
||||
# WAS IST SPF (RFC 7208)?
|
||||
# Das Sender Policy Framework verhindert E-Mail-Spoofing. Der Domaininhaber
|
||||
# hinterlegt einen TXT-Record im DNS, der festlegt, welche Server und IP-Adressen
|
||||
# berechtigt sind, Mails mit dieser Absenderdomäne zu verschicken.
|
||||
#
|
||||
# SYNTAX EINES SPF-RECORDS:
|
||||
# @ IN TXT "v=spf1 mx ip4:188.245.100.5 include:_spf.google.com -all"
|
||||
#
|
||||
# DIE BESTANDTEILE:
|
||||
# 1. Versionskennung: "v=spf1" (muss exakt am Anfang stehen)
|
||||
# 2. Mechanismen:
|
||||
# - "mx" : Alle im MX-Record gelisteten Server dürfen senden
|
||||
# - "ip4:<ip>/<cidr>" : Konkrete IPv4-Adressen oder Netze
|
||||
# - "ip6:<ipv6>" : IPv6-Adressen
|
||||
# - "include:<domain>" : Übernimmt die SPF-Regeln eines Dienstleisters
|
||||
# 3. Der All-Qualifier (Das Urteil für alle anderen):
|
||||
# - "-all" (HardFail): Strikte Ablehnung unberechtigter Server (empfohlen!)
|
||||
# - "~all" (SoftFail): Akzeptieren, aber als Spam markieren
|
||||
# - "?all" (Neutral): Keine Aussage
|
||||
# - "+all" (Pass): EXTREM GEFÄHRLICH! Erlaubt jedem Spammer den Versand!
|
||||
#
|
||||
# ⚠️ DAS 10-DNS-LOOKUP-LIMIT:
|
||||
# Ein SPF-Check darf maximal 10 DNS-Lookups auslösen ("include", "a", "mx", "ptr").
|
||||
# Überschreitet der Record dieses Limit, bricht der Empfänger mit "PermError" ab!
|
||||
# ==============================================================================
|
||||
|
||||
# 🎯 TEILZIEL 1: Gültigen SPF TXT-Record für die Domain erstellen
|
||||
# 🎯 TEILZIEL 2: Mechanismen (ip4, a, mx, include) kombinieren
|
||||
# 🎯 TEILZIEL 3: Unterschied Hardfail (-all) vs. Softfail (~all) implementieren
|
||||
# 🎯 TEILZIEL 4: Das 10-DNS-Lookup-Limit des SPF-Standards (RFC 7208) einhalten
|
||||
# 🎯 TEILZIEL 1 (TODO 1): Syntaktisch validen SPF-String zusammenbauen
|
||||
# Funktion: build_spf_record "$mx_allowed" "$ipv4_list" "$include_list" "$qualifier"
|
||||
# Parameter $1: "true" oder "false" (ob der "mx" Mechanismus enthalten sein soll)
|
||||
# Parameter $2: Leerzeichen-separierte Liste von IPv4-Adressen (z.B. "192.0.2.1 198.51.100.10")
|
||||
# Parameter $3: Leerzeichen-separierte Liste von Includes (z.B. "_spf.google.com")
|
||||
# Parameter $4: Qualifier ("-all" oder "~all")
|
||||
# Ausgabe:
|
||||
# v=spf1 [mx] [ip4:...] [include:...] <qualifier>
|
||||
# Beispiel:
|
||||
# v=spf1 mx ip4:192.0.2.1 include:_spf.google.com -all
|
||||
build_spf_record() {
|
||||
local use_mx="$1"
|
||||
local ips="$2"
|
||||
local incs="$3"
|
||||
local qual="${4:--all}"
|
||||
# TODO 1: Bilde den SPF-String
|
||||
return 0
|
||||
}
|
||||
|
||||
create_spf_record() {
|
||||
local domain="${1:-it-praxisportal.de}"
|
||||
echo "▶ Analysiere DNS-Konfiguration für $domain..."
|
||||
# 🎯 TEILZIEL 2 (TODO 2): SPF DNS-Lookups zählen (RFC 7208 Limit)
|
||||
# Funktion: count_spf_lookups "$spf_string"
|
||||
# Parameter $1: Ein vollständiger SPF-String (z.B. "v=spf1 a mx include:a.de include:b.de -all")
|
||||
# Anforderungen:
|
||||
# - Zähle jedes Vorkommen von Mechanismen, die einen DNS-Lookup auslösen:
|
||||
# * "include:"
|
||||
# * "a" oder "a:"
|
||||
# * "mx" oder "mx:"
|
||||
# * "ptr"
|
||||
# * "redirect="
|
||||
# - Wenn count <= 10:
|
||||
# Gib aus: "LOOKUP_COUNT=<count> | RFC7208_OK"
|
||||
# return 0
|
||||
# - Wenn count > 10:
|
||||
# Gib aus: "LOOKUP_LIMIT_EXCEEDED: <count> Lookups überschreiten das 10er-Limit!"
|
||||
# return 1
|
||||
count_spf_lookups() {
|
||||
local spf="$1"
|
||||
# TODO 2: Zähle die DNS-Lookups
|
||||
return 0
|
||||
}
|
||||
|
||||
# TODO: Implementiere die geforderten DNS-Prüfungen oder Record-Erstellungen
|
||||
return 1
|
||||
# 🎯 TEILZIEL 3 (TODO 3): All-Qualifier Sicherheit prüfen
|
||||
# Funktion: audit_spf_qualifier "$spf_string"
|
||||
# Parameter $1: Ein SPF-String
|
||||
# Anforderungen:
|
||||
# - Wenn "+all" enthalten ist:
|
||||
# Gib aus: "DANGEROUS: +all erlaubt jedem Server weltweit das Spoofing!"
|
||||
# return 1
|
||||
# - Wenn "?all" enthalten ist:
|
||||
# Gib aus: "WEAK: ?all ist neutral und bietet keinen Fälschungsschutz."
|
||||
# return 1
|
||||
# - Wenn "~all" enthalten ist:
|
||||
# Gib aus: "SOFTFAIL: ~all akzeptabel für Testphasen."
|
||||
# return 0
|
||||
# - Wenn "-all" enthalten ist:
|
||||
# Gib aus: "SECURE: -all HardFail erzwingt strikten Schutz."
|
||||
# return 0
|
||||
audit_spf_qualifier() {
|
||||
local spf="$1"
|
||||
# TODO 3: Prüfe den Qualifier
|
||||
return 0
|
||||
}
|
||||
|
||||
# 🎯 TEILZIEL 4 (TODO 4): Vollständigen BIND TXT-Record erzeugen
|
||||
# Funktion: format_spf_txt_record "$domain" "$spf_string" "$ttl"
|
||||
# Parameter $1: Domäne (z.B. "@" oder "firma.de.")
|
||||
# Parameter $2: Der SPF-String
|
||||
# Parameter $3: TTL (Standard: 3600)
|
||||
# Anforderungen:
|
||||
# - Formatiere als BIND TXT Record mit Anführungszeichen:
|
||||
# <domain> <ttl> IN TXT "<spf_string>"
|
||||
format_spf_txt_record() {
|
||||
local domain="$1"
|
||||
local spf="$2"
|
||||
local ttl="${3:-3600}"
|
||||
# TODO 4: Formatiere den BIND TXT-Record
|
||||
return 0
|
||||
}
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
create_spf_record "$@"
|
||||
echo "=== Test Teilziel 1 ==="
|
||||
build_spf_record "true" "192.0.2.1" "_spf.google.com" "-all"
|
||||
echo ""
|
||||
echo "=== Test Teilziel 2 ==="
|
||||
count_spf_lookups "v=spf1 mx include:_spf.google.com -all"
|
||||
echo ""
|
||||
echo "=== Test Teilziel 3 ==="
|
||||
audit_spf_qualifier "v=spf1 +all"
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
<link rel="icon" type="image/svg+xml" href="../../../assets/favicon.svg">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>DNS 10: SPF Sender Policy Framework (TXT)</title>
|
||||
<title>DNS 10: SPF Sender Policy Framework</title>
|
||||
<link rel="stylesheet" href="../../../assets/style.css">
|
||||
</head>
|
||||
<body>
|
||||
|
|
@ -12,46 +12,104 @@
|
|||
<div class="app-main">
|
||||
<header>
|
||||
<div class="header-container">
|
||||
<div class="header-badge">🛡️ SPF Schutz</div>
|
||||
<h1>DNS 10: SPF Sender Policy Framework (TXT)</h1>
|
||||
<p>Schutz vor E-Mail-Spoofing: Autorisierte Absender-IPs im TXT-Record definieren (v=spf1 mx ip4:... -all).</p>
|
||||
<div class="header-badge">🛡️ E-Mail-Sicherheit</div>
|
||||
<h1>DNS 10: SPF (Sender Policy Framework) & 10-Lookup Limit</h1>
|
||||
<p>Verhindere Mail-Spoofing mit TXT-Records: Lerne die SPF-Syntax, sichere Qualifier (-all) und das gefürchtete 10-DNS-Lookup-Limit nach RFC 7208.</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="container">
|
||||
<!-- 1. THEORIE: SPF ANATOMIE -->
|
||||
<section class="card">
|
||||
<h2>💡 Theorie & Praxiskonzept</h2>
|
||||
<p>Schutz vor E-Mail-Spoofing: Autorisierte Absender-IPs im TXT-Record definieren (v=spf1 mx ip4:... -all).</p>
|
||||
<div class="code-container">
|
||||
<div class="code-header">
|
||||
<span>DNS Command & Syntax Reference</span>
|
||||
<span>Tool: dig TXT</span>
|
||||
</div>
|
||||
<pre><code># Wichtige Befehle & Zonensyntax:
|
||||
dig dig TXT
|
||||
named-checkzone example.com /etc/bind/zones/db.example.com</code></pre>
|
||||
<h2>🛡️ 1. Anatomie eines professionellen SPF-Records</h2>
|
||||
<p>SPF wird als Standard <code>TXT</code>-Record auf der Domäne hinterlegt. Der String beginnt immer zwingend mit <code>v=spf1</code>:</p>
|
||||
|
||||
<pre><code># BIND Zonendatei:
|
||||
@ IN TXT "v=spf1 mx ip4:188.245.100.5 include:_spf.google.com -all"
|
||||
|
||||
# Bedeutung der einzelnen Komponenten:
|
||||
# 1. v=spf1 -> Kennung als SPF Version 1
|
||||
# 2. mx -> Eigene MX-Server dürfen senden
|
||||
# 3. ip4:188.245.100.5 -> Dedizierte Webserver-IP darf senden
|
||||
# 4. include:_spf.google.com -> Google Workspace Mailserver dürfen senden
|
||||
# 5. -all -> Alle anderen Server werden strikt abgewiesen!</code></pre>
|
||||
</section>
|
||||
|
||||
<!-- 2. THEORIE: DAS 10 LOOKUP LIMIT -->
|
||||
<section class="card">
|
||||
<h2>🛑 2. Das gefürchtete 10-Lookup-Limit</h2>
|
||||
<div class="box box-warnung">
|
||||
<div class="box-title">RFC 7208 Void-Lookup Limit: Max. 10 DNS-Queries</div>
|
||||
<p>Jeder <code>include</code>, <code>a</code> und <code>mx</code> Mechanismus löst eine neue DNS-Anfrage aus. Verschachtelte Includes (z.B. Office 365 + Zendesk + Mailchimp) summieren sich schnell auf über 10 Lookups. In diesem Fall verwerfen viele Empfänger-Server die Mail mit <strong>SPF PermError</strong>!</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 3. CHEAT-SHEET -->
|
||||
<section class="card">
|
||||
<h2>🎯 Aufgaben & Checkliste</h2>
|
||||
<h2>📑 3. Cheat-Sheet: Die 4 SPF-Qualifier</h2>
|
||||
<div style="overflow-x: auto;">
|
||||
<table style="width: 100%; border-collapse: collapse; font-size: 0.9rem; text-align: left;">
|
||||
<thead>
|
||||
<tr style="border-bottom: 2px solid var(--border-color); background: var(--bg-subtle);">
|
||||
<th style="padding: 10px;">Qualifier</th>
|
||||
<th style="padding: 10px;">Name</th>
|
||||
<th style="padding: 10px;">Auswirkung beim Empfänger</th>
|
||||
<th style="padding: 10px;">Empfehlung</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr style="border-bottom: 1px solid var(--border-color);">
|
||||
<td style="padding: 10px;"><code>-all</code></td>
|
||||
<td style="padding: 10px;">HardFail</td>
|
||||
<td style="padding: 10px;">Mails von ungelisteten IPs werden abgewiesen</td>
|
||||
<td style="padding: 10px; color: #166534; font-weight: 700;">Best Practice</td>
|
||||
</tr>
|
||||
<tr style="border-bottom: 1px solid var(--border-color);">
|
||||
<td style="padding: 10px;"><code>~all</code></td>
|
||||
<td style="padding: 10px;">SoftFail</td>
|
||||
<td style="padding: 10px;">Wird angenommen, aber als Spam markiert</td>
|
||||
<td style="padding: 10px; color: #92400e;">Nur für Übergangsphase</td>
|
||||
</tr>
|
||||
<tr style="border-bottom: 1px solid var(--border-color);">
|
||||
<td style="padding: 10px;"><code>?all</code></td>
|
||||
<td style="padding: 10px;">Neutral</td>
|
||||
<td style="padding: 10px;">Keine Schutzwirkung</td>
|
||||
<td style="padding: 10px;">Vermeiden</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 10px;"><code>+all</code></td>
|
||||
<td style="padding: 10px;">Pass</td>
|
||||
<td style="padding: 10px;">Jeder Server weltweit darf fälschen!</td>
|
||||
<td style="padding: 10px; color: #991b1b; font-weight: 800;">VERBOTEN</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 4. AUFGABEN CHECKLISTE -->
|
||||
<section class="card">
|
||||
<h2>🎯 Aufgaben & Checkliste in <code>aufgabe.sh</code></h2>
|
||||
<div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t1">
|
||||
<label class="task-label" for="t1"><strong>TODO 1:</strong> Gültigen SPF TXT-Record für die Domain erstellen</label>
|
||||
</div><div class="task-item">
|
||||
<label class="task-label" for="t1"><strong>TODO 1:</strong> <code>build_spf_record(...)</code>: Modularen SPF-String aufbauen.</label>
|
||||
</div>
|
||||
<div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t2">
|
||||
<label class="task-label" for="t2"><strong>TODO 2:</strong> Mechanismen (ip4, a, mx, include) kombinieren</label>
|
||||
</div><div class="task-item">
|
||||
<label class="task-label" for="t2"><strong>TODO 2:</strong> <code>count_spf_lookups(spf)</code>: DNS-Lookup-Zähler gegen 10er-Limit prüfen.</label>
|
||||
</div>
|
||||
<div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t3">
|
||||
<label class="task-label" for="t3"><strong>TODO 3:</strong> Unterschied Hardfail (-all) vs. Softfail (~all) implementieren</label>
|
||||
</div><div class="task-item">
|
||||
<label class="task-label" for="t3"><strong>TODO 3:</strong> <code>audit_spf_qualifier(spf)</code>: Gefährliches <code>+all</code> abfangen und <code>-all</code> verifizieren.</label>
|
||||
</div>
|
||||
<div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t4">
|
||||
<label class="task-label" for="t4"><strong>TODO 4:</strong> Das 10-DNS-Lookup-Limit des SPF-Standards (RFC 7208) einhalten</label>
|
||||
<label class="task-label" for="t4"><strong>TODO 4:</strong> <code>format_spf_txt_record(...)</code>: Validen BIND TXT-Record schreiben.</label>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 25px; display: flex; gap: 12px; flex-wrap: wrap;">
|
||||
<a href="../../../workspace.html?course=dns_records&track=track_3_email_security&chapter=10_txt_records_und_spf_sender_policy_framework" class="btn" style="background: #0284c7; color: #fff; font-weight: 700;">💻 In Web-IDE öffnen →</a>
|
||||
<a href="../11_dkim_domainkeys_identified_mail/index.html" class="btn btn-secondary">Weiter zu Modul →</a>
|
||||
<a href="../../../workspace.html?course=dns_records&track=track_3_email_security&chapter=10_txt_records_und_spf_sender_policy_framework" class="btn" style="background: #1d4ed8; color: #fff; font-weight: 700;">In Web-IDE öffnen →</a>
|
||||
<a href="../11_dkim_domainkeys_identified_mail/index.html" class="btn btn-secondary">Weiter zu Modul 11 →</a>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
|
|
|||
|
|
@ -1,97 +1,92 @@
|
|||
#!/usr/bin/env bash
|
||||
# ==============================================================================
|
||||
# MUSTERLÖSUNG: DNS 10: SPF Sender Policy Framework (TXT)
|
||||
# MUSTERLÖSUNG: DNS 10: SPF (SENDER POLICY FRAMEWORK) & 10-LOOKUP LIMIT
|
||||
# ==============================================================================
|
||||
|
||||
create_spf_record() {
|
||||
local domain="${1:-it-praxisportal.de}"
|
||||
local target_dir="${2:-.}"
|
||||
build_spf_record() {
|
||||
local use_mx="$1"
|
||||
local ips="$2"
|
||||
local incs="$3"
|
||||
local qual="${4:--all}"
|
||||
|
||||
echo "▶ Starte professionelle DNS-Operation für $domain..."
|
||||
local res="v=spf1"
|
||||
|
||||
case "10_txt_records_und_spf_sender_policy_framework" in
|
||||
*01_dns_hierarchie*)
|
||||
echo "Host: www | SLD: it-praxisportal | TLD: de | Root: ."
|
||||
;;
|
||||
*02_rekursive*)
|
||||
echo "Simulation Iteration: Root (.) -> de. -> ns1.it-praxisportal.de -> 188.245.100.5"
|
||||
;;
|
||||
*03_ports_udp*)
|
||||
echo "Protocol: UDP/53 (Standard) | Fallback: TCP/53 | EDNS0 Buffer: 4096 bytes"
|
||||
;;
|
||||
*04_ttl*)
|
||||
echo "TTL Current: 86400s | Migration Planned TTL: 300s | Neg Cache: 3600s"
|
||||
;;
|
||||
*05_a_und_aaaa*)
|
||||
cat << 'ZONE' > "$target_dir/zone_records.txt"
|
||||
@ IN A 188.245.100.5
|
||||
@ IN AAAA 2a01:4f8:c010:d::1
|
||||
www IN A 188.245.100.5
|
||||
ZONE
|
||||
;;
|
||||
*06_cname*)
|
||||
echo "api IN CNAME app.it-praxisportal.de." > "$target_dir/cname_record.txt"
|
||||
;;
|
||||
*07_soa*)
|
||||
cat << 'ZONE' > "$target_dir/soa_record.txt"
|
||||
@ IN SOA ns1.it-praxisportal.de. hostmaster.it-praxisportal.de. (
|
||||
2026091201 ; Serial YYYYMMDDNN
|
||||
7200 ; Refresh (2h)
|
||||
3600 ; Retry (1h)
|
||||
1209600 ; Expire (2w)
|
||||
3600 ; Negative Cache TTL (1h)
|
||||
)
|
||||
ZONE
|
||||
;;
|
||||
*08_ns_und_ptr*)
|
||||
echo "100.245.188.in-addr.arpa. IN PTR mail.it-praxisportal.de." > "$target_dir/ptr_record.txt"
|
||||
;;
|
||||
*09_mx*)
|
||||
echo "@ IN MX 10 mail.it-praxisportal.de." > "$target_dir/mx_record.txt"
|
||||
echo "@ IN MX 20 backup-mail.it-praxisportal.de." >> "$target_dir/mx_record.txt"
|
||||
;;
|
||||
*10_txt_records*)
|
||||
echo '@ IN TXT "v=spf1 mx ip4:188.245.100.5 include:_spf.google.com -all"' > "$target_dir/spf_record.txt"
|
||||
;;
|
||||
*11_dkim*)
|
||||
echo 's1._domainkey IN TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz..."' > "$target_dir/dkim_record.txt"
|
||||
;;
|
||||
*12_dmarc*)
|
||||
echo '_dmarc IN TXT "v=DMARC1; p=reject; rua=mailto:dmarc-reports@it-praxisportal.de; pct=100; aspf=s"' > "$target_dir/dmarc_record.txt"
|
||||
;;
|
||||
*13_srv*)
|
||||
echo '_ldap._tcp.dc._msdcs.corp IN SRV 0 100 389 dc01.corp.it-praxisportal.de.' > "$target_dir/srv_record.txt"
|
||||
;;
|
||||
*14_caa*)
|
||||
echo '@ IN CAA 0 issue "letsencrypt.org"' > "$target_dir/caa_record.txt"
|
||||
echo '@ IN CAA 0 iodef "mailto:security@it-praxisportal.de"' >> "$target_dir/caa_record.txt"
|
||||
;;
|
||||
*15_dns_troubleshooting*)
|
||||
echo "STATUS: NOERROR | ANSWER: 1 | FLAGS: qr aa rd ra"
|
||||
;;
|
||||
*16_master*)
|
||||
cat << 'REPORT' > "$target_dir/dns_audit_report.json"
|
||||
{
|
||||
"domain": "it-praxisportal.de",
|
||||
"audit_status": "PASSED",
|
||||
"score": 100,
|
||||
"soa_serial_valid": true,
|
||||
"ns_redundancy": 2,
|
||||
"email_security": {
|
||||
"spf": "valid (-all)",
|
||||
"dkim": "present",
|
||||
"dmarc": "enforced (p=reject)"
|
||||
},
|
||||
"dnssec": "active"
|
||||
if [ "$use_mx" = "true" ]; then
|
||||
res="$res mx"
|
||||
fi
|
||||
|
||||
if [ -n "$ips" ]; then
|
||||
for ip in $ips; do
|
||||
res="$res ip4:$ip"
|
||||
done
|
||||
fi
|
||||
|
||||
if [ -n "$incs" ]; then
|
||||
for inc in $incs; do
|
||||
res="$res include:$inc"
|
||||
done
|
||||
fi
|
||||
|
||||
res="$res $qual"
|
||||
echo "$res"
|
||||
return 0
|
||||
}
|
||||
REPORT
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "✅ DNS-Operation erfolgreich abgeschlossen."
|
||||
count_spf_lookups() {
|
||||
local spf="$1"
|
||||
local count=0
|
||||
|
||||
# Zerlege in Tokens
|
||||
for token in $spf; do
|
||||
case "$token" in
|
||||
include:*|redirect=*)
|
||||
((count++))
|
||||
;;
|
||||
a|a:*|mx|mx:*|ptr|ptr:*)
|
||||
((count++))
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ "$count" -le 10 ]; then
|
||||
echo "LOOKUP_COUNT=$count | RFC7208_OK"
|
||||
return 0
|
||||
else
|
||||
echo "LOOKUP_LIMIT_EXCEEDED: $count Lookups überschreiten das 10er-Limit!"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
audit_spf_qualifier() {
|
||||
local spf="$1"
|
||||
|
||||
if [[ "$spf" =~ \+all ]]; then
|
||||
echo "DANGEROUS: +all erlaubt jedem Server weltweit das Spoofing!"
|
||||
return 1
|
||||
elif [[ "$spf" =~ \?all ]]; then
|
||||
echo "WEAK: ?all ist neutral und bietet keinen Fälschungsschutz."
|
||||
return 1
|
||||
elif [[ "$spf" =~ \~all ]]; then
|
||||
echo "SOFTFAIL: ~all akzeptabel für Testphasen."
|
||||
return 0
|
||||
elif [[ "$spf" =~ \-all ]]; then
|
||||
echo "SECURE: -all HardFail erzwingt strikten Schutz."
|
||||
return 0
|
||||
else
|
||||
echo "ERROR: Kein gültiger All-Qualifier gefunden"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
format_spf_txt_record() {
|
||||
local domain="$1"
|
||||
local spf="$2"
|
||||
local ttl="${3:-3600}"
|
||||
|
||||
printf "%-15s %-7s IN TXT \"%s\"\n" "$domain" "$ttl" "$spf"
|
||||
return 0
|
||||
}
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
create_spf_record "$@"
|
||||
build_spf_record "$@"
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -1,36 +1,92 @@
|
|||
#!/usr/bin/env bash
|
||||
# ==============================================================================
|
||||
# TESTSUITE: DNS 10: SPF Sender Policy Framework (TXT)
|
||||
# 🧪 TESTSUITE: DNS 10 - SPF SENDER POLICY FRAMEWORK
|
||||
# ==============================================================================
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
BLUE='\033[0;34m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
TARGET_SCRIPT="${1:-aufgabe.sh}"
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
echo -e "${BLUE}🧪 TESTSUITE: DNS 10: SPF Sender Policy Framework (TXT)${NC}"
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
TOTAL_TESTS=0
|
||||
PASSED_TESTS=0
|
||||
FAILED_TESTS=0
|
||||
|
||||
TEST_DIR=$(mktemp -d /tmp/dns_test_XXXXXX)
|
||||
trap 'rm -rf "$TEST_DIR"' EXIT
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
echo -e "${BLUE}🧪 TESTSUITE: DNS 10: SPF Sender Policy Framework${NC}"
|
||||
echo -e "${BLUE} Ziel-Skript: ${YELLOW}${TARGET_SCRIPT}${NC}"
|
||||
echo -e "${BLUE}================================================================${NC}\n"
|
||||
|
||||
if [ ! -f "$SCRIPT_DIR/$TARGET_SCRIPT" ]; then
|
||||
echo -e "${RED}❌ Fehler: Datei '$TARGET_SCRIPT' nicht gefunden!${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
source "$SCRIPT_DIR/$TARGET_SCRIPT"
|
||||
|
||||
# Führe Funktion aus
|
||||
create_spf_record "example.com" "$TEST_DIR"
|
||||
RET=$?
|
||||
run_test() {
|
||||
local name="$1"
|
||||
local cmd="$2"
|
||||
((TOTAL_TESTS++))
|
||||
echo -e "▶ Test ${TOTAL_TESTS}: ${name}"
|
||||
if eval "$cmd"; then
|
||||
echo -e " ${GREEN}✓ BESTANDEN${NC}\n"
|
||||
((PASSED_TESTS++))
|
||||
else
|
||||
echo -e " ${RED}✗ FEHLGESCHLAGEN${NC}\n"
|
||||
((FAILED_TESTS++))
|
||||
fi
|
||||
}
|
||||
|
||||
if [ $RET -eq 0 ]; then
|
||||
echo -e "${GREEN}✓ Test 1: Funktion create_spf_record existiert und gibt 0 zurück.${NC}"
|
||||
echo -e "${GREEN}✓ Test 2: DNS Resource Records / Syntax validiert.${NC}"
|
||||
echo -e "${GREEN}✓ Test 3: Teilziele 1 bis 4 erfolgreich abgeschlossen.${NC}"
|
||||
echo -e "${GREEN}🎉 ERFOLG: Alle DNS-Prüfungen in DNS 10: SPF Sender Policy Framework (TXT) bestanden!${NC}"
|
||||
# TEST 1: SPF Record Builder
|
||||
run_test "build_spf_record erzeugt validen SPF-String" '
|
||||
OUT=$(build_spf_record "true" "192.0.2.1" "_spf.google.com" "-all")
|
||||
[ "$OUT" = "v=spf1 mx ip4:192.0.2.1 include:_spf.google.com -all" ]
|
||||
'
|
||||
|
||||
# TEST 2: Lookup Count im grünen Bereich (2 Lookups)
|
||||
run_test "count_spf_lookups meldet RFC7208_OK bei 2 Lookups" '
|
||||
OUT=$(count_spf_lookups "v=spf1 mx include:_spf.google.com -all")
|
||||
echo "$OUT" | grep -q "LOOKUP_COUNT=2" && \
|
||||
echo "$OUT" | grep -q "RFC7208_OK"
|
||||
'
|
||||
|
||||
# TEST 3: Lookup Count Limit überschritten (>10)
|
||||
run_test "count_spf_lookups schlägt Alarm bei 11 Lookups" '
|
||||
SPF="v=spf1 mx a include:1.de include:2.de include:3.de include:4.de include:5.de include:6.de include:7.de include:8.de include:9.de -all"
|
||||
OUT=$(count_spf_lookups "$SPF")
|
||||
echo "$OUT" | grep -q "LOOKUP_LIMIT_EXCEEDED"
|
||||
'
|
||||
|
||||
# TEST 4: DANGEROUS +all Abweisung
|
||||
run_test "audit_spf_qualifier warnt vor gefährlichem +all" '
|
||||
audit_spf_qualifier "v=spf1 mx +all" | grep -q "DANGEROUS: +all"
|
||||
'
|
||||
|
||||
# TEST 5: SECURE -all Bestätigung
|
||||
run_test "audit_spf_qualifier bestätigt striktes -all" '
|
||||
audit_spf_qualifier "v=spf1 mx -all" | grep -q "SECURE: -all"
|
||||
'
|
||||
|
||||
# TEST 6: BIND TXT Record Formatierung
|
||||
run_test "format_spf_txt_record erzeugt valides TXT-Record Format" '
|
||||
OUT=$(format_spf_txt_record "@" "v=spf1 mx -all" 3600)
|
||||
echo "$OUT" | grep -q "@" && \
|
||||
echo "$OUT" | grep -q "TXT" && \
|
||||
echo "$OUT" | grep -F -q "\"v=spf1 mx -all\""
|
||||
'
|
||||
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
if [ "$FAILED_TESTS" -eq 0 ]; then
|
||||
echo -e "${GREEN}🎉 ERFOLG: Alle ${PASSED_TESTS}/${TOTAL_TESTS} Tests in DNS 10 erfolgreich bestanden!${NC}"
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
exit 0
|
||||
else
|
||||
echo -e "${RED}❌ FEHLER: Skript fehlgeschlagen mit Status $RET${NC}"
|
||||
echo -e "${RED}❌ FEHLER: ${FAILED_TESTS} von ${TOTAL_TESTS} Tests fehlgeschlagen.${NC}"
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -1,18 +1,42 @@
|
|||
# DNS 11: DKIM Kryptografische E-Mail-Signatur
|
||||
# DNS 11: DKIM (DomainKeys Identified Mail) & Kryptografische Signaturen
|
||||
|
||||
Willkommen zum Kurs **DNS-Records, Domain Name System & E-Mail-Sicherheit** im IT-Praxisportal!
|
||||
Willkommen zu **Modul 11** des Kurses **DNS-Records, Domain Name System & E-Mail-Sicherheit**!
|
||||
|
||||
## 💡 Das Wichtigste in Kürze
|
||||
- **Ziel:** Public Key im DNS: Selektoren (s1._domainkey.domain.de), RSA/Ed25519 Schlüssel und Header-Validierung.
|
||||
- **Wichtigstes Tool:** `opendkim-testkey`
|
||||
In diesem Modul lernst du den zweiten Pfeiler moderner Mail-Security kennen: **DKIM (RFC 6376)**. Du erfährst, wie asymmetrische Kryptografie (RSA/Ed25519) die Unveränderbarkeit von E-Mails sicherstellt, wie der **DKIM-Selector** im DNS adressiert wird und wie das **255-Zeichen-String-Limit** bei langen 2048-Bit Schlüsseln gelöst wird.
|
||||
|
||||
## 🎯 Deine Aufgaben (Checkliste)
|
||||
1. **Teilziel 1:** DKIM-Schlüsselpaar (Private/Public RSA 2048 Bit) generieren
|
||||
2. **Teilziel 2:** DNS TXT-Record unter dem Selektor s1._domainkey eintragen
|
||||
3. **Teilziel 3:** Tags v=DKIM1; k=rsa; p=PublicKey im Record formatieren
|
||||
4. **Teilziel 4:** DKIM-Signaturprüfung simulieren
|
||||
---
|
||||
|
||||
## 💡 1. Das Wichtigste in Kürze
|
||||
|
||||
### Wie DKIM funktioniert
|
||||
1. Der versendende Mail-Server signiert Body und wichtige Header mit seinem privaten Schlüssel (`Private Key`).
|
||||
2. Die Signatur wird als Mail-Header `DKIM-Signature:` in die E-Mail eingebettet.
|
||||
3. Der empfangende Server liest den Selector `s=` aus und schlägt den öffentlichen Schlüssel im DNS nach:
|
||||
`<selector>._domainkey.<domain>.`
|
||||
4. Stimmt die mathematische Signaturprüfung, ist bewiesen: **Die E-Mail wurde nach dem Absenden nicht manipuliert!**
|
||||
|
||||
### Der BIND 255-Byte Split
|
||||
Ein 2048-Bit RSA-Schlüssel ist knapp 400 Zeichen lang. Ein einzelnes String-Literal in DNS darf maximal 255 Zeichen lang sein. BIND erlaubt die Aufteilung in zwei Strings in runden Klammern:
|
||||
```dns
|
||||
s2026._domainkey.firma.de. IN TXT (
|
||||
"v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA123..."
|
||||
"...RestDesSchluesselsHierFortgesetzt" )
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Deine Aufgaben in `aufgabe.sh`
|
||||
|
||||
1. **TODO 1 (`build_dkim_fqdn`):** Erzeuge den standardkonformen DKIM-DNS-Namen (`<selector>._domainkey.<domain>.`).
|
||||
2. **TODO 2 (`audit_dkim_key_security`):** Prüfe die Schlüssellänge und warne vor unsicheren 1024-Bit RSA-Keys.
|
||||
3. **TODO 3 (`format_dkim_record`):** Formatiere den TXT-Record mit 255-Zeichen-Splitting für BIND.
|
||||
4. **TODO 4 (`parse_dkim_record`):** Extrahiere Version, Algorithmus und Public Key aus einem DKIM-String.
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Tests ausführen
|
||||
|
||||
Führe im Terminal folgenden Befehl aus:
|
||||
```bash
|
||||
bash test_aufgabe.sh
|
||||
```
|
||||
|
|
|
|||
|
|
@ -1,21 +1,102 @@
|
|||
#!/usr/bin/env bash
|
||||
# ==============================================================================
|
||||
# 🛡️ DKIM Signatur - DNS 11: DKIM Kryptografische E-Mail-Signatur
|
||||
# 🌐 DNS 11: DKIM (DOMAINKEYS IDENTIFIED MAIL) & KRYPTOGRAFISCHE SIGNATUR
|
||||
# ==============================================================================
|
||||
#
|
||||
# WAS IST DKIM (RFC 6376)?
|
||||
# Während SPF nur prüft, WELCHER SERVER sendet, garantiert DKIM mit asymmetrischer
|
||||
# Kryptografie (RSA oder Ed25519), dass die E-Mail auf dem Transportweg NICHT
|
||||
# verändert oder manipuliert wurde (Integrität & Authentizität).
|
||||
#
|
||||
# WIE FUNKTIONIERT DKIM?
|
||||
# 1. Der sendende Mailserver besitzt einen privaten Schlüssel (Private Key).
|
||||
# Er berechnet einen Hash der Mail und signiert ihn im Header:
|
||||
# "DKIM-Signature: v=1; a=rsa-sha256; d=firma.de; s=s2026; ..."
|
||||
# 2. Der empfangende Server fragt den öffentlichen Schlüssel (Public Key) im DNS ab.
|
||||
#
|
||||
# DER DKIM-SELECTOR:
|
||||
# Um mehrere Schlüssel für verschiedene Standorte oder Anbieter verwalten zu können,
|
||||
# nutzt DKIM einen "Selector".
|
||||
# DNS-Speicherort: <selector>._domainkey.<domain>.
|
||||
# Beispiel: s2026._domainkey.it-praxisportal.de.
|
||||
#
|
||||
# DIE 255-ZEICHEN-STRING-GRENZE (RFC 1035):
|
||||
# Ein 2048-Bit RSA-Public-Key ist ca. 392 Zeichen lang.
|
||||
# Ein einzelnes String-Literal in einem DNS-TXT-Record darf aber maximal 255 Bytes haben!
|
||||
# In BIND löst man dies durch Klammern mit zwei aneinanderhängenden Strings:
|
||||
# s2026._domainkey IN TXT ( "v=DKIM1; k=rsa; p=Teil1..." "Teil2..." )
|
||||
# ==============================================================================
|
||||
|
||||
# 🎯 TEILZIEL 1: DKIM-Schlüsselpaar (Private/Public RSA 2048 Bit) generieren
|
||||
# 🎯 TEILZIEL 2: DNS TXT-Record unter dem Selektor s1._domainkey eintragen
|
||||
# 🎯 TEILZIEL 3: Tags v=DKIM1; k=rsa; p=PublicKey im Record formatieren
|
||||
# 🎯 TEILZIEL 4: DKIM-Signaturprüfung simulieren
|
||||
# 🎯 TEILZIEL 1 (TODO 1): DKIM-FQDN aus Selector und Domain bilden
|
||||
# Funktion: build_dkim_fqdn "$selector" "$domain"
|
||||
# Parameter $1: Selector (z.B. "s1" oder "mail2026")
|
||||
# Parameter $2: Domänenname (z.B. "it-praxisportal.de")
|
||||
# Anforderungen:
|
||||
# - Setze den Namen zusammen: <selector>._domainkey.<domain>.
|
||||
# - Stelle sicher, dass am Ende ein Punkt "." steht.
|
||||
# Ausgabe: "s1._domainkey.it-praxisportal.de."
|
||||
build_dkim_fqdn() {
|
||||
local sel="$1"
|
||||
local dom="$2"
|
||||
# TODO 1: Baue den FQDN
|
||||
return 0
|
||||
}
|
||||
|
||||
publish_dkim_key() {
|
||||
local domain="${1:-it-praxisportal.de}"
|
||||
echo "▶ Analysiere DNS-Konfiguration für $domain..."
|
||||
# 🎯 TEILZIEL 2 (TODO 2): Schlüssellänge validieren (1024 vs. 2048 Bit)
|
||||
# Funktion: audit_dkim_key_security "$pubkey_base64"
|
||||
# Parameter $1: Der Base64-kodierte Public Key (p= Wert)
|
||||
# Hintergrund:
|
||||
# 1024-Bit RSA gilt heute als kryptografisch unsicher und wird von Google/Yahoo abgewiesen!
|
||||
# Mindestanforderung: 2048 Bit (ca. 392 Base64-Zeichen).
|
||||
# Anforderungen:
|
||||
# - Berechne die Zeichenlänge des Schlüssels (${#pubkey_base64}).
|
||||
# - Wenn Länge < 300 (entspricht ~1024 Bit):
|
||||
# Gib aus: "INSECURE: 1024-Bit RSA Key (<laenge> Zeichen) ist veraltet! Mindestens 2048 Bit erforderlich."
|
||||
# return 1
|
||||
# - Wenn Länge >= 300:
|
||||
# Gib aus: "SECURE: 2048-Bit RSA Key (<laenge> Zeichen) erfüllt moderne Sicherheitsstandards."
|
||||
# return 0
|
||||
audit_dkim_key_security() {
|
||||
local key="$1"
|
||||
# TODO 2: Validiere Schlüssellänge
|
||||
return 0
|
||||
}
|
||||
|
||||
# TODO: Implementiere die geforderten DNS-Prüfungen oder Record-Erstellungen
|
||||
return 1
|
||||
# 🎯 TEILZIEL 3 (TODO 3): BIND TXT Record mit 255-Zeichen-Splitting formatieren
|
||||
# Funktion: format_dkim_record "$selector" "$domain" "$pubkey_base64" "$ttl"
|
||||
# Parameter: Selector, Domain, Public Key, TTL (Standard: 86400)
|
||||
# Anforderungen:
|
||||
# - Bildet den FQDN via build_dkim_fqdn.
|
||||
# - Wenn der vollständige Text "v=DKIM1; k=rsa; p=<key>" länger als 250 Zeichen ist:
|
||||
# Teile den String in zwei Blöcke auf und formatiere als BIND Multi-String:
|
||||
# <dkim_fqdn> <ttl> IN TXT ( "<block1>" "<block2>" )
|
||||
# - Andernfalls formatiere einzeilig:
|
||||
# <dkim_fqdn> <ttl> IN TXT "v=DKIM1; k=rsa; p=<key>"
|
||||
format_dkim_record() {
|
||||
local sel="$1"
|
||||
local dom="$2"
|
||||
local key="$3"
|
||||
local ttl="${4:-86400}"
|
||||
# TODO 3: Formatiere den DKIM-Record
|
||||
return 0
|
||||
}
|
||||
|
||||
# 🎯 TEILZIEL 4 (TODO 4): DKIM-Tags parsen
|
||||
# Funktion: parse_dkim_record "$dkim_txt_string"
|
||||
# Parameter $1: Inhalt des TXT-Records (z.B. 'v=DKIM1; k=rsa; p=MIIBIjAN...')
|
||||
# Anforderungen:
|
||||
# - Extrahiere die Tags 'v', 'k' und 'p'.
|
||||
# - Gib formatiert aus:
|
||||
# VERSION=<v>
|
||||
# KEY_TYPE=<k>
|
||||
# KEY_PREVIEW=<erste_15_zeichen_von_p>...
|
||||
parse_dkim_record() {
|
||||
local raw="$1"
|
||||
# TODO 4: Parse die DKIM-Tags
|
||||
return 0
|
||||
}
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
publish_dkim_key "$@"
|
||||
echo "=== Test Teilziel 1 ==="
|
||||
build_dkim_fqdn "s2026" "it-praxisportal.de"
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
<link rel="icon" type="image/svg+xml" href="../../../assets/favicon.svg">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>DNS 11: DKIM Kryptografische E-Mail-Signatur</title>
|
||||
<title>DNS 11: DKIM E-Mail-Signatur</title>
|
||||
<link rel="stylesheet" href="../../../assets/style.css">
|
||||
</head>
|
||||
<body>
|
||||
|
|
@ -12,46 +12,90 @@
|
|||
<div class="app-main">
|
||||
<header>
|
||||
<div class="header-container">
|
||||
<div class="header-badge">🛡️ DKIM Signatur</div>
|
||||
<h1>DNS 11: DKIM Kryptografische E-Mail-Signatur</h1>
|
||||
<p>Public Key im DNS: Selektoren (s1._domainkey.domain.de), RSA/Ed25519 Schlüssel und Header-Validierung.</p>
|
||||
<div class="header-badge">🛡️ E-Mail-Sicherheit</div>
|
||||
<h1>DNS 11: DKIM & Kryptografische E-Mail-Signaturen</h1>
|
||||
<p>Verstehe digitale E-Mail-Signaturen: Der DKIM-Selector, Public-Key-Verteilung im DNS und die Behebung des 255-Byte-TXT-Limits bei 2048-Bit RSA-Schlüsseln.</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="container">
|
||||
<!-- 1. THEORIE: DKIM ABLAUF -->
|
||||
<section class="card">
|
||||
<h2>💡 Theorie & Praxiskonzept</h2>
|
||||
<p>Public Key im DNS: Selektoren (s1._domainkey.domain.de), RSA/Ed25519 Schlüssel und Header-Validierung.</p>
|
||||
<div class="code-container">
|
||||
<div class="code-header">
|
||||
<span>DNS Command & Syntax Reference</span>
|
||||
<span>Tool: opendkim-testkey</span>
|
||||
</div>
|
||||
<pre><code># Wichtige Befehle & Zonensyntax:
|
||||
dig opendkim-testkey
|
||||
named-checkzone example.com /etc/bind/zones/db.example.com</code></pre>
|
||||
<h2>🔐 1. Funktionsweise von DKIM (RFC 6376)</h2>
|
||||
<p>DKIM schützt vor Man-in-the-Middle Manipulationen. Der Public Key wird unter der Subdomain <code>._domainkey</code> publiziert:</p>
|
||||
|
||||
<pre><code># Abfrage eines DKIM-Records mit dig (Selector 'google'):
|
||||
dig TXT google._domainkey.gmail.com +short
|
||||
|
||||
# Ausgabe:
|
||||
# "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA..."</code></pre>
|
||||
</section>
|
||||
|
||||
<!-- 2. THEORIE: SCHLÜSSELLÄNGE -->
|
||||
<section class="card">
|
||||
<h2>🛡️ 2. Warum 1024-Bit Keys verboten gehören</h2>
|
||||
<div class="box box-warnung">
|
||||
<div class="box-title">Security Alert: 1024-Bit RSA gebrochen</div>
|
||||
<p>1024-Bit RSA-Schlüssel können heute mit Cloud-Rechenleistung in wenigen Tagen faktorisiert werden! Google und Yahoo verlangen seit 2024 zwingend <strong>mindestens 2048 Bit</strong>. Mails mit 1024-Bit Schlüsseln werden zunehmend gebounced.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 3. CHEAT-SHEET -->
|
||||
<section class="card">
|
||||
<h2>🎯 Aufgaben & Checkliste</h2>
|
||||
<h2>📑 3. Cheat-Sheet: DKIM-Tags</h2>
|
||||
<div style="overflow-x: auto;">
|
||||
<table style="width: 100%; border-collapse: collapse; font-size: 0.9rem; text-align: left;">
|
||||
<thead>
|
||||
<tr style="border-bottom: 2px solid var(--border-color); background: var(--bg-subtle);">
|
||||
<th style="padding: 10px;">Tag</th>
|
||||
<th style="padding: 10px;">Bedeutung</th>
|
||||
<th style="padding: 10px;">Standard / Beispiel</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr style="border-bottom: 1px solid var(--border-color);">
|
||||
<td style="padding: 10px;"><code>v=</code></td>
|
||||
<td style="padding: 10px;">DKIM-Protokollversion</td>
|
||||
<td style="padding: 10px;"><code>v=DKIM1</code></td>
|
||||
</tr>
|
||||
<tr style="border-bottom: 1px solid var(--border-color);">
|
||||
<td style="padding: 10px;"><code>k=</code></td>
|
||||
<td style="padding: 10px;">Schlüssel-Algorithmus</td>
|
||||
<td style="padding: 10px;"><code>k=rsa</code> oder <code>k=ed25519</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 10px;"><code>p=</code></td>
|
||||
<td style="padding: 10px;">Base64 Public Key</td>
|
||||
<td style="padding: 10px;"><code>p=MIIBIjANBgkqhki...</code> (mind. 2048 Bit)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 4. AUFGABEN CHECKLISTE -->
|
||||
<section class="card">
|
||||
<h2>🎯 Aufgaben & Checkliste in <code>aufgabe.sh</code></h2>
|
||||
<div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t1">
|
||||
<label class="task-label" for="t1"><strong>TODO 1:</strong> DKIM-Schlüsselpaar (Private/Public RSA 2048 Bit) generieren</label>
|
||||
</div><div class="task-item">
|
||||
<label class="task-label" for="t1"><strong>TODO 1:</strong> <code>build_dkim_fqdn(sel, dom)</code>: Selector-Domain konstruieren.</label>
|
||||
</div>
|
||||
<div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t2">
|
||||
<label class="task-label" for="t2"><strong>TODO 2:</strong> DNS TXT-Record unter dem Selektor s1._domainkey eintragen</label>
|
||||
</div><div class="task-item">
|
||||
<label class="task-label" for="t2"><strong>TODO 2:</strong> <code>audit_dkim_key_security(key)</code>: 2048-Bit Schlüssellänge prüfen.</label>
|
||||
</div>
|
||||
<div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t3">
|
||||
<label class="task-label" for="t3"><strong>TODO 3:</strong> Tags v=DKIM1; k=rsa; p=PublicKey im Record formatieren</label>
|
||||
</div><div class="task-item">
|
||||
<label class="task-label" for="t3"><strong>TODO 3:</strong> <code>format_dkim_record(...)</code>: 255-Zeichen-Splitting für BIND umsetzen.</label>
|
||||
</div>
|
||||
<div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t4">
|
||||
<label class="task-label" for="t4"><strong>TODO 4:</strong> DKIM-Signaturprüfung simulieren</label>
|
||||
<label class="task-label" for="t4"><strong>TODO 4:</strong> <code>parse_dkim_record(raw)</code>: Tags und Public Key extrahieren.</label>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 25px; display: flex; gap: 12px; flex-wrap: wrap;">
|
||||
<a href="../../../workspace.html?course=dns_records&track=track_3_email_security&chapter=11_dkim_domainkeys_identified_mail" class="btn" style="background: #0284c7; color: #fff; font-weight: 700;">💻 In Web-IDE öffnen →</a>
|
||||
<a href="../12_dmarc_und_email_security_suite/index.html" class="btn btn-secondary">Weiter zu Modul →</a>
|
||||
<a href="../../../workspace.html?course=dns_records&track=track_3_email_security&chapter=11_dkim_domainkeys_identified_mail" class="btn" style="background: #1d4ed8; color: #fff; font-weight: 700;">In Web-IDE öffnen →</a>
|
||||
<a href="../12_dmarc_und_email_security_suite/index.html" class="btn btn-secondary">Weiter zu Modul 12 →</a>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
|
|
|||
|
|
@ -1,97 +1,73 @@
|
|||
#!/usr/bin/env bash
|
||||
# ==============================================================================
|
||||
# MUSTERLÖSUNG: DNS 11: DKIM Kryptografische E-Mail-Signatur
|
||||
# MUSTERLÖSUNG: DNS 11: DKIM & KRYPTOGRAFISCHE E-MAIL-SIGNATUR
|
||||
# ==============================================================================
|
||||
|
||||
publish_dkim_key() {
|
||||
local domain="${1:-it-praxisportal.de}"
|
||||
local target_dir="${2:-.}"
|
||||
build_dkim_fqdn() {
|
||||
local sel="$1"
|
||||
local dom="$2"
|
||||
[ -z "$sel" ] || [ -z "$dom" ] && return 1
|
||||
|
||||
echo "▶ Starte professionelle DNS-Operation für $domain..."
|
||||
|
||||
case "11_dkim_domainkeys_identified_mail" in
|
||||
*01_dns_hierarchie*)
|
||||
echo "Host: www | SLD: it-praxisportal | TLD: de | Root: ."
|
||||
;;
|
||||
*02_rekursive*)
|
||||
echo "Simulation Iteration: Root (.) -> de. -> ns1.it-praxisportal.de -> 188.245.100.5"
|
||||
;;
|
||||
*03_ports_udp*)
|
||||
echo "Protocol: UDP/53 (Standard) | Fallback: TCP/53 | EDNS0 Buffer: 4096 bytes"
|
||||
;;
|
||||
*04_ttl*)
|
||||
echo "TTL Current: 86400s | Migration Planned TTL: 300s | Neg Cache: 3600s"
|
||||
;;
|
||||
*05_a_und_aaaa*)
|
||||
cat << 'ZONE' > "$target_dir/zone_records.txt"
|
||||
@ IN A 188.245.100.5
|
||||
@ IN AAAA 2a01:4f8:c010:d::1
|
||||
www IN A 188.245.100.5
|
||||
ZONE
|
||||
;;
|
||||
*06_cname*)
|
||||
echo "api IN CNAME app.it-praxisportal.de." > "$target_dir/cname_record.txt"
|
||||
;;
|
||||
*07_soa*)
|
||||
cat << 'ZONE' > "$target_dir/soa_record.txt"
|
||||
@ IN SOA ns1.it-praxisportal.de. hostmaster.it-praxisportal.de. (
|
||||
2026091201 ; Serial YYYYMMDDNN
|
||||
7200 ; Refresh (2h)
|
||||
3600 ; Retry (1h)
|
||||
1209600 ; Expire (2w)
|
||||
3600 ; Negative Cache TTL (1h)
|
||||
)
|
||||
ZONE
|
||||
;;
|
||||
*08_ns_und_ptr*)
|
||||
echo "100.245.188.in-addr.arpa. IN PTR mail.it-praxisportal.de." > "$target_dir/ptr_record.txt"
|
||||
;;
|
||||
*09_mx*)
|
||||
echo "@ IN MX 10 mail.it-praxisportal.de." > "$target_dir/mx_record.txt"
|
||||
echo "@ IN MX 20 backup-mail.it-praxisportal.de." >> "$target_dir/mx_record.txt"
|
||||
;;
|
||||
*10_txt_records*)
|
||||
echo '@ IN TXT "v=spf1 mx ip4:188.245.100.5 include:_spf.google.com -all"' > "$target_dir/spf_record.txt"
|
||||
;;
|
||||
*11_dkim*)
|
||||
echo 's1._domainkey IN TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz..."' > "$target_dir/dkim_record.txt"
|
||||
;;
|
||||
*12_dmarc*)
|
||||
echo '_dmarc IN TXT "v=DMARC1; p=reject; rua=mailto:dmarc-reports@it-praxisportal.de; pct=100; aspf=s"' > "$target_dir/dmarc_record.txt"
|
||||
;;
|
||||
*13_srv*)
|
||||
echo '_ldap._tcp.dc._msdcs.corp IN SRV 0 100 389 dc01.corp.it-praxisportal.de.' > "$target_dir/srv_record.txt"
|
||||
;;
|
||||
*14_caa*)
|
||||
echo '@ IN CAA 0 issue "letsencrypt.org"' > "$target_dir/caa_record.txt"
|
||||
echo '@ IN CAA 0 iodef "mailto:security@it-praxisportal.de"' >> "$target_dir/caa_record.txt"
|
||||
;;
|
||||
*15_dns_troubleshooting*)
|
||||
echo "STATUS: NOERROR | ANSWER: 1 | FLAGS: qr aa rd ra"
|
||||
;;
|
||||
*16_master*)
|
||||
cat << 'REPORT' > "$target_dir/dns_audit_report.json"
|
||||
{
|
||||
"domain": "it-praxisportal.de",
|
||||
"audit_status": "PASSED",
|
||||
"score": 100,
|
||||
"soa_serial_valid": true,
|
||||
"ns_redundancy": 2,
|
||||
"email_security": {
|
||||
"spf": "valid (-all)",
|
||||
"dkim": "present",
|
||||
"dmarc": "enforced (p=reject)"
|
||||
},
|
||||
"dnssec": "active"
|
||||
dom="${dom%.}"
|
||||
echo "${sel}._domainkey.${dom}."
|
||||
return 0
|
||||
}
|
||||
REPORT
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "✅ DNS-Operation erfolgreich abgeschlossen."
|
||||
audit_dkim_key_security() {
|
||||
local key="$1"
|
||||
local len=${#key}
|
||||
|
||||
if [ "$len" -lt 300 ]; then
|
||||
echo "INSECURE: 1024-Bit RSA Key ($len Zeichen) ist veraltet! Mindestens 2048 Bit erforderlich."
|
||||
return 1
|
||||
else
|
||||
echo "SECURE: 2048-Bit RSA Key ($len Zeichen) erfüllt moderne Sicherheitsstandards."
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
format_dkim_record() {
|
||||
local sel="$1"
|
||||
local dom="$2"
|
||||
local key="$3"
|
||||
local ttl="${4:-86400}"
|
||||
|
||||
local fqdn=$(build_dkim_fqdn "$sel" "$dom")
|
||||
local payload="v=DKIM1; k=rsa; p=${key}"
|
||||
|
||||
if [ ${#payload} -gt 250 ]; then
|
||||
local chunk1="${payload:0:200}"
|
||||
local chunk2="${payload:200}"
|
||||
printf "%-30s %-7s IN TXT ( \"%s\" \"%s\" )\n" "$fqdn" "$ttl" "$chunk1" "$chunk2"
|
||||
else
|
||||
printf "%-30s %-7s IN TXT \"%s\"\n" "$fqdn" "$ttl" "$payload"
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
parse_dkim_record() {
|
||||
local raw="$1"
|
||||
local clean=$(echo "$raw" | tr -d '()"' | tr ';' '\n')
|
||||
|
||||
local v="DKIM1"
|
||||
local k="rsa"
|
||||
local p=""
|
||||
|
||||
while IFS='=' read -r tag val; do
|
||||
tag=$(echo "$tag" | tr -d '[:space:]')
|
||||
val=$(echo "$val" | tr -d '[:space:]')
|
||||
[ "$tag" = "v" ] && v="$val"
|
||||
[ "$tag" = "k" ] && k="$val"
|
||||
[ "$tag" = "p" ] && p="$val"
|
||||
done <<< "$clean"
|
||||
|
||||
local preview="${p:0:15}..."
|
||||
echo "VERSION=$v"
|
||||
echo "KEY_TYPE=$k"
|
||||
echo "KEY_PREVIEW=$preview"
|
||||
return 0
|
||||
}
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
publish_dkim_key "$@"
|
||||
build_dkim_fqdn "$@"
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -1,36 +1,89 @@
|
|||
#!/usr/bin/env bash
|
||||
# ==============================================================================
|
||||
# TESTSUITE: DNS 11: DKIM Kryptografische E-Mail-Signatur
|
||||
# 🧪 TESTSUITE: DNS 11 - DKIM SIGNATUREN & SCHLÜSSEL
|
||||
# ==============================================================================
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
BLUE='\033[0;34m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
TARGET_SCRIPT="${1:-aufgabe.sh}"
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
echo -e "${BLUE}🧪 TESTSUITE: DNS 11: DKIM Kryptografische E-Mail-Signatur${NC}"
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
TOTAL_TESTS=0
|
||||
PASSED_TESTS=0
|
||||
FAILED_TESTS=0
|
||||
|
||||
TEST_DIR=$(mktemp -d /tmp/dns_test_XXXXXX)
|
||||
trap 'rm -rf "$TEST_DIR"' EXIT
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
echo -e "${BLUE}🧪 TESTSUITE: DNS 11: DKIM E-Mail-Signatur${NC}"
|
||||
echo -e "${BLUE} Ziel-Skript: ${YELLOW}${TARGET_SCRIPT}${NC}"
|
||||
echo -e "${BLUE}================================================================${NC}\n"
|
||||
|
||||
if [ ! -f "$SCRIPT_DIR/$TARGET_SCRIPT" ]; then
|
||||
echo -e "${RED}❌ Fehler: Datei '$TARGET_SCRIPT' nicht gefunden!${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
source "$SCRIPT_DIR/$TARGET_SCRIPT"
|
||||
|
||||
# Führe Funktion aus
|
||||
publish_dkim_key "example.com" "$TEST_DIR"
|
||||
RET=$?
|
||||
run_test() {
|
||||
local name="$1"
|
||||
local cmd="$2"
|
||||
((TOTAL_TESTS++))
|
||||
echo -e "▶ Test ${TOTAL_TESTS}: ${name}"
|
||||
if eval "$cmd"; then
|
||||
echo -e " ${GREEN}✓ BESTANDEN${NC}\n"
|
||||
((PASSED_TESTS++))
|
||||
else
|
||||
echo -e " ${RED}✗ FEHLGESCHLAGEN${NC}\n"
|
||||
((FAILED_TESTS++))
|
||||
fi
|
||||
}
|
||||
|
||||
if [ $RET -eq 0 ]; then
|
||||
echo -e "${GREEN}✓ Test 1: Funktion publish_dkim_key existiert und gibt 0 zurück.${NC}"
|
||||
echo -e "${GREEN}✓ Test 2: DNS Resource Records / Syntax validiert.${NC}"
|
||||
echo -e "${GREEN}✓ Test 3: Teilziele 1 bis 4 erfolgreich abgeschlossen.${NC}"
|
||||
echo -e "${GREEN}🎉 ERFOLG: Alle DNS-Prüfungen in DNS 11: DKIM Kryptografische E-Mail-Signatur bestanden!${NC}"
|
||||
# TEST 1: DKIM FQDN Erzeugung mit Selector
|
||||
run_test "build_dkim_fqdn erzeugt s1._domainkey.firma.de." '
|
||||
OUT=$(build_dkim_fqdn "s1" "firma.de")
|
||||
[ "$OUT" = "s1._domainkey.firma.de." ]
|
||||
'
|
||||
|
||||
# TEST 2: Unsicheren 1024-Bit Schlüssel erkennen
|
||||
run_test "audit_dkim_key_security warnt vor 1024-Bit RSA Key (<300 Zeichen)" '
|
||||
SHORT_KEY="MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC3QVRXA1rNu7eBwI+2fW=="
|
||||
audit_dkim_key_security "$SHORT_KEY" | grep -q "INSECURE: 1024-Bit"
|
||||
'
|
||||
|
||||
# TEST 3: Sicheren 2048-Bit Schlüssel bestätigen
|
||||
run_test "audit_dkim_key_security bestätigt 2048-Bit RSA Key (>=300 Zeichen)" '
|
||||
LONG_KEY=$(head -c 392 < /dev/zero | tr "\0" "A")
|
||||
audit_dkim_key_security "$LONG_KEY" | grep -q "SECURE: 2048-Bit"
|
||||
'
|
||||
|
||||
# TEST 4: BIND TXT Splitting bei >250 Zeichen
|
||||
run_test "format_dkim_record splittet lange Keys in BIND Multi-String ( ... )" '
|
||||
LONG_KEY=$(head -c 350 < /dev/zero | tr "\0" "X")
|
||||
OUT=$(format_dkim_record "s2026" "it-praxisportal.de" "$LONG_KEY" 86400)
|
||||
echo "$OUT" | grep -q "s2026._domainkey.it-praxisportal.de." && \
|
||||
echo "$OUT" | grep -q "TXT ("
|
||||
'
|
||||
|
||||
# TEST 5: DKIM Tags parsen
|
||||
run_test "parse_dkim_record extrahiert v, k und Key-Preview" '
|
||||
SAMPLE="v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA012345"
|
||||
OUT=$(parse_dkim_record "$SAMPLE")
|
||||
echo "$OUT" | grep -q "VERSION=DKIM1" && \
|
||||
echo "$OUT" | grep -q "KEY_TYPE=rsa" && \
|
||||
echo "$OUT" | grep -q "KEY_PREVIEW=MIIBIjANBgkqhki..."
|
||||
'
|
||||
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
if [ "$FAILED_TESTS" -eq 0 ]; then
|
||||
echo -e "${GREEN}🎉 ERFOLG: Alle ${PASSED_TESTS}/${TOTAL_TESTS} Tests in DNS 11 erfolgreich bestanden!${NC}"
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
exit 0
|
||||
else
|
||||
echo -e "${RED}❌ FEHLER: Skript fehlgeschlagen mit Status $RET${NC}"
|
||||
echo -e "${RED}❌ FEHLER: ${FAILED_TESTS} von ${TOTAL_TESTS} Tests fehlgeschlagen.${NC}"
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -1,18 +1,37 @@
|
|||
# DNS 12: DMARC Policy & Phishing-Abwehr
|
||||
|
||||
Willkommen zum Kurs **DNS-Records, Domain Name System & E-Mail-Sicherheit** im IT-Praxisportal!
|
||||
Willkommen zu **Modul 12** des Kurses **DNS-Records, Domain Name System & E-Mail-Sicherheit**!
|
||||
|
||||
## 💡 Das Wichtigste in Kürze
|
||||
- **Ziel:** Die Krone der Mail-Sicherheit: DMARC Policy (_dmarc.domain.de), SPF/DKIM Alignment und RUA/RUF Reporting.
|
||||
- **Wichtigstes Tool:** `checkdmarc`
|
||||
In diesem Modul komplettieren wir die E-Mail-Sicherheits-Trilogie: **DMARC (RFC 7489)** verbindet SPF und DKIM zu einer bindenden Durchsetzungsrichtlinie. Du lernst, wie du per `p=reject` Spoofing im Keim erstickst, aggregierte XML-Reports (`rua=`) auswertest und führst ein automatisches Gesamtaudit deiner Domain durch.
|
||||
|
||||
## 🎯 Deine Aufgaben (Checkliste)
|
||||
1. **Teilziel 1:** DMARC TXT-Record unter _dmarc.domain.de anlegen
|
||||
2. **Teilziel 2:** Policy-Stufen (p=none -> p=quarantine -> p=reject) staffeln
|
||||
3. **Teilziel 3:** Aggregierte Report-Adresse (rua=mailto:dmarc@domain.de) einrichten
|
||||
4. **Teilziel 4:** Strenges vs. relatives Alignment (aspf/adkim) festlegen
|
||||
---
|
||||
|
||||
## 💡 1. Das Wichtigste in Kürze
|
||||
|
||||
### Was DMARC leistet
|
||||
Ohne DMARC weiß ein empfangender Server nicht, was er tun soll, wenn SPF oder DKIM fehlschlagen. DMARC gibt dem Domain-Inhaber die Kontrolle zurück:
|
||||
```dns
|
||||
_dmarc.it-praxisportal.de. 3600 IN TXT "v=DMARC1; p=reject; rua=mailto:dmarc-reports@it-praxisportal.de; pct=100"
|
||||
```
|
||||
|
||||
### Die drei Policies (`p=`)
|
||||
1. **`p=none` (Monitoring):** Zum Start. Mails werden zugestellt, du erhältst aber tägliche Reports über unberechtigte Sender.
|
||||
2. **`p=quarantine` (Spam):** Mails ohne gültigen Nachweis landen im Spam-Ordner.
|
||||
3. **`p=reject` (Abweisung):** Die Königsdisziplin! Der empfangende Server bricht die Verbindung noch während des SMTP-Dialogs ab (`550 5.7.1 Unauthenticated mail rejected`).
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Deine Aufgaben in `aufgabe.sh`
|
||||
|
||||
1. **TODO 1 (`format_dmarc_record`):** Erstelle einen standardkonformen DMARC TXT-Record unter `_dmarc.<domain>.`.
|
||||
2. **TODO 2 (`evaluate_dmarc_verdict`):** Simuliere die Empfänger-Entscheidung (Pass, Deliver, Quarantine, Reject).
|
||||
3. **TODO 3 (`audit_email_security_suite`):** Führe ein 100-Punkte-Audit der Kombination aus SPF, DKIM und DMARC durch.
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Tests ausführen
|
||||
|
||||
Führe im Terminal folgenden Befehl aus:
|
||||
```bash
|
||||
bash test_aufgabe.sh
|
||||
```
|
||||
|
|
|
|||
|
|
@ -1,21 +1,108 @@
|
|||
#!/usr/bin/env bash
|
||||
# ==============================================================================
|
||||
# 🛡️ DMARC Policy - DNS 12: DMARC Policy & Phishing-Abwehr
|
||||
# 🌐 DNS 12: DMARC (RFC 7489) & DIE E-MAIL-SECURITY TRILOGIE
|
||||
# ==============================================================================
|
||||
#
|
||||
# WAS IST DMARC?
|
||||
# DMARC verbindet SPF und DKIM zu einem lückenlosen Schutzschild gegen Phishing,
|
||||
# CEO-Fraud und Brand-Impersonation.
|
||||
#
|
||||
# WO LIEGT DMARC IM DNS?
|
||||
# Immer zwingend unter der Subdomain: _dmarc.<domain>.
|
||||
# Beispiel: _dmarc.it-praxisportal.de.
|
||||
#
|
||||
# DIE DREI DMARC-POLICIES (p=):
|
||||
# 1. p=none : Reine Beobachtung (Monitoring). Mails werden normal zugestellt.
|
||||
# 2. p=quarantine : Mails, die SPF und DKIM verfehlen, landen im SPAM-Ordner.
|
||||
# 3. p=reject : Mails werden vom empfangenden Mail-Server sofort ABGEWIESEN!
|
||||
#
|
||||
# REPORTING (rua=):
|
||||
# Der Empfänger sendet tägliche XML-Reports an die angegebene E-Mail:
|
||||
# rua=mailto:dmarc-reports@firma.de
|
||||
# ==============================================================================
|
||||
|
||||
# 🎯 TEILZIEL 1: DMARC TXT-Record unter _dmarc.domain.de anlegen
|
||||
# 🎯 TEILZIEL 2: Policy-Stufen (p=none -> p=quarantine -> p=reject) staffeln
|
||||
# 🎯 TEILZIEL 3: Aggregierte Report-Adresse (rua=mailto:dmarc@domain.de) einrichten
|
||||
# 🎯 TEILZIEL 4: Strenges vs. relatives Alignment (aspf/adkim) festlegen
|
||||
# 🎯 TEILZIEL 1 (TODO 1): Syntaktisch validen DMARC-Record erzeugen
|
||||
# Funktion: format_dmarc_record "$domain" "$policy" "$rua_email" "$pct"
|
||||
# Parameter $1: Domäne (z.B. "it-praxisportal.de")
|
||||
# Parameter $2: Policy ("none", "quarantine", "reject")
|
||||
# Parameter $3: E-Mail für Reports (z.B. "dmarc@it-praxisportal.de")
|
||||
# Parameter $4: Prozentsatz der Anwendung (Standard: 100)
|
||||
# Anforderungen:
|
||||
# - DNS-Name muss "_dmarc.<domain>." sein.
|
||||
# - Formatiere als BIND TXT Record:
|
||||
# _dmarc.<domain>. 3600 IN TXT "v=DMARC1; p=<policy>; rua=mailto:<rua_email>; pct=<pct>"
|
||||
format_dmarc_record() {
|
||||
local domain="$1"
|
||||
local policy="$2"
|
||||
local rua="$3"
|
||||
local pct="${4:-100}"
|
||||
# TODO 1: Erzeuge den DMARC TXT Record
|
||||
return 0
|
||||
}
|
||||
|
||||
deploy_dmarc_policy() {
|
||||
local domain="${1:-it-praxisportal.de}"
|
||||
echo "▶ Analysiere DNS-Konfiguration für $domain..."
|
||||
# 🎯 TEILZIEL 2 (TODO 2): DMARC-Entscheidung (Verdict) simulieren
|
||||
# Funktion: evaluate_dmarc_verdict "$spf_pass" "$dkim_pass" "$policy"
|
||||
# Parameter $1: SPF Status ("true" oder "false")
|
||||
# Parameter $2: DKIM Status ("true" oder "false")
|
||||
# Parameter $3: DMARC Policy ("none", "quarantine", "reject")
|
||||
# DMARC-Logik:
|
||||
# - Wenn spf_pass == "true" ODER dkim_pass == "true":
|
||||
# DMARC gilt als BESTANDEN (Alignment vorausgesetzt).
|
||||
# Gib aus: "VERDICT_PASS: Zustellung im Posteingang (mindestens ein Mechanismus valide)."
|
||||
# return 0
|
||||
# - Wenn BEIDE "false" sind:
|
||||
# DMARC ist FEHLGESCHLAGEN. Die Policy greift:
|
||||
# * Wenn policy == "none":
|
||||
# Gib aus: "VERDICT_FAIL_DELIVER: Fehlgeschlagen, aber p=none -> Zustellung erfolgt dennoch."
|
||||
# return 0
|
||||
# * Wenn policy == "quarantine":
|
||||
# Gib aus: "VERDICT_QUARANTINE: Fehlgeschlagen -> Mail in SPAM-Ordner verschieben."
|
||||
# return 1
|
||||
# * Wenn policy == "reject":
|
||||
# Gib aus: "VERDICT_REJECT: Fehlgeschlagen -> Mail an SMTP-Pforte abweisen (550 DMARC Failed)!"
|
||||
# return 1
|
||||
evaluate_dmarc_verdict() {
|
||||
local spf="$1"
|
||||
local dkim="$2"
|
||||
local pol="$3"
|
||||
# TODO 2: Simuliere die Empfängerentscheidung
|
||||
return 0
|
||||
}
|
||||
|
||||
# TODO: Implementiere die geforderten DNS-Prüfungen oder Record-Erstellungen
|
||||
return 1
|
||||
# 🎯 TEILZIEL 3 (TODO 3): E-Mail-Security Trilogy Gesamt-Audit (Score 0-100)
|
||||
# Funktion: audit_email_security_suite "$spf_record" "$dkim_record" "$dmarc_record"
|
||||
# Parameter $1: SPF String (z.B. "v=spf1 mx -all")
|
||||
# Parameter $2: DKIM TXT String
|
||||
# Parameter $3: DMARC String (z.B. "v=DMARC1; p=reject; ...")
|
||||
# Punkte-Berechnung:
|
||||
# - SPF:
|
||||
# * mit "-all": +30 Punkte
|
||||
# * mit "~all": +15 Punkte
|
||||
# * sonst: 0 Punkte
|
||||
# - DKIM:
|
||||
# * Wenn "v=DKIM1" und Key vorhanden: +35 Punkte
|
||||
# - DMARC:
|
||||
# * Wenn policy "p=reject": +35 Punkte
|
||||
# * Wenn policy "p=quarantine": +25 Punkte
|
||||
# * Wenn policy "p=none": +10 Punkte
|
||||
# Ausgabe:
|
||||
# SCORE=<gesamtpunkte> | RATING=<GRADE>
|
||||
# Ratings:
|
||||
# 90-100: GRADE=EXCELLENT
|
||||
# 60-89: GRADE=ACCEPTABLE
|
||||
# 0-59: GRADE=INSECURE
|
||||
audit_email_security_suite() {
|
||||
local spf="$1"
|
||||
local dkim="$2"
|
||||
local dmarc="$3"
|
||||
# TODO 3: Führe das Gesamt-Audit durch
|
||||
return 0
|
||||
}
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
deploy_dmarc_policy "$@"
|
||||
echo "=== Test Teilziel 1 ==="
|
||||
format_dmarc_record "firma.de" "reject" "dmarc@firma.de" 100
|
||||
echo ""
|
||||
echo "=== Test Teilziel 2 ==="
|
||||
evaluate_dmarc_verdict "false" "false" "reject"
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
<link rel="icon" type="image/svg+xml" href="../../../assets/favicon.svg">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>DNS 12: DMARC Policy & Phishing-Abwehr</title>
|
||||
<title>DNS 12: DMARC Policy & Security</title>
|
||||
<link rel="stylesheet" href="../../../assets/style.css">
|
||||
</head>
|
||||
<body>
|
||||
|
|
@ -12,46 +12,94 @@
|
|||
<div class="app-main">
|
||||
<header>
|
||||
<div class="header-container">
|
||||
<div class="header-badge">🛡️ DMARC Policy</div>
|
||||
<h1>DNS 12: DMARC Policy & Phishing-Abwehr</h1>
|
||||
<p>Die Krone der Mail-Sicherheit: DMARC Policy (_dmarc.domain.de), SPF/DKIM Alignment und RUA/RUF Reporting.</p>
|
||||
<div class="header-badge">🛡️ E-Mail-Sicherheit</div>
|
||||
<h1>DNS 12: DMARC Policy & Phishing-Abwehr</h1>
|
||||
<p>Schließe die Sicherheitskette: Kopple SPF und DKIM mit DMARC, erzwinge die Ablehnung gefälschter Mails (p=reject) und analysiere Aggregated Reports.</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="container">
|
||||
<!-- 1. THEORIE: DMARC TRIALOGIE -->
|
||||
<section class="card">
|
||||
<h2>💡 Theorie & Praxiskonzept</h2>
|
||||
<p>Die Krone der Mail-Sicherheit: DMARC Policy (_dmarc.domain.de), SPF/DKIM Alignment und RUA/RUF Reporting.</p>
|
||||
<div class="code-container">
|
||||
<div class="code-header">
|
||||
<span>DNS Command & Syntax Reference</span>
|
||||
<span>Tool: checkdmarc</span>
|
||||
</div>
|
||||
<pre><code># Wichtige Befehle & Zonensyntax:
|
||||
dig checkdmarc
|
||||
named-checkzone example.com /etc/bind/zones/db.example.com</code></pre>
|
||||
<h2>🛡️ 1. Die E-Mail-Security Trilogie im Zusammenspiel</h2>
|
||||
<p>Erst wenn SPF, DKIM und DMARC Hand in Hand arbeiten, ist eine Domain vor CEO-Fraud und Identitätsdiebstahl geschützt:</p>
|
||||
|
||||
<pre><code>+-----------+ +------------+ +-------------------+
|
||||
| SPF | AND | DKIM | ==> | DMARC |
|
||||
| (IP-Auth) | | (Signatur) | | (Policy & Report) |
|
||||
+-----------+ +------------+ +-------------------+</code></pre>
|
||||
</section>
|
||||
|
||||
<!-- 2. THEORIE: POLICIES -->
|
||||
<section class="card">
|
||||
<h2>🎯 2. Der DMARC-Einführungsplan für Unternehmen</h2>
|
||||
<div class="box box-tipp">
|
||||
<div class="box-title">💡 Best-Practice Rollout in 3 Phasen</div>
|
||||
<ol style="margin-left: 20px; line-height: 1.6;">
|
||||
<li><strong>Phase 1 (Woche 1-4):</strong> <code>p=none; rua=mailto:...</code> → Nur Berichte sammeln und legitime Absender (CRM, Newsletter) in SPF aufnehmen.</li>
|
||||
<li><strong>Phase 2 (Woche 5-8):</strong> <code>p=quarantine; pct=50</code> → Schrittweise Mails ohne Authentifizierung in den Spam-Ordner leiten.</li>
|
||||
<li><strong>Phase 3 (Finale):</strong> <code>p=reject; pct=100</code> → Vollständige Abweisung gefälschter Mails an der SMTP-Pforte!</li>
|
||||
</ol>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 3. CHEAT-SHEET -->
|
||||
<section class="card">
|
||||
<h2>🎯 Aufgaben & Checkliste</h2>
|
||||
<h2>📑 3. Cheat-Sheet: DMARC-Tags</h2>
|
||||
<div style="overflow-x: auto;">
|
||||
<table style="width: 100%; border-collapse: collapse; font-size: 0.9rem; text-align: left;">
|
||||
<thead>
|
||||
<tr style="border-bottom: 2px solid var(--border-color); background: var(--bg-subtle);">
|
||||
<th style="padding: 10px;">Tag</th>
|
||||
<th style="padding: 10px;">Zweck</th>
|
||||
<th style="padding: 10px;">Mögliche Werte & Empfehlung</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr style="border-bottom: 1px solid var(--border-color);">
|
||||
<td style="padding: 10px;"><code>p=</code></td>
|
||||
<td style="padding: 10px;">Empfänger-Aktion bei Fehler</td>
|
||||
<td style="padding: 10px;"><code>none</code>, <code>quarantine</code>, <code>reject</code> (Ziel: reject)</td>
|
||||
</tr>
|
||||
<tr style="border-bottom: 1px solid var(--border-color);">
|
||||
<td style="padding: 10px;"><code>rua=</code></td>
|
||||
<td style="padding: 10px;">Aggregierte Berichte</td>
|
||||
<td style="padding: 10px;"><code>mailto:dmarc-reports@firma.de</code></td>
|
||||
</tr>
|
||||
<tr style="border-bottom: 1px solid var(--border-color);">
|
||||
<td style="padding: 10px;"><code>pct=</code></td>
|
||||
<td style="padding: 10px;">Prozentuale Anwendung</td>
|
||||
<td style="padding: 10px;"><code>100</code> (bei Rollout 20, 50, ...)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 10px;"><code>aspf=</code> / <code>adkim=</code></td>
|
||||
<td style="padding: 10px;">Alignment Modus</td>
|
||||
<td style="padding: 10px;"><code>r</code> (Relaxed, Standard) oder <code>s</code> (Strict)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 4. AUFGABEN CHECKLISTE -->
|
||||
<section class="card">
|
||||
<h2>🎯 Aufgaben & Checkliste in <code>aufgabe.sh</code></h2>
|
||||
<div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t1">
|
||||
<label class="task-label" for="t1"><strong>TODO 1:</strong> DMARC TXT-Record unter _dmarc.domain.de anlegen</label>
|
||||
</div><div class="task-item">
|
||||
<label class="task-label" for="t1"><strong>TODO 1:</strong> <code>format_dmarc_record(...)</code>: DMARC TXT-Record für _dmarc erzeugen.</label>
|
||||
</div>
|
||||
<div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t2">
|
||||
<label class="task-label" for="t2"><strong>TODO 2:</strong> Policy-Stufen (p=none -> p=quarantine -> p=reject) staffeln</label>
|
||||
</div><div class="task-item">
|
||||
<label class="task-label" for="t2"><strong>TODO 2:</strong> <code>evaluate_dmarc_verdict(...)</code>: Empfängerentscheidung simulieren.</label>
|
||||
</div>
|
||||
<div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t3">
|
||||
<label class="task-label" for="t3"><strong>TODO 3:</strong> Aggregierte Report-Adresse (rua=mailto:dmarc@domain.de) einrichten</label>
|
||||
</div><div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t4">
|
||||
<label class="task-label" for="t4"><strong>TODO 4:</strong> Strenges vs. relatives Alignment (aspf/adkim) festlegen</label>
|
||||
<label class="task-label" for="t3"><strong>TODO 3:</strong> <code>audit_email_security_suite(...)</code>: 100-Punkte Gesamtaudit durchführen.</label>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 25px; display: flex; gap: 12px; flex-wrap: wrap;">
|
||||
<a href="../../../workspace.html?course=dns_records&track=track_3_email_security&chapter=12_dmarc_und_email_security_suite" class="btn" style="background: #0284c7; color: #fff; font-weight: 700;">💻 In Web-IDE öffnen →</a>
|
||||
<a href="../13_srv_service_records_fuer_ad_und_sip/index.html" class="btn btn-secondary">Weiter zu Modul →</a>
|
||||
<a href="../../../workspace.html?course=dns_records&track=track_3_email_security&chapter=12_dmarc_und_email_security_suite" class="btn" style="background: #1d4ed8; color: #fff; font-weight: 700;">In Web-IDE öffnen →</a>
|
||||
<a href="../13_srv_service_records_fuer_ad_und_sip/index.html" class="btn btn-secondary">Weiter zu Lehrpfad 4 (Modul 13) →</a>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
|
|
|||
|
|
@ -1,97 +1,90 @@
|
|||
#!/usr/bin/env bash
|
||||
# ==============================================================================
|
||||
# MUSTERLÖSUNG: DNS 12: DMARC Policy & Phishing-Abwehr
|
||||
# MUSTERLÖSUNG: DNS 12: DMARC & E-MAIL-SECURITY SUITE
|
||||
# ==============================================================================
|
||||
|
||||
deploy_dmarc_policy() {
|
||||
local domain="${1:-it-praxisportal.de}"
|
||||
local target_dir="${2:-.}"
|
||||
format_dmarc_record() {
|
||||
local dom="$1"
|
||||
local pol="$2"
|
||||
local rua="$3"
|
||||
local pct="${4:-100}"
|
||||
|
||||
echo "▶ Starte professionelle DNS-Operation für $domain..."
|
||||
dom="${dom%.}"
|
||||
local fqdn="_dmarc.${dom}."
|
||||
|
||||
case "12_dmarc_und_email_security_suite" in
|
||||
*01_dns_hierarchie*)
|
||||
echo "Host: www | SLD: it-praxisportal | TLD: de | Root: ."
|
||||
;;
|
||||
*02_rekursive*)
|
||||
echo "Simulation Iteration: Root (.) -> de. -> ns1.it-praxisportal.de -> 188.245.100.5"
|
||||
;;
|
||||
*03_ports_udp*)
|
||||
echo "Protocol: UDP/53 (Standard) | Fallback: TCP/53 | EDNS0 Buffer: 4096 bytes"
|
||||
;;
|
||||
*04_ttl*)
|
||||
echo "TTL Current: 86400s | Migration Planned TTL: 300s | Neg Cache: 3600s"
|
||||
;;
|
||||
*05_a_und_aaaa*)
|
||||
cat << 'ZONE' > "$target_dir/zone_records.txt"
|
||||
@ IN A 188.245.100.5
|
||||
@ IN AAAA 2a01:4f8:c010:d::1
|
||||
www IN A 188.245.100.5
|
||||
ZONE
|
||||
;;
|
||||
*06_cname*)
|
||||
echo "api IN CNAME app.it-praxisportal.de." > "$target_dir/cname_record.txt"
|
||||
;;
|
||||
*07_soa*)
|
||||
cat << 'ZONE' > "$target_dir/soa_record.txt"
|
||||
@ IN SOA ns1.it-praxisportal.de. hostmaster.it-praxisportal.de. (
|
||||
2026091201 ; Serial YYYYMMDDNN
|
||||
7200 ; Refresh (2h)
|
||||
3600 ; Retry (1h)
|
||||
1209600 ; Expire (2w)
|
||||
3600 ; Negative Cache TTL (1h)
|
||||
)
|
||||
ZONE
|
||||
;;
|
||||
*08_ns_und_ptr*)
|
||||
echo "100.245.188.in-addr.arpa. IN PTR mail.it-praxisportal.de." > "$target_dir/ptr_record.txt"
|
||||
;;
|
||||
*09_mx*)
|
||||
echo "@ IN MX 10 mail.it-praxisportal.de." > "$target_dir/mx_record.txt"
|
||||
echo "@ IN MX 20 backup-mail.it-praxisportal.de." >> "$target_dir/mx_record.txt"
|
||||
;;
|
||||
*10_txt_records*)
|
||||
echo '@ IN TXT "v=spf1 mx ip4:188.245.100.5 include:_spf.google.com -all"' > "$target_dir/spf_record.txt"
|
||||
;;
|
||||
*11_dkim*)
|
||||
echo 's1._domainkey IN TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz..."' > "$target_dir/dkim_record.txt"
|
||||
;;
|
||||
*12_dmarc*)
|
||||
echo '_dmarc IN TXT "v=DMARC1; p=reject; rua=mailto:dmarc-reports@it-praxisportal.de; pct=100; aspf=s"' > "$target_dir/dmarc_record.txt"
|
||||
;;
|
||||
*13_srv*)
|
||||
echo '_ldap._tcp.dc._msdcs.corp IN SRV 0 100 389 dc01.corp.it-praxisportal.de.' > "$target_dir/srv_record.txt"
|
||||
;;
|
||||
*14_caa*)
|
||||
echo '@ IN CAA 0 issue "letsencrypt.org"' > "$target_dir/caa_record.txt"
|
||||
echo '@ IN CAA 0 iodef "mailto:security@it-praxisportal.de"' >> "$target_dir/caa_record.txt"
|
||||
;;
|
||||
*15_dns_troubleshooting*)
|
||||
echo "STATUS: NOERROR | ANSWER: 1 | FLAGS: qr aa rd ra"
|
||||
;;
|
||||
*16_master*)
|
||||
cat << 'REPORT' > "$target_dir/dns_audit_report.json"
|
||||
{
|
||||
"domain": "it-praxisportal.de",
|
||||
"audit_status": "PASSED",
|
||||
"score": 100,
|
||||
"soa_serial_valid": true,
|
||||
"ns_redundancy": 2,
|
||||
"email_security": {
|
||||
"spf": "valid (-all)",
|
||||
"dkim": "present",
|
||||
"dmarc": "enforced (p=reject)"
|
||||
},
|
||||
"dnssec": "active"
|
||||
printf "%-25s %-7s IN TXT \"v=DMARC1; p=%s; rua=mailto:%s; pct=%s\"\n" "$fqdn" "3600" "$pol" "$rua" "$pct"
|
||||
return 0
|
||||
}
|
||||
REPORT
|
||||
|
||||
evaluate_dmarc_verdict() {
|
||||
local spf="$1"
|
||||
local dkim="$2"
|
||||
local pol="$3"
|
||||
|
||||
if [ "$spf" = "true" ] || [ "$dkim" = "true" ]; then
|
||||
echo "VERDICT_PASS: Zustellung im Posteingang (mindestens ein Mechanismus valide)."
|
||||
return 0
|
||||
fi
|
||||
|
||||
case "$pol" in
|
||||
none)
|
||||
echo "VERDICT_FAIL_DELIVER: Fehlgeschlagen, aber p=none -> Zustellung erfolgt dennoch."
|
||||
return 0
|
||||
;;
|
||||
quarantine)
|
||||
echo "VERDICT_QUARANTINE: Fehlgeschlagen -> Mail in SPAM-Ordner verschieben."
|
||||
return 1
|
||||
;;
|
||||
reject)
|
||||
echo "VERDICT_REJECT: Fehlgeschlagen -> Mail an SMTP-Pforte abweisen (550 DMARC Failed)!"
|
||||
return 1
|
||||
;;
|
||||
*)
|
||||
echo "UNKNOWN_POLICY"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
echo "✅ DNS-Operation erfolgreich abgeschlossen."
|
||||
audit_email_security_suite() {
|
||||
local spf="$1"
|
||||
local dkim="$2"
|
||||
local dmarc="$3"
|
||||
|
||||
local score=0
|
||||
|
||||
# SPF
|
||||
if [[ "$spf" =~ -all ]]; then
|
||||
score=$((score + 30))
|
||||
elif [[ "$spf" =~ ~all ]]; then
|
||||
score=$((score + 15))
|
||||
fi
|
||||
|
||||
# DKIM
|
||||
if [[ "$dkim" =~ v=DKIM1 ]] && [[ "$dkim" =~ p= ]]; then
|
||||
score=$((score + 35))
|
||||
fi
|
||||
|
||||
# DMARC
|
||||
if [[ "$dmarc" =~ p=reject ]]; then
|
||||
score=$((score + 35))
|
||||
elif [[ "$dmarc" =~ p=quarantine ]]; then
|
||||
score=$((score + 25))
|
||||
elif [[ "$dmarc" =~ p=none ]]; then
|
||||
score=$((score + 10))
|
||||
fi
|
||||
|
||||
local grade="INSECURE"
|
||||
if [ "$score" -ge 90 ]; then
|
||||
grade="EXCELLENT"
|
||||
elif [ "$score" -ge 60 ]; then
|
||||
grade="ACCEPTABLE"
|
||||
fi
|
||||
|
||||
echo "SCORE=$score | RATING=$grade"
|
||||
return 0
|
||||
}
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
deploy_dmarc_policy "$@"
|
||||
format_dmarc_record "$@"
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -1,36 +1,97 @@
|
|||
#!/usr/bin/env bash
|
||||
# ==============================================================================
|
||||
# TESTSUITE: DNS 12: DMARC Policy & Phishing-Abwehr
|
||||
# 🧪 TESTSUITE: DNS 12 - DMARC & E-MAIL-SECURITY SUITE
|
||||
# ==============================================================================
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
BLUE='\033[0;34m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
TARGET_SCRIPT="${1:-aufgabe.sh}"
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
echo -e "${BLUE}🧪 TESTSUITE: DNS 12: DMARC Policy & Phishing-Abwehr${NC}"
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
TOTAL_TESTS=0
|
||||
PASSED_TESTS=0
|
||||
FAILED_TESTS=0
|
||||
|
||||
TEST_DIR=$(mktemp -d /tmp/dns_test_XXXXXX)
|
||||
trap 'rm -rf "$TEST_DIR"' EXIT
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
echo -e "${BLUE}🧪 TESTSUITE: DNS 12: DMARC & E-Mail-Security Suite${NC}"
|
||||
echo -e "${BLUE} Ziel-Skript: ${YELLOW}${TARGET_SCRIPT}${NC}"
|
||||
echo -e "${BLUE}================================================================${NC}\n"
|
||||
|
||||
if [ ! -f "$SCRIPT_DIR/$TARGET_SCRIPT" ]; then
|
||||
echo -e "${RED}❌ Fehler: Datei '$TARGET_SCRIPT' nicht gefunden!${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
source "$SCRIPT_DIR/$TARGET_SCRIPT"
|
||||
|
||||
# Führe Funktion aus
|
||||
deploy_dmarc_policy "example.com" "$TEST_DIR"
|
||||
RET=$?
|
||||
run_test() {
|
||||
local name="$1"
|
||||
local cmd="$2"
|
||||
((TOTAL_TESTS++))
|
||||
echo -e "▶ Test ${TOTAL_TESTS}: ${name}"
|
||||
if eval "$cmd"; then
|
||||
echo -e " ${GREEN}✓ BESTANDEN${NC}\n"
|
||||
((PASSED_TESTS++))
|
||||
else
|
||||
echo -e " ${RED}✗ FEHLGESCHLAGEN${NC}\n"
|
||||
((FAILED_TESTS++))
|
||||
fi
|
||||
}
|
||||
|
||||
if [ $RET -eq 0 ]; then
|
||||
echo -e "${GREEN}✓ Test 1: Funktion deploy_dmarc_policy existiert und gibt 0 zurück.${NC}"
|
||||
echo -e "${GREEN}✓ Test 2: DNS Resource Records / Syntax validiert.${NC}"
|
||||
echo -e "${GREEN}✓ Test 3: Teilziele 1 bis 4 erfolgreich abgeschlossen.${NC}"
|
||||
echo -e "${GREEN}🎉 ERFOLG: Alle DNS-Prüfungen in DNS 12: DMARC Policy & Phishing-Abwehr bestanden!${NC}"
|
||||
# TEST 1: DMARC Record Format
|
||||
run_test "format_dmarc_record erzeugt _dmarc.<dom>. mit p=reject" '
|
||||
OUT=$(format_dmarc_record "firma.de" "reject" "dmarc@firma.de" 100)
|
||||
echo "$OUT" | grep -q "_dmarc.firma.de." && \
|
||||
echo "$OUT" | grep -q "p=reject" && \
|
||||
echo "$OUT" | grep -q "rua=mailto:dmarc@firma.de"
|
||||
'
|
||||
|
||||
# TEST 2: Verdict Pass wenn DKIM gültig
|
||||
run_test "evaluate_dmarc_verdict liefert PASS wenn DKIM wahr ist" '
|
||||
evaluate_dmarc_verdict "false" "true" "reject" | grep -q "VERDICT_PASS"
|
||||
'
|
||||
|
||||
# TEST 3: Verdict Reject wenn beides fehlschlägt und p=reject
|
||||
run_test "evaluate_dmarc_verdict weist Mail ab bei p=reject" '
|
||||
evaluate_dmarc_verdict "false" "false" "reject" | grep -q "VERDICT_REJECT"
|
||||
'
|
||||
|
||||
# TEST 4: Verdict Quarantine wenn beides fehlschlägt und p=quarantine
|
||||
run_test "evaluate_dmarc_verdict leitet in Spam um bei p=quarantine" '
|
||||
evaluate_dmarc_verdict "false" "false" "quarantine" | grep -q "VERDICT_QUARANTINE"
|
||||
'
|
||||
|
||||
# TEST 5: Gesamtaudit Score 100 (SPF -all + DKIM + DMARC reject)
|
||||
run_test "audit_email_security_suite vergibt 100 Punkte und EXCELLENT" '
|
||||
SPF="v=spf1 mx -all"
|
||||
DKIM="v=DKIM1; k=rsa; p=MIIBIjANBgkqhki..."
|
||||
DMARC="v=DMARC1; p=reject; rua=mailto:a@b.de"
|
||||
OUT=$(audit_email_security_suite "$SPF" "$DKIM" "$DMARC")
|
||||
echo "$OUT" | grep -q "SCORE=100" && \
|
||||
echo "$OUT" | grep -q "RATING=EXCELLENT"
|
||||
'
|
||||
|
||||
# TEST 6: Gesamtaudit Score bei schwacher Konfiguration
|
||||
run_test "audit_email_security_suite stuft p=none und Softfail ab" '
|
||||
SPF="v=spf1 ~all"
|
||||
DKIM=""
|
||||
DMARC="v=DMARC1; p=none"
|
||||
OUT=$(audit_email_security_suite "$SPF" "$DKIM" "$DMARC")
|
||||
echo "$OUT" | grep -q "SCORE=25" && \
|
||||
echo "$OUT" | grep -q "RATING=INSECURE"
|
||||
'
|
||||
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
if [ "$FAILED_TESTS" -eq 0 ]; then
|
||||
echo -e "${GREEN}🎉 ERFOLG: Alle ${PASSED_TESTS}/${TOTAL_TESTS} Tests in DNS 12 erfolgreich bestanden!${NC}"
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
exit 0
|
||||
else
|
||||
echo -e "${RED}❌ FEHLER: Skript fehlgeschlagen mit Status $RET${NC}"
|
||||
echo -e "${RED}❌ FEHLER: ${FAILED_TESTS} von ${TOTAL_TESTS} Tests fehlgeschlagen.${NC}"
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -1,18 +1,51 @@
|
|||
# DNS 13: SRV Service Records (Active Directory & SIP)
|
||||
# Modul 13: SRV-Service Records für Active Directory & SIP (RFC 2782)
|
||||
|
||||
Willkommen zum Kurs **DNS-Records, Domain Name System & E-Mail-Sicherheit** im IT-Praxisportal!
|
||||
## 📌 Didaktische Einführung & Relevanz für IT-Systemintegration
|
||||
Ein Standard A-Record löst einen Hostnamen in eine IP-Adresse auf. Doch welcher Dienst (HTTP, LDAP, Kerberos, SIP) läuft auf welchem Port? Ohne SRV-Records mussten Client-Anwendungen Ports hardcoden oder manuell konfiguriert werden.
|
||||
|
||||
## 💡 Das Wichtigste in Kürze
|
||||
- **Ziel:** Dienste automatisch im Netzwerk finden: _service._proto.name TTL Class SRV Priority Weight Port Target.
|
||||
- **Wichtigstes Tool:** `dig SRV`
|
||||
**RFC 2782 definiert den SRV-Record (Service Record)**. Er verbindet:
|
||||
1. **Dienst & Protokoll** (`_service._proto.name`)
|
||||
2. **Priorität (Priority)**: Niedrigere Zahl = bevorzugt (wie bei MX).
|
||||
3. **Gewichtung (Weight)**: Lastverteilung unter Servern mit gleicher Priorität.
|
||||
4. **Port**: TCP- oder UDP-Portnummer (z. B. 389 für LDAP, 88 für Kerberos, 5060 für SIP).
|
||||
5. **Ziel-Host (Target)**: Der vollqualifizierte Hostname (FQDN) des Zielservers.
|
||||
|
||||
## 🎯 Deine Aufgaben (Checkliste)
|
||||
1. **Teilziel 1:** SRV-Record für Active Directory LDAP (_ldap._tcp.dc._msdcs) erstellen
|
||||
2. **Teilziel 2:** SRV-Record für Kerberos Authentifizierung (_kerberos._tcp) anlegen
|
||||
3. **Teilziel 3:** Priorität und Gewichtung (Weight) für Lastverteilung berechnen
|
||||
4. **Teilziel 4:** Service-Discovery Abfragen mit dig SRV durchführen
|
||||
In **Microsoft Active Directory** ist DNS mit SRV-Records das absolute Herzstück: Ein Windows-Client findet seinen zuständigen Domain Controller über `_ldap._tcp.dc._msdcs.domaene.local`!
|
||||
|
||||
## 🧪 Tests ausführen
|
||||
```bash
|
||||
bash test_aufgabe.sh
|
||||
---
|
||||
|
||||
## ⚙️ RFC 2782 Syntax im BIND Zonenformat
|
||||
|
||||
```text
|
||||
; _service._proto.name. TTL Class SRV Priority Weight Port Target.
|
||||
_ldap._tcp.corp.local. 86400 IN SRV 0 100 389 dc01.corp.local.
|
||||
_ldap._tcp.corp.local. 86400 IN SRV 10 50 389 dc02.corp.local.
|
||||
_sip._udp.firma.de. 3600 IN SRV 10 60 5060 pbx01.firma.de.
|
||||
_sip._udp.firma.de. 3600 IN SRV 10 40 5060 pbx02.firma.de.
|
||||
```
|
||||
|
||||
### Die Felder im Detail:
|
||||
1. **Name**: Muss mit einem Unterstrich `_` für Service und Protokoll beginnen (`_ldap._tcp.domain.tld.`).
|
||||
2. **Priority (0-65535)**: Der Client kontaktiert zuerst den Zielserver mit der niedrigsten Prioritätsnummer.
|
||||
3. **Weight (0-65535)**: Wenn zwei Server dieselbe Priorität haben (z. B. 10), bestimmt das Verhältnis ihrer Gewichte (z. B. 60 und 40) die Wahrscheinlichkeit der Auswahl (60% zu 40%). Wenn Gewicht 0 ist, wird der Server minimal gewichtet.
|
||||
4. **Port (1-65535)**: Der Zielport des Dienstes.
|
||||
5. **Target**: Der FQDN des Servers mit Schlusspunkt `.`. **Ein CNAME als Ziel ist laut RFC 2782 strikt verboten!**
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Deine Aufgaben im Modul 13:
|
||||
|
||||
1. **`format_srv_record`**:
|
||||
Erzeugt einen RFC 2782 BIND SRV-Record:
|
||||
`format_srv_record "_ldap" "_tcp" "corp.de" 0 100 389 "dc1.corp.de" 86400`
|
||||
-> `_ldap._tcp.corp.de. 86400 IN SRV 0 100 389 dc1.corp.de.`
|
||||
|
||||
2. **`parse_srv_record`**:
|
||||
Nimmt eine SRV-Zeile entgegen und extrahiert Service, Proto, Priority, Weight, Port und Target im Key-Value-Format:
|
||||
`SERVICE=_ldap PROTO=_tcp PRIO=0 WEIGHT=100 PORT=389 TARGET=dc1.corp.de.`
|
||||
|
||||
3. **`generate_ad_srv_records`**:
|
||||
Erzeugt die essenziellen Microsoft Active Directory Discovery Records für einen DC (`_ldap._tcp` Port 389, `_kerberos._tcp` Port 88, `_gc._tcp` Port 3268 für Global Catalog).
|
||||
|
||||
4. **`select_srv_target_by_prio`**:
|
||||
Nimmt eine Liste von SRV-Records entgegen und ermittelt den primären Ziel-Host mit der niedrigsten Prioritätszahl.
|
||||
|
|
|
|||
|
|
@ -1,21 +1,53 @@
|
|||
#!/usr/bin/env bash
|
||||
# ==============================================================================
|
||||
# 🚀 Service Discovery - DNS 13: SRV Service Records (Active Directory & SIP)
|
||||
# Modul 13: SRV-Service Records für Active Directory & SIP (RFC 2782)
|
||||
# ==============================================================================
|
||||
|
||||
# 🎯 TEILZIEL 1: SRV-Record für Active Directory LDAP (_ldap._tcp.dc._msdcs) erstellen
|
||||
# 🎯 TEILZIEL 2: SRV-Record für Kerberos Authentifizierung (_kerberos._tcp) anlegen
|
||||
# 🎯 TEILZIEL 3: Priorität und Gewichtung (Weight) für Lastverteilung berechnen
|
||||
# 🎯 TEILZIEL 4: Service-Discovery Abfragen mit dig SRV durchführen
|
||||
# 🎯 TEILZIEL 1: RFC 2782 SRV-Record erzeugen
|
||||
# Format: _service._proto.domain. TTL IN SRV Priority Weight Port Target.
|
||||
# Beispiel: format_srv_record "_ldap" "_tcp" "corp.de" 0 100 389 "dc1.corp.de" 86400
|
||||
# Rückgabe: _ldap._tcp.corp.de. 86400 IN SRV 0 100 389 dc1.corp.de.
|
||||
function format_srv_record() {
|
||||
local service="$1"
|
||||
local proto="$2"
|
||||
local domain="$3"
|
||||
local prio="$4"
|
||||
local weight="$5"
|
||||
local port="$6"
|
||||
local target="$7"
|
||||
local ttl="${8:-86400}"
|
||||
|
||||
configure_ad_srv_records() {
|
||||
local domain="${1:-it-praxisportal.de}"
|
||||
echo "▶ Analysiere DNS-Konfiguration für $domain..."
|
||||
|
||||
# TODO: Implementiere die geforderten DNS-Prüfungen oder Record-Erstellungen
|
||||
return 1
|
||||
# TODO: Implementiere Formatierung mit führenden Unterstrichen und Trailing Dots
|
||||
return 1
|
||||
}
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
configure_ad_srv_records "$@"
|
||||
fi
|
||||
# 🎯 TEILZIEL 2: SRV-Record parsen
|
||||
# Extrahiert Service, Protokoll, Prio, Weight, Port und Target
|
||||
# Rückgabe: SERVICE=_ldap PROTO=_tcp PRIO=0 WEIGHT=100 PORT=389 TARGET=dc1.corp.de.
|
||||
function parse_srv_record() {
|
||||
local srv_line="$1"
|
||||
|
||||
# TODO: Parse die SRV-Zeile und gebe formatierte Key-Values zurück
|
||||
return 1
|
||||
}
|
||||
|
||||
# 🎯 TEILZIEL 3: Active Directory Standard-SRV Records erzeugen
|
||||
# Erzeugt Records für LDAP (389), Kerberos (88) und Global Catalog (3268)
|
||||
function generate_ad_srv_records() {
|
||||
local domain="$1"
|
||||
local dc_hostname="$2"
|
||||
local prio="${3:-0}"
|
||||
local weight="${4:-100}"
|
||||
|
||||
# TODO: Erzeuge die drei Standard Active Directory SRV Einträge
|
||||
return 1
|
||||
}
|
||||
|
||||
# 🎯 TEILZIEL 4: Primäres SRV-Ziel ermitteln (Niedrigste Prioritätszahl gewinnt)
|
||||
# Liest mehrere SRV-Zeilen (eine pro Zeile) und gibt den Target-Host der niedrigsten Prio zurück
|
||||
function select_srv_target_by_prio() {
|
||||
local srv_records="$1"
|
||||
|
||||
# TODO: Finde den Host mit minimaler Priorität
|
||||
return 1
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,63 +1,58 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<link rel="icon" type="image/svg+xml" href="../../../assets/favicon.svg">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>DNS 13: SRV Service Records (Active Directory & SIP)</title>
|
||||
<title>Modul 13: SRV-Service Records für Active Directory & SIP</title>
|
||||
<link rel="stylesheet" href="../../../assets/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-layout">
|
||||
<div class="app-main">
|
||||
<header>
|
||||
<div class="header-container">
|
||||
<div class="header-badge">🚀 Service Discovery</div>
|
||||
<h1>DNS 13: SRV Service Records (Active Directory & SIP)</h1>
|
||||
<p>Dienste automatisch im Netzwerk finden: _service._proto.name TTL Class SRV Priority Weight Port Target.</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="container">
|
||||
<section class="card">
|
||||
<h2>💡 Theorie & Praxiskonzept</h2>
|
||||
<p>Dienste automatisch im Netzwerk finden: _service._proto.name TTL Class SRV Priority Weight Port Target.</p>
|
||||
<div class="code-container">
|
||||
<div class="code-header">
|
||||
<span>DNS Command & Syntax Reference</span>
|
||||
<span>Tool: dig SRV</span>
|
||||
</div>
|
||||
<pre><code># Wichtige Befehle & Zonensyntax:
|
||||
dig dig SRV
|
||||
named-checkzone example.com /etc/bind/zones/db.example.com</code></pre>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<h2>🎯 Aufgaben & Checkliste</h2>
|
||||
<div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t1">
|
||||
<label class="task-label" for="t1"><strong>TODO 1:</strong> SRV-Record für Active Directory LDAP (_ldap._tcp.dc._msdcs) erstellen</label>
|
||||
</div><div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t2">
|
||||
<label class="task-label" for="t2"><strong>TODO 2:</strong> SRV-Record für Kerberos Authentifizierung (_kerberos._tcp) anlegen</label>
|
||||
</div><div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t3">
|
||||
<label class="task-label" for="t3"><strong>TODO 3:</strong> Priorität und Gewichtung (Weight) für Lastverteilung berechnen</label>
|
||||
</div><div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t4">
|
||||
<label class="task-label" for="t4"><strong>TODO 4:</strong> Service-Discovery Abfragen mit dig SRV durchführen</label>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 25px; display: flex; gap: 12px; flex-wrap: wrap;">
|
||||
<a href="../../../workspace.html?course=dns_records&track=track_4_advanced_dns&chapter=13_srv_service_records_fuer_ad_und_sip" class="btn" style="background: #0284c7; color: #fff; font-weight: 700;">💻 In Web-IDE öffnen →</a>
|
||||
<a href="../14_caa_und_dnssec_sicherheit/index.html" class="btn btn-secondary">Weiter zu Modul →</a>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
<header>
|
||||
<div class="header-container">
|
||||
<div class="header-badge">Lehrpfad 4: Erweiterte Records, DNSSEC & Troubleshooting</div>
|
||||
<h1>Modul 13: SRV-Service Records für Active Directory & SIP (RFC 2782)</h1>
|
||||
<p>Service Discovery im DNS: Wie Clients Server für LDAP, Kerberos und VoIP dynamisch mit Port, Priorität und Lastverteilung finden.</p>
|
||||
<div class="nav-links">
|
||||
<a href="../12_dmarc_und_email_security_suite/index.html">← Modul 12: DMARC</a>
|
||||
<a href="../../../dashboard.html">Kurs-Dashboard</a>
|
||||
<a href="../14_caa_und_dnssec_sicherheit/index.html">Modul 14: CAA & DNSSEC →</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="../../../assets/courses_manifest.js"></script>
|
||||
<script src="../../../assets/course_sidebar.js"></script>
|
||||
</header>
|
||||
|
||||
<main class="container">
|
||||
<div class="card">
|
||||
<h2>1. Warum SRV-Records? Das Problem klassischer A-Records</h2>
|
||||
<p>Ein A-Record liefert nur eine IP-Adresse. Läuft der LDAP-Server auf Port 389 oder 636? Welcher von drei DCs hat die höchste Rechenleistung? <strong>RFC 2782 SRV-Records</strong> lösen dies durch strukturierte Metadaten direkt in der DNS-Zone.</p>
|
||||
<div class="box box-analogie">
|
||||
<div class="box-title">🏢 Analogie: Das Firmen-Telefonbuch mit Durchwahlen</div>
|
||||
<p>Ein A-Record ist die Firmenadresse auf dem Briefkopf. Ein <strong>SRV-Record</strong> ist der Empfang mit Durchwahlliste: Er sagt dir, in welchem Büro (Host), auf welcher Durchwahl (Port) und wer zuerst abhebt (Priorität/Gewichtung).</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>2. Active Directory DNS-Discovery Struktur</h2>
|
||||
<div class="code-container">
|
||||
<div class="code-header">SRV-Discovery in Microsoft Windows Server Umgebungen</div>
|
||||
<pre><code>; Active Directory Domain Controller Discovery
|
||||
_ldap._tcp.dc._msdcs.corp.local. 600 IN SRV 0 100 389 dc01.corp.local.
|
||||
_kerberos._tcp.dc._msdcs.corp.local. 600 IN SRV 0 100 88 dc01.corp.local.
|
||||
_gc._tcp.corp.local. 600 IN SRV 0 100 3268 dc01.corp.local.</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>3. 🎯 Deine Teilziele in der Web-IDE</h2>
|
||||
<ul class="task-checklist">
|
||||
<li><strong>Teilziel 1:</strong> <code>format_srv_record</code> – Standardkonformes BIND SRV-Format mit Trailing Dots.</li>
|
||||
<li><strong>Teilziel 2:</strong> <code>parse_srv_record</code> – Parsing der 6 Kernfelder eines SRV-Eintrags.</li>
|
||||
<li><strong>Teilziel 3:</strong> <code>generate_ad_srv_records</code> – Automatische Erstellung der Kerberos-, LDAP- und GC-Records.</li>
|
||||
<li><strong>Teilziel 4:</strong> <code>select_srv_target_by_prio</code> – Bestimmung des Failover-Ziels mit niedrigster Prioritätszahl.</li>
|
||||
</ul>
|
||||
<div style="margin-top: 20px;">
|
||||
<a href="../../../workspace.html?course=dns_records&chapter=13_srv_service_records_fuer_ad_und_sip" class="btn">🚀 Aufgabe in der Web-IDE bearbeiten</a>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
|
@ -1,97 +1,61 @@
|
|||
#!/usr/bin/env bash
|
||||
# ==============================================================================
|
||||
# MUSTERLÖSUNG: DNS 13: SRV Service Records (Active Directory & SIP)
|
||||
# Musterlösung Modul 13: SRV-Service Records für AD & SIP
|
||||
# ==============================================================================
|
||||
|
||||
configure_ad_srv_records() {
|
||||
local domain="${1:-it-praxisportal.de}"
|
||||
local target_dir="${2:-.}"
|
||||
function format_srv_record() {
|
||||
local service="$1"
|
||||
local proto="$2"
|
||||
local domain="$3"
|
||||
local prio="$4"
|
||||
local weight="$5"
|
||||
local port="$6"
|
||||
local target="$7"
|
||||
local ttl="${8:-86400}"
|
||||
|
||||
echo "▶ Starte professionelle DNS-Operation für $domain..."
|
||||
[[ "$service" != _* ]] && service="_${service}"
|
||||
[[ "$proto" != _* ]] && proto="_${proto}"
|
||||
[[ "$domain" != *. ]] && domain="${domain}."
|
||||
[[ "$target" != *. ]] && target="${target}."
|
||||
|
||||
case "13_srv_service_records_fuer_ad_und_sip" in
|
||||
*01_dns_hierarchie*)
|
||||
echo "Host: www | SLD: it-praxisportal | TLD: de | Root: ."
|
||||
;;
|
||||
*02_rekursive*)
|
||||
echo "Simulation Iteration: Root (.) -> de. -> ns1.it-praxisportal.de -> 188.245.100.5"
|
||||
;;
|
||||
*03_ports_udp*)
|
||||
echo "Protocol: UDP/53 (Standard) | Fallback: TCP/53 | EDNS0 Buffer: 4096 bytes"
|
||||
;;
|
||||
*04_ttl*)
|
||||
echo "TTL Current: 86400s | Migration Planned TTL: 300s | Neg Cache: 3600s"
|
||||
;;
|
||||
*05_a_und_aaaa*)
|
||||
cat << 'ZONE' > "$target_dir/zone_records.txt"
|
||||
@ IN A 188.245.100.5
|
||||
@ IN AAAA 2a01:4f8:c010:d::1
|
||||
www IN A 188.245.100.5
|
||||
ZONE
|
||||
;;
|
||||
*06_cname*)
|
||||
echo "api IN CNAME app.it-praxisportal.de." > "$target_dir/cname_record.txt"
|
||||
;;
|
||||
*07_soa*)
|
||||
cat << 'ZONE' > "$target_dir/soa_record.txt"
|
||||
@ IN SOA ns1.it-praxisportal.de. hostmaster.it-praxisportal.de. (
|
||||
2026091201 ; Serial YYYYMMDDNN
|
||||
7200 ; Refresh (2h)
|
||||
3600 ; Retry (1h)
|
||||
1209600 ; Expire (2w)
|
||||
3600 ; Negative Cache TTL (1h)
|
||||
)
|
||||
ZONE
|
||||
;;
|
||||
*08_ns_und_ptr*)
|
||||
echo "100.245.188.in-addr.arpa. IN PTR mail.it-praxisportal.de." > "$target_dir/ptr_record.txt"
|
||||
;;
|
||||
*09_mx*)
|
||||
echo "@ IN MX 10 mail.it-praxisportal.de." > "$target_dir/mx_record.txt"
|
||||
echo "@ IN MX 20 backup-mail.it-praxisportal.de." >> "$target_dir/mx_record.txt"
|
||||
;;
|
||||
*10_txt_records*)
|
||||
echo '@ IN TXT "v=spf1 mx ip4:188.245.100.5 include:_spf.google.com -all"' > "$target_dir/spf_record.txt"
|
||||
;;
|
||||
*11_dkim*)
|
||||
echo 's1._domainkey IN TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz..."' > "$target_dir/dkim_record.txt"
|
||||
;;
|
||||
*12_dmarc*)
|
||||
echo '_dmarc IN TXT "v=DMARC1; p=reject; rua=mailto:dmarc-reports@it-praxisportal.de; pct=100; aspf=s"' > "$target_dir/dmarc_record.txt"
|
||||
;;
|
||||
*13_srv*)
|
||||
echo '_ldap._tcp.dc._msdcs.corp IN SRV 0 100 389 dc01.corp.it-praxisportal.de.' > "$target_dir/srv_record.txt"
|
||||
;;
|
||||
*14_caa*)
|
||||
echo '@ IN CAA 0 issue "letsencrypt.org"' > "$target_dir/caa_record.txt"
|
||||
echo '@ IN CAA 0 iodef "mailto:security@it-praxisportal.de"' >> "$target_dir/caa_record.txt"
|
||||
;;
|
||||
*15_dns_troubleshooting*)
|
||||
echo "STATUS: NOERROR | ANSWER: 1 | FLAGS: qr aa rd ra"
|
||||
;;
|
||||
*16_master*)
|
||||
cat << 'REPORT' > "$target_dir/dns_audit_report.json"
|
||||
{
|
||||
"domain": "it-praxisportal.de",
|
||||
"audit_status": "PASSED",
|
||||
"score": 100,
|
||||
"soa_serial_valid": true,
|
||||
"ns_redundancy": 2,
|
||||
"email_security": {
|
||||
"spf": "valid (-all)",
|
||||
"dkim": "present",
|
||||
"dmarc": "enforced (p=reject)"
|
||||
},
|
||||
"dnssec": "active"
|
||||
}
|
||||
REPORT
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "✅ DNS-Operation erfolgreich abgeschlossen."
|
||||
return 0
|
||||
echo "${service}.${proto}.${domain} ${ttl} IN SRV ${prio} ${weight} ${port} ${target}"
|
||||
}
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
configure_ad_srv_records "$@"
|
||||
fi
|
||||
function parse_srv_record() {
|
||||
local srv_line="$1"
|
||||
read -r fqdn ttl in_kw srv_kw prio weight port target <<< "$srv_line"
|
||||
|
||||
local service proto domain
|
||||
service=$(echo "$fqdn" | awk -F. '{print $1}')
|
||||
proto=$(echo "$fqdn" | awk -F. '{print $2}')
|
||||
|
||||
echo "SERVICE=${service} PROTO=${proto} PRIO=${prio} WEIGHT=${weight} PORT=${port} TARGET=${target}"
|
||||
}
|
||||
|
||||
function generate_ad_srv_records() {
|
||||
local domain="$1"
|
||||
local dc_hostname="$2"
|
||||
local prio="${3:-0}"
|
||||
local weight="${4:-100}"
|
||||
|
||||
format_srv_record "_ldap" "_tcp" "$domain" "$prio" "$weight" 389 "$dc_hostname" 600
|
||||
format_srv_record "_kerberos" "_tcp" "$domain" "$prio" "$weight" 88 "$dc_hostname" 600
|
||||
format_srv_record "_gc" "_tcp" "$domain" "$prio" "$weight" 3268 "$dc_hostname" 600
|
||||
}
|
||||
|
||||
function select_srv_target_by_prio() {
|
||||
local srv_records="$1"
|
||||
local best_target=""
|
||||
local best_prio=999999
|
||||
|
||||
while IFS= read -r line; do
|
||||
[[ -z "$line" ]] && continue
|
||||
read -r _ _ _ _ prio _ _ target <<< "$line"
|
||||
if [ "$prio" -lt "$best_prio" ]; then
|
||||
best_prio="$prio"
|
||||
best_target="$target"
|
||||
fi
|
||||
done <<< "$srv_records"
|
||||
|
||||
echo "$best_target"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,36 +1,59 @@
|
|||
#!/usr/bin/env bash
|
||||
# ==============================================================================
|
||||
# TESTSUITE: DNS 13: SRV Service Records (Active Directory & SIP)
|
||||
# ==============================================================================
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
TARGET_SCRIPT="${1:-aufgabe.sh}"
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
TARGET_SCRIPT="${1:-$SCRIPT_DIR/aufgabe.sh}"
|
||||
source "$TARGET_SCRIPT"
|
||||
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
echo -e "${BLUE}🧪 TESTSUITE: DNS 13: SRV Service Records (Active Directory & SIP)${NC}"
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
PASSED_TESTS=0
|
||||
TOTAL_TESTS=0
|
||||
|
||||
TEST_DIR=$(mktemp -d /tmp/dns_test_XXXXXX)
|
||||
trap 'rm -rf "$TEST_DIR"' EXIT
|
||||
run_test() {
|
||||
local desc="$1"
|
||||
local cmd="$2"
|
||||
((TOTAL_TESTS++))
|
||||
echo -e "
|
||||
▶ Test ${TOTAL_TESTS}: ${desc}"
|
||||
if eval "$cmd"; then
|
||||
echo -e " [32m✓ BESTANDEN[0m"
|
||||
((PASSED_TESTS++))
|
||||
else
|
||||
echo -e " [31m✗ FEHLGESCHLAGEN[0m
|
||||
"
|
||||
fi
|
||||
}
|
||||
|
||||
source "$SCRIPT_DIR/$TARGET_SCRIPT"
|
||||
run_test "format_srv_record erzeugt RFC 2782 konformen SRV Eintrag" '
|
||||
OUT=$(format_srv_record "ldap" "tcp" "corp.de" 0 100 389 "dc1.corp.de" 86400)
|
||||
[ "$OUT" = "_ldap._tcp.corp.de. 86400 IN SRV 0 100 389 dc1.corp.de." ]
|
||||
'
|
||||
|
||||
# Führe Funktion aus
|
||||
configure_ad_srv_records "example.com" "$TEST_DIR"
|
||||
RET=$?
|
||||
run_test "parse_srv_record extrahiert Prio, Port und Target" '
|
||||
LINE="_sip._udp.firma.de. 3600 IN SRV 10 60 5060 pbx01.firma.de."
|
||||
OUT=$(parse_srv_record "$LINE")
|
||||
echo "$OUT" | grep -q "SERVICE=_sip" && echo "$OUT" | grep -q "PORT=5060" && echo "$OUT" | grep -q "TARGET=pbx01.firma.de."
|
||||
'
|
||||
|
||||
if [ $RET -eq 0 ]; then
|
||||
echo -e "${GREEN}✓ Test 1: Funktion configure_ad_srv_records existiert und gibt 0 zurück.${NC}"
|
||||
echo -e "${GREEN}✓ Test 2: DNS Resource Records / Syntax validiert.${NC}"
|
||||
echo -e "${GREEN}✓ Test 3: Teilziele 1 bis 4 erfolgreich abgeschlossen.${NC}"
|
||||
echo -e "${GREEN}🎉 ERFOLG: Alle DNS-Prüfungen in DNS 13: SRV Service Records (Active Directory & SIP) bestanden!${NC}"
|
||||
run_test "generate_ad_srv_records erzeugt LDAP, Kerberos und GC Records" '
|
||||
OUT=$(generate_ad_srv_records "ad.local" "dc01.ad.local" 0 100)
|
||||
echo "$OUT" | grep -q "_ldap._tcp.ad.local." && echo "$OUT" | grep -q "_kerberos._tcp.ad.local." && echo "$OUT" | grep -q "_gc._tcp.ad.local."
|
||||
'
|
||||
|
||||
run_test "select_srv_target_by_prio wählt Host mit kleinster Priorität" '
|
||||
RECS=$(cat <<EOF
|
||||
_ldap._tcp.dom.de. 300 IN SRV 20 50 389 backup.dom.de.
|
||||
_ldap._tcp.dom.de. 300 IN SRV 5 100 389 primary.dom.de.
|
||||
_ldap._tcp.dom.de. 300 IN SRV 10 80 389 secondary.dom.de.
|
||||
EOF
|
||||
)
|
||||
TARGET=$(select_srv_target_by_prio "$RECS")
|
||||
[ "$TARGET" = "primary.dom.de." ]
|
||||
'
|
||||
|
||||
if [ "$PASSED_TESTS" -eq "$TOTAL_TESTS" ]; then
|
||||
echo -e "
|
||||
[32m🎉 ERFOLG: Alle ${PASSED_TESTS}/${TOTAL_TESTS} Tests in DNS 13 bestanden![0m"
|
||||
exit 0
|
||||
else
|
||||
echo -e "${RED}❌ FEHLER: Skript fehlgeschlagen mit Status $RET${NC}"
|
||||
echo -e "
|
||||
[31m❌ FEHLER: ${PASSED_TESTS}/${TOTAL_TESTS} Tests bestanden.[0m"
|
||||
exit 1
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -1,18 +1,61 @@
|
|||
# DNS 14: CAA Records & DNSSEC Vertrauenskette
|
||||
# Modul 14: CAA Records & DNSSEC Sicherheit (RFC 8659, RFC 4034)
|
||||
|
||||
Willkommen zum Kurs **DNS-Records, Domain Name System & E-Mail-Sicherheit** im IT-Praxisportal!
|
||||
## 📌 Didaktische Einführung & Relevanz für IT-Sicherheit
|
||||
Klassisches DNS besitzt **keine Authentizität und keine Integritätsprüfung**. Jeder Man-in-the-Middle kann gefälschte DNS-Antworten unterschieben (DNS Spoofing / Cache Poisoning). Zudem kann theoretisch jede öffentliche Zertifizierungsstelle (CA) SSL/TLS-Zertifikate für beliebige Domains ausstellen.
|
||||
|
||||
## 💡 Das Wichtigste in Kürze
|
||||
- **Ziel:** CAA zur Autorisierung von Zertifizierungsstellen (Let's Encrypt) und DNSSEC Signierung (RRSIG, DNSKEY, DS).
|
||||
- **Wichtigstes Tool:** `delv`
|
||||
Dieses Modul schließt beide Sicherheitslücken:
|
||||
1. **CAA Records (Certification Authority Authorization - RFC 8659)**:
|
||||
Legt verbindlich fest, welche CAs (z. B. Let's Encrypt oder DigiCert) überhaupt TLS-Zertifikate für die Domain ausstellen dürfen. Verhindert unautorisierte Zertifikatsausstellung!
|
||||
2. **DNSSEC (DNS Security Extensions - RFC 4033-4035)**:
|
||||
Signiert DNS-Resource-Records kryptografisch mit asymmetrischen Schlüsseln.
|
||||
- **RRSIG**: Die digitale Signatur über ein Record-Set (RRset).
|
||||
- **DNSKEY**: Der öffentliche Schlüssel der Zone (ZSK: Zone Signing Key & KSK: Key Signing Key).
|
||||
- **DS (Delegation Signer)**: Der SHA-256 Hash des KSK, der in der übergeordneten Parent-Zone (z. B. `.de`) hinterlegt wird.
|
||||
|
||||
## 🎯 Deine Aufgaben (Checkliste)
|
||||
1. **Teilziel 1:** CAA-Record für autorisierte CAs ('issue "letsencrypt.org"') anlegen
|
||||
2. **Teilziel 2:** DNSSEC Komponenten: Key Signing Key (KSK) & Zone Signing Key (ZSK) verstehen
|
||||
3. **Teilziel 3:** Delegation Signer (DS) Record für die übergeordnete TLD berechnen
|
||||
4. **Teilziel 4:** DNSSEC Vertrauenskette mit delv oder dig +dnssec validieren
|
||||
---
|
||||
|
||||
## 🧪 Tests ausführen
|
||||
```bash
|
||||
bash test_aufgabe.sh
|
||||
## ⚙️ CAA-Record Syntax im BIND-Format
|
||||
|
||||
```text
|
||||
; domain.tld. TTL Class CAA Flags Tag Value
|
||||
firma.de. 3600 IN CAA 0 issue "letsencrypt.org"
|
||||
firma.de. 3600 IN CAA 0 issuewild ";"
|
||||
firma.de. 3600 IN CAA 0 iodef "mailto:security@firma.de"
|
||||
```
|
||||
|
||||
### Die CAA-Tags:
|
||||
- `issue`: Erlaubt der genannten CA normale Host-Zertifikate auszustellen.
|
||||
- `issuewild`: Regelt Wildcard-Zertifikate (`*.firma.de`). Der Wert `";"` verbietet Wildcards komplett!
|
||||
- `iodef`: Empfängt Vorfalls-Meldungen (Incident Reporting) bei unberechtigten Ausstellungsversuchen.
|
||||
|
||||
---
|
||||
|
||||
## 🔐 DNSSEC Vertrauenskette (Chain of Trust)
|
||||
|
||||
```text
|
||||
[ Root-Zone . ] --> Verankert mit Trust Anchor
|
||||
↓ (DS Record für 'de')
|
||||
[ TLD-Zone .de ] --> DNSKEY & RRSIG
|
||||
↓ (DS Record für 'firma.de')
|
||||
[ Eigene Zone firma.de ] --> KSK signiert ZSK, ZSK signiert A/AAAA/MX
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Deine Aufgaben im Modul 14:
|
||||
|
||||
1. **`format_caa_record`**:
|
||||
Erzeugt standardkonforme BIND CAA Records mit Tag und Anführungszeichen:
|
||||
`format_caa_record "firma.de" "issue" "letsencrypt.org" 3600`
|
||||
-> `firma.de. 3600 IN CAA 0 issue "letsencrypt.org"`
|
||||
|
||||
2. **`format_caa_wildcard_block`**:
|
||||
Erzeugt die Richtlinie, die normale Zertifikate über Let's Encrypt erlaubt, Wildcard-Zertifikate aber strikt blockiert (`issuewild ";"`).
|
||||
|
||||
3. **`parse_ds_record`**:
|
||||
Zerlegt einen DNSSEC DS-Record in Key-Tag, Algorithmus, Digest-Type und Digest-Hash:
|
||||
Eingabe: `firma.de. 3600 IN DS 2371 13 2 49FD...`
|
||||
Ausgabe: `KEY_TAG=2371 ALGO=13 DIGEST_TYPE=2 HASH=49FD...`
|
||||
|
||||
4. **`verify_dnssec_chain_status`**:
|
||||
Prüft, ob KSK und DS übereinstimmen und liefert `DNSSEC_VALIDATED` oder `BOGUS_CHAIN_BROKEN`.
|
||||
|
|
|
|||
|
|
@ -1,21 +1,50 @@
|
|||
#!/usr/bin/env bash
|
||||
# ==============================================================================
|
||||
# 🚀 DNSSEC & CAA - DNS 14: CAA Records & DNSSEC Vertrauenskette
|
||||
# Modul 14: CAA & DNSSEC Sicherheit (RFC 8659, RFC 4034)
|
||||
# ==============================================================================
|
||||
|
||||
# 🎯 TEILZIEL 1: CAA-Record für autorisierte CAs ('issue "letsencrypt.org"') anlegen
|
||||
# 🎯 TEILZIEL 2: DNSSEC Komponenten: Key Signing Key (KSK) & Zone Signing Key (ZSK) verstehen
|
||||
# 🎯 TEILZIEL 3: Delegation Signer (DS) Record für die übergeordnete TLD berechnen
|
||||
# 🎯 TEILZIEL 4: DNSSEC Vertrauenskette mit delv oder dig +dnssec validieren
|
||||
# 🎯 TEILZIEL 1: RFC 8659 CAA-Record formatieren
|
||||
# Format: <domain>. <ttl> IN CAA <flags> <tag> "<value>"
|
||||
function format_caa_record() {
|
||||
local domain="$1"
|
||||
local tag="$2"
|
||||
local value="$3"
|
||||
local flags="${4:-0}"
|
||||
local ttl="${5:-3600}"
|
||||
|
||||
setup_caa_and_dnssec() {
|
||||
local domain="${1:-it-praxisportal.de}"
|
||||
echo "▶ Analysiere DNS-Konfiguration für $domain..."
|
||||
|
||||
# TODO: Implementiere die geforderten DNS-Prüfungen oder Record-Erstellungen
|
||||
return 1
|
||||
# TODO: Implementiere CAA Record Formatierung mit Trailing Dot und Anführungszeichen
|
||||
return 1
|
||||
}
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
setup_caa_and_dnssec "$@"
|
||||
fi
|
||||
# 🎯 TEILZIEL 2: Kompletten CAA-Sicherheitsblock generieren
|
||||
# Erlaubt normale Zertifikate über 'letsencrypt.org'
|
||||
# Blockiert Wildcards mit issuewild ";"
|
||||
# Meldet Vorfälle an mailto:security@<domain>
|
||||
function format_caa_wildcard_block() {
|
||||
local domain="$1"
|
||||
|
||||
# TODO: Erzeuge die 3 definierten CAA-Records
|
||||
return 1
|
||||
}
|
||||
|
||||
# 🎯 TEILZIEL 3: DNSSEC DS-Record zerlegen
|
||||
# Beispiel: firma.de. 3600 IN DS 2371 13 2 49FDE...
|
||||
# Rückgabe: KEY_TAG=2371 ALGO=13 DIGEST_TYPE=2 HASH=49FDE...
|
||||
function parse_ds_record() {
|
||||
local ds_line="$1"
|
||||
|
||||
# TODO: Extrahiere die 4 DNSSEC-DS Felder
|
||||
return 1
|
||||
}
|
||||
|
||||
# 🎯 TEILZIEL 4: Trust Chain Verifikation simulieren
|
||||
# Prüft, ob der generierte Hash des KSK-Public-Keys mit dem Parent DS-Hash übereinstimmt
|
||||
# Wenn gleich: "DNSSEC_SECURE: Chain of trust verified"
|
||||
# Wenn ungleich: "BOGUS_CHAIN_BROKEN: Key tag or hash mismatch"
|
||||
function verify_dnssec_chain_status() {
|
||||
local ksk_hash="$1"
|
||||
local ds_hash="$2"
|
||||
|
||||
# TODO: Vergleiche Hashes und gebe Status zurück
|
||||
return 1
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,63 +1,51 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<link rel="icon" type="image/svg+xml" href="../../../assets/favicon.svg">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>DNS 14: CAA Records & DNSSEC Vertrauenskette</title>
|
||||
<title>Modul 14: CAA & DNSSEC Sicherheit</title>
|
||||
<link rel="stylesheet" href="../../../assets/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-layout">
|
||||
<div class="app-main">
|
||||
<header>
|
||||
<div class="header-container">
|
||||
<div class="header-badge">🚀 DNSSEC & CAA</div>
|
||||
<h1>DNS 14: CAA Records & DNSSEC Vertrauenskette</h1>
|
||||
<p>CAA zur Autorisierung von Zertifizierungsstellen (Let's Encrypt) und DNSSEC Signierung (RRSIG, DNSKEY, DS).</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="container">
|
||||
<section class="card">
|
||||
<h2>💡 Theorie & Praxiskonzept</h2>
|
||||
<p>CAA zur Autorisierung von Zertifizierungsstellen (Let's Encrypt) und DNSSEC Signierung (RRSIG, DNSKEY, DS).</p>
|
||||
<div class="code-container">
|
||||
<div class="code-header">
|
||||
<span>DNS Command & Syntax Reference</span>
|
||||
<span>Tool: delv</span>
|
||||
</div>
|
||||
<pre><code># Wichtige Befehle & Zonensyntax:
|
||||
dig delv
|
||||
named-checkzone example.com /etc/bind/zones/db.example.com</code></pre>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<h2>🎯 Aufgaben & Checkliste</h2>
|
||||
<div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t1">
|
||||
<label class="task-label" for="t1"><strong>TODO 1:</strong> CAA-Record für autorisierte CAs ('issue "letsencrypt.org"') anlegen</label>
|
||||
</div><div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t2">
|
||||
<label class="task-label" for="t2"><strong>TODO 2:</strong> DNSSEC Komponenten: Key Signing Key (KSK) & Zone Signing Key (ZSK) verstehen</label>
|
||||
</div><div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t3">
|
||||
<label class="task-label" for="t3"><strong>TODO 3:</strong> Delegation Signer (DS) Record für die übergeordnete TLD berechnen</label>
|
||||
</div><div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t4">
|
||||
<label class="task-label" for="t4"><strong>TODO 4:</strong> DNSSEC Vertrauenskette mit delv oder dig +dnssec validieren</label>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 25px; display: flex; gap: 12px; flex-wrap: wrap;">
|
||||
<a href="../../../workspace.html?course=dns_records&track=track_4_advanced_dns&chapter=14_caa_und_dnssec_sicherheit" class="btn" style="background: #0284c7; color: #fff; font-weight: 700;">💻 In Web-IDE öffnen →</a>
|
||||
<a href="../15_dns_troubleshooting_mit_dig_und_nslookup/index.html" class="btn btn-secondary">Weiter zu Modul →</a>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
<header>
|
||||
<div class="header-container">
|
||||
<div class="header-badge">Lehrpfad 4: Erweiterte Records, DNSSEC & Troubleshooting</div>
|
||||
<h1>Modul 14: CAA & DNSSEC Sicherheit (RFC 8659, RFC 4034)</h1>
|
||||
<p>Kryptografischer Schutz vor DNS-Hijacking und unautorisierten SSL/TLS-Zertifikaten.</p>
|
||||
<div class="nav-links">
|
||||
<a href="../13_srv_service_records_fuer_ad_und_sip/index.html">← Modul 13: SRV-Records</a>
|
||||
<a href="../../../dashboard.html">Kurs-Dashboard</a>
|
||||
<a href="../15_dns_troubleshooting_mit_dig_und_nslookup/index.html">Modul 15: Troubleshooting →</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="../../../assets/courses_manifest.js"></script>
|
||||
<script src="../../../assets/course_sidebar.js"></script>
|
||||
</header>
|
||||
|
||||
<main class="container">
|
||||
<div class="card">
|
||||
<h2>1. CAA: Wer darf Zertifikate für mich ausstellen?</h2>
|
||||
<p>CAs sind verpflichtet, vor Ausstellung eines Zertifikats die CAA-Records der Domain im DNS abzufragen. Ist dort <code>issue "letsencrypt.org"</code> definiert, weist DigiCert jeden Zertifikatsantrag für deine Domain automatisch ab.</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>2. DNSSEC: Digitale Signaturen für Record-Sets</h2>
|
||||
<div class="box box-analogie">
|
||||
<div class="box-title">🔏 Analogie: Der notariell beglaubigte Personalausweis</div>
|
||||
<p>Ein DNS-Record ohne DNSSEC ist wie ein handgeschriebener Zettel: Jeder kann ihn nachmachen. Mit DNSSEC ist jeder Record notariell versiegelt (RRSIG) und der Notar wird von der übergeordneten Behörde (Parent DS) beglaubigt.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>3. 🎯 Deine Teilziele in der Web-IDE</h2>
|
||||
<ul class="task-checklist">
|
||||
<li><strong>Teilziel 1:</strong> <code>format_caa_record</code> – CAA-Syntax nach RFC 8659 generieren.</li>
|
||||
<li><strong>Teilziel 2:</strong> <code>format_caa_wildcard_block</code> – Absicherung gegen Wildcard-Missbrauch.</li>
|
||||
<li><strong>Teilziel 3:</strong> <code>parse_ds_record</code> – Zerlegung des Parent Delegation Signer Records.</li>
|
||||
<li><strong>Teilziel 4:</strong> <code>verify_dnssec_chain_status</code> – Validierung der Trust Chain.</li>
|
||||
</ul>
|
||||
<div style="margin-top: 20px;">
|
||||
<a href="../../../workspace.html?course=dns_records&chapter=14_caa_und_dnssec_sicherheit" class="btn">🚀 Aufgabe in der Web-IDE bearbeiten</a>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
|
@ -1,97 +1,44 @@
|
|||
#!/usr/bin/env bash
|
||||
# ==============================================================================
|
||||
# MUSTERLÖSUNG: DNS 14: CAA Records & DNSSEC Vertrauenskette
|
||||
# Musterlösung Modul 14: CAA & DNSSEC Sicherheit
|
||||
# ==============================================================================
|
||||
|
||||
setup_caa_and_dnssec() {
|
||||
local domain="${1:-it-praxisportal.de}"
|
||||
local target_dir="${2:-.}"
|
||||
function format_caa_record() {
|
||||
local domain="$1"
|
||||
local tag="$2"
|
||||
local value="$3"
|
||||
local flags="${4:-0}"
|
||||
local ttl="${5:-3600}"
|
||||
|
||||
echo "▶ Starte professionelle DNS-Operation für $domain..."
|
||||
[[ "$domain" != *. ]] && domain="${domain}."
|
||||
|
||||
case "14_caa_und_dnssec_sicherheit" in
|
||||
*01_dns_hierarchie*)
|
||||
echo "Host: www | SLD: it-praxisportal | TLD: de | Root: ."
|
||||
;;
|
||||
*02_rekursive*)
|
||||
echo "Simulation Iteration: Root (.) -> de. -> ns1.it-praxisportal.de -> 188.245.100.5"
|
||||
;;
|
||||
*03_ports_udp*)
|
||||
echo "Protocol: UDP/53 (Standard) | Fallback: TCP/53 | EDNS0 Buffer: 4096 bytes"
|
||||
;;
|
||||
*04_ttl*)
|
||||
echo "TTL Current: 86400s | Migration Planned TTL: 300s | Neg Cache: 3600s"
|
||||
;;
|
||||
*05_a_und_aaaa*)
|
||||
cat << 'ZONE' > "$target_dir/zone_records.txt"
|
||||
@ IN A 188.245.100.5
|
||||
@ IN AAAA 2a01:4f8:c010:d::1
|
||||
www IN A 188.245.100.5
|
||||
ZONE
|
||||
;;
|
||||
*06_cname*)
|
||||
echo "api IN CNAME app.it-praxisportal.de." > "$target_dir/cname_record.txt"
|
||||
;;
|
||||
*07_soa*)
|
||||
cat << 'ZONE' > "$target_dir/soa_record.txt"
|
||||
@ IN SOA ns1.it-praxisportal.de. hostmaster.it-praxisportal.de. (
|
||||
2026091201 ; Serial YYYYMMDDNN
|
||||
7200 ; Refresh (2h)
|
||||
3600 ; Retry (1h)
|
||||
1209600 ; Expire (2w)
|
||||
3600 ; Negative Cache TTL (1h)
|
||||
)
|
||||
ZONE
|
||||
;;
|
||||
*08_ns_und_ptr*)
|
||||
echo "100.245.188.in-addr.arpa. IN PTR mail.it-praxisportal.de." > "$target_dir/ptr_record.txt"
|
||||
;;
|
||||
*09_mx*)
|
||||
echo "@ IN MX 10 mail.it-praxisportal.de." > "$target_dir/mx_record.txt"
|
||||
echo "@ IN MX 20 backup-mail.it-praxisportal.de." >> "$target_dir/mx_record.txt"
|
||||
;;
|
||||
*10_txt_records*)
|
||||
echo '@ IN TXT "v=spf1 mx ip4:188.245.100.5 include:_spf.google.com -all"' > "$target_dir/spf_record.txt"
|
||||
;;
|
||||
*11_dkim*)
|
||||
echo 's1._domainkey IN TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz..."' > "$target_dir/dkim_record.txt"
|
||||
;;
|
||||
*12_dmarc*)
|
||||
echo '_dmarc IN TXT "v=DMARC1; p=reject; rua=mailto:dmarc-reports@it-praxisportal.de; pct=100; aspf=s"' > "$target_dir/dmarc_record.txt"
|
||||
;;
|
||||
*13_srv*)
|
||||
echo '_ldap._tcp.dc._msdcs.corp IN SRV 0 100 389 dc01.corp.it-praxisportal.de.' > "$target_dir/srv_record.txt"
|
||||
;;
|
||||
*14_caa*)
|
||||
echo '@ IN CAA 0 issue "letsencrypt.org"' > "$target_dir/caa_record.txt"
|
||||
echo '@ IN CAA 0 iodef "mailto:security@it-praxisportal.de"' >> "$target_dir/caa_record.txt"
|
||||
;;
|
||||
*15_dns_troubleshooting*)
|
||||
echo "STATUS: NOERROR | ANSWER: 1 | FLAGS: qr aa rd ra"
|
||||
;;
|
||||
*16_master*)
|
||||
cat << 'REPORT' > "$target_dir/dns_audit_report.json"
|
||||
{
|
||||
"domain": "it-praxisportal.de",
|
||||
"audit_status": "PASSED",
|
||||
"score": 100,
|
||||
"soa_serial_valid": true,
|
||||
"ns_redundancy": 2,
|
||||
"email_security": {
|
||||
"spf": "valid (-all)",
|
||||
"dkim": "present",
|
||||
"dmarc": "enforced (p=reject)"
|
||||
},
|
||||
"dnssec": "active"
|
||||
}
|
||||
REPORT
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "✅ DNS-Operation erfolgreich abgeschlossen."
|
||||
return 0
|
||||
echo "${domain} ${ttl} IN CAA ${flags} ${tag} "${value}""
|
||||
}
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
setup_caa_and_dnssec "$@"
|
||||
fi
|
||||
function format_caa_wildcard_block() {
|
||||
local domain="$1"
|
||||
|
||||
format_caa_record "$domain" "issue" "letsencrypt.org" 0 3600
|
||||
format_caa_record "$domain" "issuewild" ";" 0 3600
|
||||
format_caa_record "$domain" "iodef" "mailto:security@${domain}" 0 3600
|
||||
}
|
||||
|
||||
function parse_ds_record() {
|
||||
local ds_line="$1"
|
||||
read -r domain ttl in_kw ds_kw key_tag algo digest_type hash <<< "$ds_line"
|
||||
|
||||
echo "KEY_TAG=${key_tag} ALGO=${algo} DIGEST_TYPE=${digest_type} HASH=${hash}"
|
||||
}
|
||||
|
||||
function verify_dnssec_chain_status() {
|
||||
local ksk_hash="$1"
|
||||
local ds_hash="$2"
|
||||
|
||||
if [ -n "$ksk_hash" ] && [ "$ksk_hash" = "$ds_hash" ]; then
|
||||
echo "DNSSEC_SECURE: Chain of trust verified"
|
||||
return 0
|
||||
else
|
||||
echo "BOGUS_CHAIN_BROKEN: Key tag or hash mismatch"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,36 +1,60 @@
|
|||
#!/usr/bin/env bash
|
||||
# ==============================================================================
|
||||
# TESTSUITE: DNS 14: CAA Records & DNSSEC Vertrauenskette
|
||||
# ==============================================================================
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
TARGET_SCRIPT="${1:-aufgabe.sh}"
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
TARGET_SCRIPT="${1:-$SCRIPT_DIR/aufgabe.sh}"
|
||||
source "$TARGET_SCRIPT"
|
||||
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
echo -e "${BLUE}🧪 TESTSUITE: DNS 14: CAA Records & DNSSEC Vertrauenskette${NC}"
|
||||
echo -e "${BLUE}================================================================${NC}"
|
||||
PASSED_TESTS=0
|
||||
TOTAL_TESTS=0
|
||||
|
||||
TEST_DIR=$(mktemp -d /tmp/dns_test_XXXXXX)
|
||||
trap 'rm -rf "$TEST_DIR"' EXIT
|
||||
run_test() {
|
||||
local desc="$1"
|
||||
local cmd="$2"
|
||||
((TOTAL_TESTS++))
|
||||
echo -e "
|
||||
▶ Test ${TOTAL_TESTS}: ${desc}"
|
||||
if eval "$cmd"; then
|
||||
echo -e " [32m✓ BESTANDEN[0m"
|
||||
((PASSED_TESTS++))
|
||||
else
|
||||
echo -e " [31m✗ FEHLGESCHLAGEN[0m
|
||||
"
|
||||
fi
|
||||
}
|
||||
|
||||
source "$SCRIPT_DIR/$TARGET_SCRIPT"
|
||||
run_test "format_caa_record erzeugt validen CAA-Eintrag" '
|
||||
OUT=$(format_caa_record "firma.de" "issue" "letsencrypt.org" 0 3600)
|
||||
echo "$OUT" | grep -q "firma.de." && \
|
||||
echo "$OUT" | grep -q "CAA 0 issue" && \
|
||||
echo "$OUT" | grep -q "letsencrypt.org"
|
||||
'
|
||||
|
||||
# Führe Funktion aus
|
||||
setup_caa_and_dnssec "example.com" "$TEST_DIR"
|
||||
RET=$?
|
||||
run_test "format_caa_wildcard_block sperrt Wildcard-Zertifikate" '
|
||||
OUT=$(format_caa_wildcard_block "it-praxisportal.de")
|
||||
echo "$OUT" | grep -q "issue" && \
|
||||
echo "$OUT" | grep -q "issuewild" && \
|
||||
echo "$OUT" | grep -q "iodef"
|
||||
'
|
||||
|
||||
if [ $RET -eq 0 ]; then
|
||||
echo -e "${GREEN}✓ Test 1: Funktion setup_caa_and_dnssec existiert und gibt 0 zurück.${NC}"
|
||||
echo -e "${GREEN}✓ Test 2: DNS Resource Records / Syntax validiert.${NC}"
|
||||
echo -e "${GREEN}✓ Test 3: Teilziele 1 bis 4 erfolgreich abgeschlossen.${NC}"
|
||||
echo -e "${GREEN}🎉 ERFOLG: Alle DNS-Prüfungen in DNS 14: CAA Records & DNSSEC Vertrauenskette bestanden!${NC}"
|
||||
run_test "parse_ds_record extrahiert DNSSEC Parameter" '
|
||||
LINE="meinedomain.de. 3600 IN DS 19842 13 2 AABBCCDDEEFF0011223344"
|
||||
OUT=$(parse_ds_record "$LINE")
|
||||
echo "$OUT" | grep -q "KEY_TAG=19842" && echo "$OUT" | grep -q "ALGO=13" && echo "$OUT" | grep -q "HASH=AABBCCDDEEFF0011223344"
|
||||
'
|
||||
|
||||
run_test "verify_dnssec_chain_status meldet SECURE bei Übereinstimmung" '
|
||||
verify_dnssec_chain_status "HASH123" "HASH123" | grep -q "DNSSEC_SECURE"
|
||||
'
|
||||
|
||||
run_test "verify_dnssec_chain_status meldet BOGUS bei Abweichung" '
|
||||
verify_dnssec_chain_status "HASH123" "HASH999" | grep -q "BOGUS_CHAIN_BROKEN"
|
||||
'
|
||||
|
||||
if [ "$PASSED_TESTS" -eq "$TOTAL_TESTS" ]; then
|
||||
echo -e "
|
||||
[32m🎉 ERFOLG: Alle ${PASSED_TESTS}/${TOTAL_TESTS} Tests in DNS 14 bestanden![0m"
|
||||
exit 0
|
||||
else
|
||||
echo -e "${RED}❌ FEHLER: Skript fehlgeschlagen mit Status $RET${NC}"
|
||||
echo -e "
|
||||
[31m❌ FEHLER: ${PASSED_TESTS}/${TOTAL_TESTS} Tests bestanden.[0m"
|
||||
exit 1
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -1,18 +1,49 @@
|
|||
# DNS 15: DNS-Troubleshooting mit dig & nslookup
|
||||
# Modul 15: DNS-Troubleshooting mit dig & nslookup
|
||||
|
||||
Willkommen zum Kurs **DNS-Records, Domain Name System & E-Mail-Sicherheit** im IT-Praxisportal!
|
||||
## 📌 Didaktische Einführung & Relevanz für IT-Systemintegration
|
||||
"It's not DNS. There's no way it's DNS. It was DNS." – Dieser alte Sysadmin-Witz spiegelt den Berufsalltag wider: Fehler in der DNS-Konfiguration führen zu nicht erreichbaren Websites, verworfenen E-Mails und scheiternden Logins.
|
||||
|
||||
## 💡 Das Wichtigste in Kürze
|
||||
- **Ziel:** Netzwerkdiagnose im Terminal: dig +trace, +short, Reverse Lookups, Statuscodes (NOERROR, NXDOMAIN, SERVFAIL) & AXFR Leak-Testing.
|
||||
- **Wichtigstes Tool:** `dig`
|
||||
Professionelle Fachinformatiker nutzen das Tool `dig` (Domain Information Groper), um DNS-Probleme strukturiert einzugrenzen.
|
||||
|
||||
## 🎯 Deine Aufgaben (Checkliste)
|
||||
1. **Teilziel 1:** Rückgabecodes (NOERROR, NXDOMAIN, SERVFAIL, REFUSED) unterscheiden
|
||||
2. **Teilziel 2:** Gezielte Abfrage an spezifischen DNS-Server (dig @1.1.1.1)
|
||||
3. **Teilziel 3:** Offenen Zonentransfer (AXFR) als Sicherheitslücke testen
|
||||
4. **Teilziel 4:** Automatisches Diagnose-Skript für Domain-Health schreiben
|
||||
---
|
||||
|
||||
## 🔍 Wichtige DNS-RCODEs (Response Codes)
|
||||
|
||||
1. **`NOERROR` (Code 0)**: Anfrage war erfolgreich. Wenn die ANSWER SECTION leer ist, existiert der Name zwar, aber nicht der angefragte Typ (NODATA).
|
||||
2. **`NXDOMAIN` (Code 3 - Non-Existent Domain)**: Der abgefragte FQDN existiert nicht im Namensraum.
|
||||
3. **`SERVFAIL` (Code 2 - Server Failure)**: Der Nameserver konnte die Anfrage nicht auflösen (häufig bei DNSSEC Validierungsfehlern, Netzwerk-Timeouts oder Zonenkonfigurationsfehlern).
|
||||
4. **`REFUSED` (Code 5)**: Der Server lehnt die Antwort ab (z. B. keine Rekursion für externe Clients erlaubt oder Zonentransfer gesperrt).
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ Wichtige dig-Befehle im Arsenal
|
||||
|
||||
## 🧪 Tests ausführen
|
||||
```bash
|
||||
bash test_aufgabe.sh
|
||||
# Kurzantwort für Skripte
|
||||
dig +short www.heise.de A
|
||||
|
||||
# Schrittweise Verfolgung vom Root bis zum Autoritativen Server
|
||||
dig +trace www.heise.de
|
||||
|
||||
# Gezielte Abfrage eines bestimmten DNS-Resolvers
|
||||
dig @1.1.1.1 google.com MX
|
||||
|
||||
# Zonentransfer-Test (Sicherheitsüberprüfung)
|
||||
dig @ns1.firma.de firma.de AXFR
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Deine Aufgaben im Modul 15:
|
||||
|
||||
1. **`diagnose_dns_rcode`**:
|
||||
Interpretiert den RCODE aus einer dig-Antwort und gibt präzise Fehlerbeschreibungen und Sysadmin-Handlungsempfehlungen aus.
|
||||
|
||||
2. **`extract_dig_ips`**:
|
||||
Filtert aus einer rohen dig-Ausgabe alle gültigen IPv4-Adressen der ANSWER SECTION heraus.
|
||||
|
||||
3. **`detect_soa_desync`**:
|
||||
Vergleicht die SOA-Serial eines Primary Masters mit einem Secondary Slave Server. Wenn die Serial abweicht, wird `DESYNC: Slave out of date` gemeldet.
|
||||
|
||||
4. **`verify_axfr_blocked`**:
|
||||
Prüft, ob ein Zonentransfer (AXFR) ordnungsgemäß abgewiesen wird (`Transfer failed` oder `REFUSED`).
|
||||
|
|
|
|||
|
|
@ -1,21 +1,46 @@
|
|||
#!/usr/bin/env bash
|
||||
# ==============================================================================
|
||||
# 🚀 Diagnose & Analyse - DNS 15: DNS-Troubleshooting mit dig & nslookup
|
||||
# Modul 15: DNS-Troubleshooting mit dig & nslookup
|
||||
# ==============================================================================
|
||||
|
||||
# 🎯 TEILZIEL 1: Rückgabecodes (NOERROR, NXDOMAIN, SERVFAIL, REFUSED) unterscheiden
|
||||
# 🎯 TEILZIEL 2: Gezielte Abfrage an spezifischen DNS-Server (dig @1.1.1.1)
|
||||
# 🎯 TEILZIEL 3: Offenen Zonentransfer (AXFR) als Sicherheitslücke testen
|
||||
# 🎯 TEILZIEL 4: Automatisches Diagnose-Skript für Domain-Health schreiben
|
||||
# 🎯 TEILZIEL 1: DNS RCODE diagnostizieren
|
||||
# Nimmt einen Statuscode (z. B. "NOERROR", "NXDOMAIN", "SERVFAIL", "REFUSED")
|
||||
# und gibt die Bedeutung und Handlungsempfehlung zurück
|
||||
function diagnose_dns_rcode() {
|
||||
local rcode="$1"
|
||||
|
||||
diagnose_dns_health() {
|
||||
local domain="${1:-it-praxisportal.de}"
|
||||
echo "▶ Analysiere DNS-Konfiguration für $domain..."
|
||||
|
||||
# TODO: Implementiere die geforderten DNS-Prüfungen oder Record-Erstellungen
|
||||
return 1
|
||||
# TODO: Werte RCODE aus und liefere verständliche Diagnose
|
||||
return 1
|
||||
}
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
diagnose_dns_health "$@"
|
||||
fi
|
||||
# 🎯 TEILZIEL 2: IPv4-Adressen aus dig ANSWER SECTION extrahieren
|
||||
# Nimmt Text einer dig-Ausgabe entgegen und gibt alle A-Record IPs zeilenweise aus
|
||||
function extract_dig_ips() {
|
||||
local dig_output="$1"
|
||||
|
||||
# TODO: Filtere IPv4-Adressen aus der Antwort
|
||||
return 1
|
||||
}
|
||||
|
||||
# 🎯 TEILZIEL 3: SOA Master/Slave Desynchronisation erkennen
|
||||
# Vergleicht Primary Serial mit Secondary Serial
|
||||
# Wenn gleich: "IN_SYNC: Serials match (YYYYMMDDNN)"
|
||||
# Wenn ungleich: "DESYNC: Slave lag detected (Primary: X, Slave: Y)"
|
||||
function detect_soa_desync() {
|
||||
local primary_serial="$1"
|
||||
local secondary_serial="$2"
|
||||
|
||||
# TODO: Vergleiche Serials
|
||||
return 1
|
||||
}
|
||||
|
||||
# 🎯 TEILZIEL 4: Zonentransfer (AXFR) Sperre prüfen
|
||||
# Prüft, ob AXFR erfolgreich war (Sicherheitsrisiko) oder abgewiesen wurde
|
||||
# Wenn abgewiesen: "SECURE: AXFR transfer blocked"
|
||||
# Wenn Zoneninhalte sichtbar: "CRITICAL_LEAK: AXFR open to public"
|
||||
function verify_axfr_blocked() {
|
||||
local axfr_output="$1"
|
||||
|
||||
# TODO: Prüfe auf Transfer-Blockade
|
||||
return 1
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,63 +1,43 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<link rel="icon" type="image/svg+xml" href="../../../assets/favicon.svg">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>DNS 15: DNS-Troubleshooting mit dig & nslookup</title>
|
||||
<title>Modul 15: DNS-Troubleshooting mit dig & nslookup</title>
|
||||
<link rel="stylesheet" href="../../../assets/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-layout">
|
||||
<div class="app-main">
|
||||
<header>
|
||||
<div class="header-container">
|
||||
<div class="header-badge">🚀 Diagnose & Analyse</div>
|
||||
<h1>DNS 15: DNS-Troubleshooting mit dig & nslookup</h1>
|
||||
<p>Netzwerkdiagnose im Terminal: dig +trace, +short, Reverse Lookups, Statuscodes (NOERROR, NXDOMAIN, SERVFAIL) & AXFR Leak-Testing.</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="container">
|
||||
<section class="card">
|
||||
<h2>💡 Theorie & Praxiskonzept</h2>
|
||||
<p>Netzwerkdiagnose im Terminal: dig +trace, +short, Reverse Lookups, Statuscodes (NOERROR, NXDOMAIN, SERVFAIL) & AXFR Leak-Testing.</p>
|
||||
<div class="code-container">
|
||||
<div class="code-header">
|
||||
<span>DNS Command & Syntax Reference</span>
|
||||
<span>Tool: dig</span>
|
||||
</div>
|
||||
<pre><code># Wichtige Befehle & Zonensyntax:
|
||||
dig dig
|
||||
named-checkzone example.com /etc/bind/zones/db.example.com</code></pre>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<h2>🎯 Aufgaben & Checkliste</h2>
|
||||
<div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t1">
|
||||
<label class="task-label" for="t1"><strong>TODO 1:</strong> Rückgabecodes (NOERROR, NXDOMAIN, SERVFAIL, REFUSED) unterscheiden</label>
|
||||
</div><div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t2">
|
||||
<label class="task-label" for="t2"><strong>TODO 2:</strong> Gezielte Abfrage an spezifischen DNS-Server (dig @1.1.1.1)</label>
|
||||
</div><div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t3">
|
||||
<label class="task-label" for="t3"><strong>TODO 3:</strong> Offenen Zonentransfer (AXFR) als Sicherheitslücke testen</label>
|
||||
</div><div class="task-item">
|
||||
<input type="checkbox" class="task-checkbox" id="t4">
|
||||
<label class="task-label" for="t4"><strong>TODO 4:</strong> Automatisches Diagnose-Skript für Domain-Health schreiben</label>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 25px; display: flex; gap: 12px; flex-wrap: wrap;">
|
||||
<a href="../../../workspace.html?course=dns_records&track=track_4_advanced_dns&chapter=15_dns_troubleshooting_mit_dig_und_nslookup" class="btn" style="background: #0284c7; color: #fff; font-weight: 700;">💻 In Web-IDE öffnen →</a>
|
||||
<a href="../16_master_abschlussprojekt_dns_zone_auditor/index.html" class="btn btn-secondary">Weiter zu Modul →</a>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
<header>
|
||||
<div class="header-container">
|
||||
<div class="header-badge">Lehrpfad 4: Erweiterte Records, DNSSEC & Troubleshooting</div>
|
||||
<h1>Modul 15: DNS-Troubleshooting mit dig & nslookup</h1>
|
||||
<p>Systematische Fehlersuche im Terminal: RCODEs analysieren, Caching-Probleme identifizieren und Zonen-Synchronisation prüfen.</p>
|
||||
<div class="nav-links">
|
||||
<a href="../14_caa_und_dnssec_sicherheit/index.html">← Modul 14: CAA & DNSSEC</a>
|
||||
<a href="../../../dashboard.html">Kurs-Dashboard</a>
|
||||
<a href="../16_master_abschlussprojekt_dns_zone_auditor/index.html">Modul 16: Master-Projekt →</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="../../../assets/courses_manifest.js"></script>
|
||||
<script src="../../../assets/course_sidebar.js"></script>
|
||||
</header>
|
||||
|
||||
<main class="container">
|
||||
<div class="card">
|
||||
<h2>1. RCODEs verstehen: Was sagt der Server wirklich?</h2>
|
||||
<p>Wenn ein Ping fehlschlägt, liefert <code>dig</code> die exakte Diagnose. Ob <code>NXDOMAIN</code> oder <code>SERVFAIL</code> macht den Unterschied zwischen einem Tippfehler im Hostnamen und einem kollabierten DNSSEC-Zertifikat.</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>2. 🎯 Deine Teilziele in der Web-IDE</h2>
|
||||
<ul class="task-checklist">
|
||||
<li><strong>Teilziel 1:</strong> <code>diagnose_dns_rcode</code> – RCODE-Interpretation & Handlungsanweisungen.</li>
|
||||
<li><strong>Teilziel 2:</strong> <code>extract_dig_ips</code> – Präzise IP-Filterung aus Dig Answer Sections.</li>
|
||||
<li><strong>Teilziel 3:</strong> <code>detect_soa_desync</code> – Erkennung von Replikationsverzögerungen zwischen Nameservern.</li>
|
||||
<li><strong>Teilziel 4:</strong> <code>verify_axfr_blocked</code> – Sicherheits-Audit gegen unerlaubten Zonentransfer.</li>
|
||||
</ul>
|
||||
<div style="margin-top: 20px;">
|
||||
<a href="../../../workspace.html?course=dns_records&chapter=15_dns_troubleshooting_mit_dig_und_nslookup" class="btn">🚀 Aufgabe in der Web-IDE bearbeiten</a>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
|
@ -1,97 +1,55 @@
|
|||
#!/usr/bin/env bash
|
||||
# ==============================================================================
|
||||
# MUSTERLÖSUNG: DNS 15: DNS-Troubleshooting mit dig & nslookup
|
||||
# Musterlösung Modul 15: DNS-Troubleshooting
|
||||
# ==============================================================================
|
||||
|
||||
diagnose_dns_health() {
|
||||
local domain="${1:-it-praxisportal.de}"
|
||||
local target_dir="${2:-.}"
|
||||
|
||||
echo "▶ Starte professionelle DNS-Operation für $domain..."
|
||||
|
||||
case "15_dns_troubleshooting_mit_dig_und_nslookup" in
|
||||
*01_dns_hierarchie*)
|
||||
echo "Host: www | SLD: it-praxisportal | TLD: de | Root: ."
|
||||
;;
|
||||
*02_rekursive*)
|
||||
echo "Simulation Iteration: Root (.) -> de. -> ns1.it-praxisportal.de -> 188.245.100.5"
|
||||
;;
|
||||
*03_ports_udp*)
|
||||
echo "Protocol: UDP/53 (Standard) | Fallback: TCP/53 | EDNS0 Buffer: 4096 bytes"
|
||||
;;
|
||||
*04_ttl*)
|
||||
echo "TTL Current: 86400s | Migration Planned TTL: 300s | Neg Cache: 3600s"
|
||||
;;
|
||||
*05_a_und_aaaa*)
|
||||
cat << 'ZONE' > "$target_dir/zone_records.txt"
|
||||
@ IN A 188.245.100.5
|
||||
@ IN AAAA 2a01:4f8:c010:d::1
|
||||
www IN A 188.245.100.5
|
||||
ZONE
|
||||
;;
|
||||
*06_cname*)
|
||||
echo "api IN CNAME app.it-praxisportal.de." > "$target_dir/cname_record.txt"
|
||||
;;
|
||||
*07_soa*)
|
||||
cat << 'ZONE' > "$target_dir/soa_record.txt"
|
||||
@ IN SOA ns1.it-praxisportal.de. hostmaster.it-praxisportal.de. (
|
||||
2026091201 ; Serial YYYYMMDDNN
|
||||
7200 ; Refresh (2h)
|
||||
3600 ; Retry (1h)
|
||||
1209600 ; Expire (2w)
|
||||
3600 ; Negative Cache TTL (1h)
|
||||
)
|
||||
ZONE
|
||||
;;
|
||||
*08_ns_und_ptr*)
|
||||
echo "100.245.188.in-addr.arpa. IN PTR mail.it-praxisportal.de." > "$target_dir/ptr_record.txt"
|
||||
;;
|
||||
*09_mx*)
|
||||
echo "@ IN MX 10 mail.it-praxisportal.de." > "$target_dir/mx_record.txt"
|
||||
echo "@ IN MX 20 backup-mail.it-praxisportal.de." >> "$target_dir/mx_record.txt"
|
||||
;;
|
||||
*10_txt_records*)
|
||||
echo '@ IN TXT "v=spf1 mx ip4:188.245.100.5 include:_spf.google.com -all"' > "$target_dir/spf_record.txt"
|
||||
;;
|
||||
*11_dkim*)
|
||||
echo 's1._domainkey IN TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz..."' > "$target_dir/dkim_record.txt"
|
||||
;;
|
||||
*12_dmarc*)
|
||||
echo '_dmarc IN TXT "v=DMARC1; p=reject; rua=mailto:dmarc-reports@it-praxisportal.de; pct=100; aspf=s"' > "$target_dir/dmarc_record.txt"
|
||||
;;
|
||||
*13_srv*)
|
||||
echo '_ldap._tcp.dc._msdcs.corp IN SRV 0 100 389 dc01.corp.it-praxisportal.de.' > "$target_dir/srv_record.txt"
|
||||
;;
|
||||
*14_caa*)
|
||||
echo '@ IN CAA 0 issue "letsencrypt.org"' > "$target_dir/caa_record.txt"
|
||||
echo '@ IN CAA 0 iodef "mailto:security@it-praxisportal.de"' >> "$target_dir/caa_record.txt"
|
||||
;;
|
||||
*15_dns_troubleshooting*)
|
||||
echo "STATUS: NOERROR | ANSWER: 1 | FLAGS: qr aa rd ra"
|
||||
;;
|
||||
*16_master*)
|
||||
cat << 'REPORT' > "$target_dir/dns_audit_report.json"
|
||||
{
|
||||
"domain": "it-praxisportal.de",
|
||||
"audit_status": "PASSED",
|
||||
"score": 100,
|
||||
"soa_serial_valid": true,
|
||||
"ns_redundancy": 2,
|
||||
"email_security": {
|
||||
"spf": "valid (-all)",
|
||||
"dkim": "present",
|
||||
"dmarc": "enforced (p=reject)"
|
||||
},
|
||||
"dnssec": "active"
|
||||
}
|
||||
REPORT
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "✅ DNS-Operation erfolgreich abgeschlossen."
|
||||
return 0
|
||||
function diagnose_dns_rcode() {
|
||||
local rcode="$1"
|
||||
case "$rcode" in
|
||||
"NOERROR")
|
||||
echo "OK: Query successful. Records returned or NODATA."
|
||||
;;
|
||||
"NXDOMAIN")
|
||||
echo "ERROR_NXDOMAIN: Domain or hostname does not exist. Check spelling or registrar status."
|
||||
;;
|
||||
"SERVFAIL")
|
||||
echo "ERROR_SERVFAIL: Nameserver failed to answer. Check DNSSEC validation or authoritative connectivity."
|
||||
;;
|
||||
"REFUSED")
|
||||
echo "ERROR_REFUSED: Nameserver refused query. Check zone ACLs and recursion permissions."
|
||||
;;
|
||||
*)
|
||||
echo "UNKNOWN_RCODE: ${rcode}"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
diagnose_dns_health "$@"
|
||||
fi
|
||||
function extract_dig_ips() {
|
||||
local dig_output="$1"
|
||||
echo "$dig_output" | grep -E -o "([0-9]{1,3}\.){3}[0-9]{1,3}" | sort -u
|
||||
}
|
||||
|
||||
function detect_soa_desync() {
|
||||
local primary_serial="$1"
|
||||
local secondary_serial="$2"
|
||||
|
||||
if [ "$primary_serial" -eq "$secondary_serial" ]; then
|
||||
echo "IN_SYNC: Serials match (${primary_serial})"
|
||||
return 0
|
||||
else
|
||||
echo "DESYNC: Slave lag detected (Primary: ${primary_serial}, Slave: ${secondary_serial})"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
function verify_axfr_blocked() {
|
||||
local axfr_output="$1"
|
||||
|
||||
if echo "$axfr_output" | grep -qiE "(Transfer failed|REFUSED|connection refused)"; then
|
||||
echo "SECURE: AXFR transfer blocked"
|
||||
return 0
|
||||
else
|
||||
echo "CRITICAL_LEAK: AXFR open to public"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue