From 55ae83dc3294db5ca0288161fde5a533bc0b6870 Mon Sep 17 00:00:00 2001 From: Alex Mueller Date: Tue, 15 Sep 2026 13:46:05 +0200 Subject: [PATCH] feat(diagrams): add ER, Struktogramm (DIN 66261) and UML editors, revise IHK exam simulation questions and enhance typography contrast --- assets/ihk_tools/diagram_studio.css | 472 ++++++++++++ assets/ihk_tools/diagram_studio.js | 63 ++ assets/ihk_tools/er_editor.js | 683 ++++++++++++++++++ assets/ihk_tools/exam_engine.js | 18 +- assets/ihk_tools/ihk_question_bank.js | 202 +++++- assets/ihk_tools/ihk_question_bank.json | 195 +++++ assets/ihk_tools/struktogramm_editor.js | 611 ++++++++++++++++ assets/ihk_tools/uml_editor.js | 346 +++++++++ assets/style.css | 22 +- .../README.md | 66 +- .../aufgabe.sh | 130 +++- .../index.html | 123 +++- .../musterloesung.sh | 214 +++--- .../test_aufgabe.sh | 98 ++- .../README.md | 78 +- .../aufgabe.sh | 125 +++- .../index.html | 113 ++- .../musterloesung.sh | 164 ++--- .../test_aufgabe.sh | 93 ++- .../README.md | 42 +- .../aufgabe.sh | 120 ++- .../index.html | 103 ++- .../musterloesung.sh | 162 ++--- .../test_aufgabe.sh | 97 ++- .../04_ttl_und_dns_propagation/README.md | 47 +- .../04_ttl_und_dns_propagation/aufgabe.sh | 117 ++- .../04_ttl_und_dns_propagation/index.html | 110 ++- .../musterloesung.sh | 162 ++--- .../test_aufgabe.sh | 84 ++- .../05_a_und_aaaa_records/README.md | 47 +- .../05_a_und_aaaa_records/aufgabe.sh | 101 ++- .../05_a_und_aaaa_records/index.html | 100 ++- .../05_a_und_aaaa_records/musterloesung.sh | 157 ++-- .../05_a_und_aaaa_records/test_aufgabe.sh | 107 ++- .../README.md | 52 +- .../aufgabe.sh | 102 ++- .../index.html | 104 ++- .../musterloesung.sh | 169 +++-- .../test_aufgabe.sh | 117 ++- .../README.md | 49 +- .../aufgabe.sh | 109 ++- .../index.html | 104 ++- .../musterloesung.sh | 159 ++-- .../test_aufgabe.sh | 81 ++- .../README.md | 50 +- .../aufgabe.sh | 107 ++- .../index.html | 94 ++- .../musterloesung.sh | 138 ++-- .../test_aufgabe.sh | 82 ++- .../README.md | 45 +- .../aufgabe.sh | 98 ++- .../index.html | 94 ++- .../musterloesung.sh | 142 ++-- .../test_aufgabe.sh | 94 ++- .../README.md | 52 +- .../aufgabe.sh | 121 +++- .../index.html | 106 ++- .../musterloesung.sh | 163 ++--- .../test_aufgabe.sh | 86 ++- .../README.md | 44 +- .../aufgabe.sh | 103 ++- .../index.html | 92 ++- .../musterloesung.sh | 146 ++-- .../test_aufgabe.sh | 83 ++- .../README.md | 37 +- .../aufgabe.sh | 109 ++- .../index.html | 98 ++- .../musterloesung.sh | 157 ++-- .../test_aufgabe.sh | 91 ++- .../README.md | 59 +- .../aufgabe.sh | 60 +- .../index.html | 101 ++- .../musterloesung.sh | 142 ++-- .../test_aufgabe.sh | 73 +- .../14_caa_und_dnssec_sicherheit/README.md | 69 +- .../14_caa_und_dnssec_sicherheit/aufgabe.sh | 57 +- .../14_caa_und_dnssec_sicherheit/index.html | 94 ++- .../musterloesung.sh | 125 +--- .../test_aufgabe.sh | 74 +- .../README.md | 55 +- .../aufgabe.sh | 53 +- .../index.html | 86 +-- .../musterloesung.sh | 140 ++-- .../test_aufgabe.sh | 74 +- .../README.md | 35 +- .../aufgabe.sh | 62 +- .../index.html | 86 +-- .../musterloesung.sh | 170 ++--- .../test_aufgabe.sh | 94 ++- .../index.html | 12 + .../index.html | 12 + .../index.html | 12 + dashboard.html | 23 +- diagram_editor.html | 87 +++ index.html | 2 + workspace.html | 3 +- 96 files changed, 8186 insertions(+), 2524 deletions(-) create mode 100644 assets/ihk_tools/diagram_studio.css create mode 100644 assets/ihk_tools/diagram_studio.js create mode 100644 assets/ihk_tools/er_editor.js create mode 100644 assets/ihk_tools/struktogramm_editor.js create mode 100644 assets/ihk_tools/uml_editor.js create mode 100644 diagram_editor.html diff --git a/assets/ihk_tools/diagram_studio.css b/assets/ihk_tools/diagram_studio.css new file mode 100644 index 0000000..d4d9849 --- /dev/null +++ b/assets/ihk_tools/diagram_studio.css @@ -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; +} diff --git a/assets/ihk_tools/diagram_studio.js b/assets/ihk_tools/diagram_studio.js new file mode 100644 index 0000000..a51faf1 --- /dev/null +++ b/assets/ihk_tools/diagram_studio.js @@ -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'); + } +}); diff --git a/assets/ihk_tools/er_editor.js b/assets/ihk_tools/er_editor.js new file mode 100644 index 0000000..46cad61 --- /dev/null +++ b/assets/ihk_tools/er_editor.js @@ -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 = ` +
+
+
+ + +
+ + +
+
+ + +
+
+
+ + + +
+
+ +
+
+ +
+
+
+

Eigenschaften

+
+
+

Wähle eine Entität oder Beziehung im Diagramm aus, um Eigenschaften und Attribute zu bearbeiten.

+
+
+
+ + + +
+ `; + + 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 = ` + + + + + + + + `; + + // 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 += ``; + + 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 += ` + + + ${rel.name} + + + ${cardSrcText} + + + ${cardTgtText} + `; + }); + + // 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 += ` + + + + + 🗄️ ${ent.name} + `; + + 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 += ` + + ${keyBadge ? `${keyBadge}` : ''} + ${attr.name} + ${attr.type} + + `; + }); + + html += ``; + }); + + 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 = `

Klicke auf eine Entität im Diagramm, um Eigenschaften und Attribute zu bearbeiten.

`; + return; + } + + sidebarTitle.innerText = `Entität: ${ent.name}`; + + let html = ` +
+ + +
+ +
+ Attribute / Spalten: + +
+ +
+ `; + + ent.attributes.forEach((attr, idx) => { + html += ` +
+
+ + + +
+
+ + + +
+
+ `; + }); + + html += ` +
+
+ +
+ `; + + 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; diff --git a/assets/ihk_tools/exam_engine.js b/assets/ihk_tools/exam_engine.js index 2e3882f..6c5e268 100644 --- a/assets/ihk_tools/exam_engine.js +++ b/assets/ihk_tools/exam_engine.js @@ -153,9 +153,12 @@ class IhkExamEngine { -
+
+ + 📐 Diagramm-Studio (ER / SG / UML) +
`; @@ -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 = ``; + } else if (topicLow.includes('struktogramm') || topicLow.includes('nassi') || topicLow.includes('din 66261')) { + diagramLinkHtml = ``; + } else if (topicLow.includes('uml') || topicLow.includes('klassendiagramm') || topicLow.includes('sequenzdiagramm') || topicLow.includes('use case')) { + diagramLinkHtml = ``; + } + task.innerHTML = `

@@ -370,6 +383,7 @@ class IhkExamEngine {

${this.formatQuestionText(q.question)}

+ ${diagramLinkHtml}

diff --git a/assets/ihk_tools/ihk_question_bank.js b/assets/ihk_tools/ihk_question_bank.js index cc962f5..4a9f02e 100644 --- a/assets/ihk_tools/ihk_question_bank.js +++ b/assets/ihk_tools/ihk_question_bank.js @@ -1,7 +1,4 @@ -/** - * Offizieller IHK Prüfungskatalog ZPA Nord-West 2. Auflage - */ -window.IHK_QUESTION_BANK = [ +/**\n * Offizieller IHK Prüfungskatalog ZPA Nord-West 2. Auflage mit Diagramm-Studio Erweiterung\n */\nwindow.IHK_QUESTION_BANK = [ { "id": "ap1_lf1_001", "exam": "AP1", @@ -2571,5 +2568,200 @@ window.IHK_QUESTION_BANK = [ "scenario": "Ein Einzelhändler plant den Einsatz von KI-gestützter Videoanalyse und Gesichtserkennung in seinen Filialen, um personalisierte Werbung auf Kundenmonitoren auszuspielen.", "question": "a) Prüfen Sie, ob für dieses Vorhaben eine Datenschutz-Folgenabschätzung (DSFA nach Art. 35 DSGVO) zwingend erforderlich ist, und begründen Sie Ihre Entscheidung.\nb) In welche Risikoklasse nach dem EU AI Act (KI-Verordnung) fällt biometrische Identifikation und Emotionserkennung im öffentlichen Raum?\nc) Was versteht man im Rahmen von Analytics unter dem Konzept 'k-Anonymität'?", "expected_solution": "a) Ja, zwingend erforderlich. Gemäß Art. 35 Abs. 3 DSGVO ist eine DSFA vorgeschrieben bei systematischer und umfassender Überwachung öffentlich zugänglicher Bereiche sowie beim Einsatz neuer Technologien mit voraussichtlich hohem Risiko für die Rechte und Freiheiten natürlicher Personen.\nb) Hochrisiko-KI-System (bzw. in Teilen unzulässige/verbotene KI-Praxis bei biometrischer Echtzeit-Fernidentifikation ohne strenge richterliche Ausnahme).\nc) Ein Datensatz erfüllt k-Anonymität, wenn die quasi-identifizierenden Merkmale (z.B. PLZ, Alter, Geschlecht) jedes Individuums nicht von mindestens k-1 anderen Personen im Datensatz unterschieden werden können (Gruppengröße >= k)." + }, + { + "id": "ap1_erm_001", + "exam": "AP1", + "lernfeld": "LF 5", + "topic": "ER-Modellierung: Kardinalitäten und Notationen", + "scenario": "Für eine Autovermietung soll eine relationale Datenbank entworfen werden. Ein Kunde kann mehrere Mietverträge abschließen, aber jeder Mietvertrag gehört genau zu einem Kunden. Zu jedem Mietvertrag gehört genau ein Fahrzeug, jedoch kann ein Fahrzeug im Laufe der Zeit in mehreren Mietverträgen gebucht werden.", + "question": "a) Bestimmen Sie die Kardinalitäten zwischen den Entitäten KUNDE, MIETVERTRAG und FAHRZEUG in der Chen-Notation sowie in der Min-Max-Notation.\nb) Begründen Sie, warum zwischen KUNDE und FAHRZEUG keine direkte n:m-Beziehung modelliert werden sollte.", + "points": 8, + "solution": "a) Kardinalitäten:\n- KUNDE zu MIETVERTRAG: 1 : n (Chen) bzw. KUNDE (0,n) und MIETVERTRAG (1,1) (Min-Max). Ein Kunde kann 0 bis beliebig viele Verträge haben; ein Vertrag muss genau einem Kunden gehören. (2 Pkt.)\n- FAHRZEUG zu MIETVERTRAG: 1 : n (Chen) bzw. FAHRZEUG (0,n) und MIETVERTRAG (1,1) (Min-Max). Ein Fahrzeug kann 0 bis viele Verträge haben; ein Vertrag bezieht sich auf genau 1 Fahrzeug. (2 Pkt.)\nb) Begründung:\nEine direkte n:m-Beziehung KUNDE - FAHRZEUG könnte keine historischen Buchungsdaten (Mietzeitraum von/bis, Kilometerstand, Rechnungsbetrag) speichern, ohne Attribute an der Beziehung zu verdoppeln. Die Entität MIETVERTRAG fungiert als zentrale Geschäftseinheit und löst die n:m-Beziehung sauber in zwei 1:n-Beziehungen auf. (4 Pkt.)", + "type": "text", + "options": [] + }, + { + "id": "ap1_erm_002", + "exam": "AP1", + "lernfeld": "LF 5", + "topic": "Datenbank-Normalisierung 1NF bis 3NF", + "scenario": "Gegeben ist folgende nicht-normalisierte Tabelle 'tbl_projekte':\n[ProjektID, Projektname, MitarbeiterListe ('Müller, Schmidt, Meier'), Abteilung, Abteilungsleiter, Stundensatz].", + "question": "Erläutern Sie die Anforderungen der 1., 2. und 3. Normalform anhand dieses Beispiels und führen Sie die erforderlichen Zerlegungsschritte durch.", + "points": 10, + "solution": "1. Normalform (1NF):\n- Anforderung: Alle Attribute müssen atomar sein (keine Wiederholungsgruppen oder kommagetrennten Listen) und ein Primärschlüssel muss existieren. (2 Pkt.)\n- Schritt: 'MitarbeiterListe' aufspalten in atomare Einzeleinträge oder separate Zuordnungstabelle. (1 Pkt.)\n\n2. Normalform (2NF):\n- Anforderung: Tabelle ist in 1NF und jedes Nichtschlüsselattribut ist voll funktional vom gesamten Primärschlüssel abhängig (relevant bei zusammengesetzten Primärschlüsseln, z. B. ProjektID + MitarbeiterID). (2 Pkt.)\n- Schritt: Mitarbeiterbezogene Attribute (Name, Stundensatz) in separate Tabelle 'tbl_mitarbeiter' auslagern. (1 Pkt.)\n\n3. Normalform (3NF):\n- Anforderung: Tabelle ist in 2NF und kein Nichtschlüsselattribut ist transitiv von einem Primärschlüssel abhängig (keine Abhängigkeiten zwischen Nichtschlüsseln). (2 Pkt.)\n- Schritt: 'Abteilungsleiter' hängt von 'Abteilung' ab, nicht direkt von ProjektID. Daher Auslagerung in 'tbl_abteilung' [AbteilungsID, Abteilungsname, AbteilungsleiterID]. (2 Pkt.)", + "type": "text", + "options": [] + }, + { + "id": "ap1_erm_003", + "exam": "AP1", + "lernfeld": "LF 5", + "topic": "Referentielle Integrität und Fremdschlüssel-Löschweiterleitung", + "scenario": "In einem Datenbanksystem existiert eine 1:n-Beziehung zwischen 'tbl_abteilung' (Primärtabelle) und 'tbl_mitarbeiter' (Fremdtabelle über abteilung_id).", + "question": "a) Definieren Sie den Begriff 'Referentielle Integrität'.\nb) Vergleichen Sie die beiden Löschregeln 'ON DELETE CASCADE' und 'ON DELETE RESTRICT' und beurteilen Sie deren Auswirkung beim Löschen einer Abteilung, der noch Mitarbeiter zugeordnet sind.", + "points": 6, + "solution": "a) Referentielle Integrität stellt sicher, dass Fremdschlüsselwerte in einer Detailtabelle immer auf einen tatsächlich existierenden Primärschlüssel in der Mastertabelle verweisen (Verhinderung von Waisen-Datensätzen). (2 Pkt.)\nb) Vergleich der Löschregeln:\n- ON DELETE CASCADE: Das Löschen der Abteilung führt zum automatischen Mitlöschen aller in dieser Abteilung beschäftigten Mitarbeiter. Gefahr von irreversiblem Datenverlust! (2 Pkt.)\n- ON DELETE RESTRICT (oder NO ACTION): Die Datenbank blockiert das Löschen der Abteilung mit einer Exception, solange noch mindestens ein Mitarbeiter dieser Abteilung zugeordnet ist. Dies schützt vor versehentlichem Löschen. (2 Pkt.)", + "type": "text", + "options": [] + }, + { + "id": "ap1_erm_004", + "exam": "AP1", + "lernfeld": "LF 5", + "topic": "Auflösung n:m Beziehung (Junction Table)", + "scenario": "In einer Schuldatenbank belegen viele Schüler (tbl_schueler) viele verschiedene Wahlkurse (tbl_kurs).", + "question": "Beschreiben Sie den Standardansatz zur relationalen Implementierung dieser n:m-Beziehung. Geben Sie die Struktur der erforderlichen Zwischentabelle einschließlich Primär- und Fremdschlüsseln sowie eventueller Zusatzattribute an.", + "points": 6, + "solution": "1. Erstellung einer Verbindungstabelle / Junction Table (z. B. 'tbl_schueler_belegt_kurs'). (2 Pkt.)\n2. Struktur der Zwischentabelle:\n- Fremdschlüssel 1: schueler_id (verweist auf tbl_schueler.id) (1 Pkt.)\n- Fremdschlüssel 2: kurs_id (verweist auf tbl_kurs.id) (1 Pkt.)\n- Zusammengesetzter Primärschlüssel aus (schueler_id, kurs_id), um Mehrfachbelegungen des gleichen Kurses durch denselben Schüler zu verhindern. (1 Pkt.)\n- Mögliche relationale Zusatzattribute: z. B. belegungsdatum, endnote, fehlstunden. (1 Pkt.)", + "type": "text", + "options": [] + }, + { + "id": "ap1_sg_001", + "exam": "AP1", + "lernfeld": "LF 5", + "topic": "DIN 66261 Struktogramm: Schleifentypen im Vergleich", + "scenario": "Ein Fachinformatiker soll einen Algorithmus zur Benutzereingabe eines sicheren Passworts entwerfen. Das Passwort muss mindestens einmal abgefragt werden und solange wiederholt eingegeben werden, bis es den Richtlinien entspricht.", + "question": "a) Unterscheiden Sie die abweisende (kopfgesteuerte) Schleife und die durchlaufende (fußgesteuerte) Schleife hinsichtlich Prüfzeitpunkt der Abbruchbedingung und Mindestanzahl der Durchläufe.\nb) Welcher Schleifentyp ist für die Passwortabfrage nach DIN 66261 zu wählen? Skizzieren oder beschreiben Sie das DIN 66261 Struktogramm-Symbol.", + "points": 6, + "solution": "a) Vergleich der Schleifentypen:\n- Abweisende Schleife (WHILE / kopfgesteuert): Bedingung wird VOR dem ersten Durchlauf geprüft. Wenn die Bedingung anfangs falsch ist, wird der Schleifenkörper 0-mal durchlaufen (Mindestdurchlauf: 0). (2 Pkt.)\n- Durchlaufende Schleife (DO..WHILE / fußgesteuert): Bedingung wird NACH dem Schleifenkörper am Ende geprüft. Der Schleifenkörper wird mindestens 1-mal durchlaufen (Mindestdurchlauf: 1). (2 Pkt.)\nb) Auswahl & DIN 66261 Darstellung:\n- Zu wählen ist die fußgesteuerte Schleife (DO..WHILE), da der Benutzer das Passwort mindestens einmal eingeben muss, bevor die Validierung greifen kann. (1 Pkt.)\n- Symbol nach DIN 66261: L-förmiger Block; der Schleifenrumpf steht oben, und am unteren Ende über die gesamte Breite verläuft der Bedingungsbalken 'solange passwort_gueltig == False'. (1 Pkt.)", + "type": "text", + "options": [] + }, + { + "id": "ap1_sg_002", + "exam": "AP1", + "lernfeld": "LF 5", + "topic": "DIN 66261 Struktogramm Trace & Variablenverlauf", + "scenario": "Gegeben ist folgendes DIN 66261 Struktogramm zur Berechnung:\n1. a = 14; b = 4; ergebnis = 0\n2. Solange b > 0 (kopfgesteuerte Schleife):\n 2.1. IF b % 2 == 1 THEN ergebnis = ergebnis + a\n 2.2. a = a * 2\n 2.3. b = b // 2 (Ganzzahldivision)\n3. Ausgabe ergebnis", + "question": "Erstellen Sie eine Trace-Tabelle (Schreibtischtest) für alle Schleifendurchläufe mit den Werten von a, b und ergebnis und geben Sie den finalen Ausgabewert an. Welcher mathematische Algorithmus wird hier ausgeführt?", + "points": 8, + "solution": "Trace-Tabelle:\n- Initialisierung: a = 14, b = 4, ergebnis = 0. Bedingung (b > 0) ist WAHR. (1 Pkt.)\n- Durchlauf 1: b=4 (4 % 2 == 0 -> IF nein). a = 28, b = 4 // 2 = 2, ergebnis = 0. Bedingung (2 > 0) ist WAHR. (2 Pkt.)\n- Durchlauf 2: b=2 (2 % 2 == 0 -> IF nein). a = 56, b = 2 // 2 = 1, ergebnis = 0. Bedingung (1 > 0) ist WAHR. (2 Pkt.)\n- Durchlauf 3: b=1 (1 % 2 == 1 -> IF ja). ergebnis = 0 + 56 = 56. a = 112, b = 1 // 2 = 0. Bedingung (0 > 0) ist FALSCH. Schleifenabbruch. (2 Pkt.)\n\nFinaler Ausgabewert: 56. (1 Pkt.)\nMathematischer Algorithmus: Russische Bauernmultiplikation (Ägyptische Multiplikation) zur Berechnung von 14 * 4 = 56 durch Verdoppeln und Halbieren.", + "type": "text", + "options": [] + }, + { + "id": "ap1_sg_003", + "exam": "AP1", + "lernfeld": "LF 5", + "topic": "DIN 66261 Mehrfachauswahl (CASE / SWITCH) vs. verschachteltes IF", + "scenario": "In einem IT-Versandhandel richten sich die Versandkosten nach dem Zielland: DE = 4.90 EUR, AT/CH = 9.90 EUR, EU = 14.90 EUR, Sonstige = 29.90 EUR.", + "question": "a) Erläutern Sie, warum ein DIN 66261 Fallauswahl-Block (CASE/SWITCH) gegenüber tief verschachtelten IF-THEN-ELSE-Strukturen zu bevorzugen ist.\nb) Beschreiben Sie die DIN 66261 Struktur für diese Fallunterscheidung.", + "points": 6, + "solution": "a) Begründung:\nEine Fallauswahl (CASE) vermeidet die unübersichtliche diagonale Kaskadierung von eingerückten IF-ELSE-Blöcken ('Treppeneffekt'). Sie verbessert die Lesbarkeit, Wartbarkeit und lässt sich im Quelltext oft effizienter als Sprungtabelle (Jump Table) kompilieren. (3 Pkt.)\nb) DIN 66261 Struktur:\n- Kopfblock: Dreieck über die gesamte Breite mit dem Selektorausdruck 'land_code'. (1 Pkt.)\n- Spaltenaufteilung darunter: 4 getrennte Spalten für die Werte ['DE'], ['AT', 'CH'], ['EU'] und eine Restspalte ['Sonstige' / Default]. (1 Pkt.)\n- In jeder Spalte steht die jeweilige Zuweisung der Versandkosten. (1 Pkt.)", + "type": "text", + "options": [] + }, + { + "id": "ap2_uml_001", + "exam": "AP2", + "lernfeld": "LF 9", + "topic": "UML Klassendiagramm: Sichtbarkeiten und Notationsstandard", + "scenario": "Ein Softwaredesign-Dokument enthält folgende Methodendefinition in einer UML-Klasse:\n# berechneBonus(in umsatz: double, out steuer: double): double", + "question": "a) Entschlüsseln Sie alle Bestandteile dieser UML-Signatur (Sichtbarkeit, Parameterrichtung, Parametertypen und Rückgabewert).\nb) Nennen Sie alle vier UML-Sichtbarkeitsmodifikatoren mit ihrem Symbol und ihrer Bedeutung in der objektorientierten Programmierung (OOP).", + "points": 8, + "solution": "a) Entschlüsselung der Signatur:\n- '#' = Sichtbarkeit 'protected' (nur in dieser Klasse und abgeleiteten Unterklassen sichtbar). (1 Pkt.)\n- 'berechneBonus' = Name der Operation/Methode. (1 Pkt.)\n- 'in umsatz: double' = Eingabeparameter namens umsatz vom Datentyp double (Pass-by-value). (1 Pkt.)\n- 'out steuer: double' = Ausgabeparameter zur Rückgabe eines zusätzlichen Werts. (1 Pkt.)\n- ': double' am Ende = Typ des Rückgabewerts der Methode. (1 Pkt.)\nb) Die 4 UML-Sichtbarkeiten:\n1. '+' = public (öffentlich: für alle Klassen uneingeschränkt sichtbar). (1 Pkt.)\n2. '-' = private (privat: nur innerhalb der deklarierenden Klasse sichtbar). (1 Pkt.)\n3. '#' = protected (geschützt: nur in der deklarierenden Klasse und ihren Spezialisierungen/Subklassen sichtbar). (0.5 Pkt.)\n4. '~' = package (Paket: sichtbar für alle Klassen im selben Namespace/Package). (0.5 Pkt.)", + "type": "text", + "options": [] + }, + { + "id": "ap2_uml_002", + "exam": "AP2", + "lernfeld": "LF 9", + "topic": "UML Assoziation vs. Aggregation vs. Komposition", + "scenario": "In einem ERP-System werden Beziehungen zwischen folgenden Konzepten modelliert:\n1. Auto und Motor\n2. Universität und Student\n3. Dokument und Paragraph", + "question": "a) Unterscheiden Sie die UML-Beziehungstypen 'einfache Assoziation', 'Aggregation' (shared aggregation) und 'Komposition' (composite aggregation) hinsichtlich der Lebenszeitabhängigkeit (Existenzenzkopplung).\nb) Ordnen Sie die drei Beispiele den passenden Beziehungstypen zu und geben Sie jeweils das grafische UML-Symbol am Ende der Beziehungslinie an.", + "points": 9, + "solution": "a) Begriffsabgrenzung:\n- Einfache Assoziation: Lose Beziehung zwischen zwei eigenständigen Klassen; keine Teil-Ganzes-Hierarchie; Lebenszyklen sind vollkommen unabhängig. (1.5 Pkt.)\n- Aggregation: 'Teil-von'-Beziehung, bei der das Teilobjekt auch ohne das Ganzes-Objekt eigenständig weiterexistieren kann (schwache Bindung). (1.5 Pkt.)\n- Komposition: Strenge 'Besteht-aus'-Beziehung mit existenzieller Abhängigkeit: Wird das Ganzes-Objekt gelöscht, werden alle seine Teilobjekte zwingend mitvernichtet. Ein Teil kann zu jedem Zeitpunkt höchstens zu einem Ganzen gehören. (2 Pkt.)\nb) Zuordnung und Symbole:\n1. Universität und Student: Aggregation. Der Student existiert auch ohne die Universität weiter. Symbol: Offene, weiße Raute an der Seite der Universität. (1.5 Pkt.)\n2. Dokument und Paragraph: Komposition. Ein Paragraph existiert nicht ohne das zugehörige Dokument. Symbol: Ausgefüllte, schwarze Raute an der Seite des Dokuments. (1.5 Pkt.)\n3. Auto und Motor: Je nach Geschäftsdomäne Komposition oder Aggregation (wenn Motoren im Lager autark gewartet und getauscht werden -> Aggregation). In der Prüfung wird meist Komposition (schwarze Raute) oder Aggregation (weiße Raute) bei Begründung voll gewertet. (1 Pkt.)", + "type": "text", + "options": [] + }, + { + "id": "ap2_uml_003", + "exam": "AP2", + "lernfeld": "LF 9", + "topic": "UML Interfaces vs. Abstrakte Klassen", + "scenario": "In einer Banking-Software soll ein flexibles Zahlungssystem entwickelt werden, das Kreditkarte, SEPA-Lastschrift und PayPal unterstützt.", + "question": "a) Vergleichen Sie eine abstrakte Klasse und ein Interface in UML 2.5 hinsichtlich Vererbung, Methoden-Implementierung und Instanziierbarkeit.\nb) Skizzieren oder beschreiben Sie die beiden unterschiedlichen Pfeilarten in UML für die 'Generalisierung' (extends) und die 'Realisierung' (implements).", + "points": 7, + "solution": "a) Vergleich:\n- Abstrakte Klasse: Kann sowohl abstrakte Methoden (ohne Rumpf) als auch vollständig ausprogrammierte Methoden und Instanzvariablen/Zustände enthalten. Eine Klasse kann in den meisten Programmiersprachen nur von maximal einer (abstrakten) Basisklasse erben (Einfachvererbung). Kann nicht direkt instanziiert werden. (2.5 Pkt.)\n- Interface (Schnittstelle): Definiert reine Funktionssignaturen / Verträge (in modernen Sprachen auch Default-Methods). Klassen können beliebig viele Interfaces implementieren (Mehrfachvererbung von Typen). Besitzt keinen eigenen Objektzustand. (2.5 Pkt.)\nb) Pfeilarten in UML:\n- Generalisierung (extends / Vererbung): Durchgezogene Linie mit einer geschlossenen, nicht ausgefüllten (weißen) Pfeilspitze (Dreieck) in Richtung der Basisklasse. (1 Pkt.)\n- Realisierung (implements / Schnittstelle): Gestrichelte Linie mit einer geschlossenen, nicht ausgefüllten (weißen) Pfeilspitze (Dreieck) in Richtung des Interfaces. (1 Pkt.)", + "type": "text", + "options": [] + }, + { + "id": "ap2_uml_004", + "exam": "AP2", + "lernfeld": "LF 9", + "topic": "UML Sequenzdiagramm: Synchrone vs Asynchrone Aufrufe & Fragmente", + "scenario": "In einer Microservices-Architektur kommunizieren ein Frontend-Client, ein Bestellservice und ein externer Zahlungsanbieter über ein Sequenzdiagramm.", + "question": "a) Unterscheiden Sie synchrone Aufrufe, asynchrone Aufrufe und Rückgabenachrichten (Return Messages) anhand ihrer graphischen Pfeildarstellung im UML-Sequenzdiagramm.\nb) Erläutern Sie die Bedeutung des kombinierten Fragments 'alt' und grenzen Sie es gegen 'opt' und 'loop' ab.", + "points": 8, + "solution": "a) Pfeilarten im Sequenzdiagramm:\n- Synchroner Aufruf: Durchgezogene Linie mit ausgefüllter schwarzer Dreieckspfeilspitze ('->>'). Der Sender blockiert und wartet auf die Antwort. (2 Pkt.)\n- Asynchroner Aufruf: Durchgezogene Linie mit offener Pfeilspitze ('->'). Der Sender sendet das Event/Message und arbeitet sofort weiter, ohne auf die Ausführung zu warten. (2 Pkt.)\n- Antwortnachricht (Return): Gestrichelte Linie mit offener Pfeilspitze ('-->>'). Gibt das Berechnungsergebnis an den Aufrufer zurück. (1 Pkt.)\nb) Kombinierte Fragmente (Interaction Frames):\n- 'alt' (Alternative): Entspricht einem IF-ELSE Block. Es existieren zwei oder mehr Operandenbereiche, getrennt durch eine gestrichelte Linie. Genau der Zweig wird ausgeführt, dessen Guard-Bedingung wahr ist. (1 Pkt.)\n- 'opt' (Option): Entspricht einem einfachen IF ohne ELSE. Der Block wird nur ausgeführt, wenn die Bedingung zutrifft, andernfalls übersprungen. (1 Pkt.)\n- 'loop' (Schleife): Der Inhalt wird so lange wiederholt, wie die Schleifenbedingung (Guard) wahr ist bzw. die definierte Durchlaufanzahl (min..max) nicht erreicht ist. (1 Pkt.)", + "type": "text", + "options": [] + }, + { + "id": "ap2_uml_005", + "exam": "AP2", + "lernfeld": "LF 9", + "topic": "UML Anwendungsfalldiagramm: <> vs <>", + "scenario": "Für ein Geldautomatensystem (ATM) werden die Use Cases 'Geld abheben', 'PIN prüfen' und 'Kontoauszug drucken' entworfen.", + "question": "a) Unterscheiden Sie die beiden Beziehungstypen '<>' und '<>' zwischen Anwendungsfällen hinsichtlich Verpflichtung und Richtung des Pfeils.\nb) Ordnen Sie die Beziehung zwischen 'Geld abheben' und 'PIN prüfen' sowie zwischen 'Geld abheben' und 'Kontoauszug drucken' zu.", + "points": 7, + "solution": "a) Definition & Pfeilrichtung:\n- <>: Zwingend erforderliche Einbindung. Der Basis-Use-Case kann ohne die Ausführung des inkludierten Use Cases nicht erfolgreich abgeschlossen werden. Der gestrichelte Pfeil zeigt vom Basis-Use-Case HIN zum inkludierten Use Case (Basisfall -> Inklusionsfall). (2.5 Pkt.)\n- <>: Optionale Erweiterung unter bestimmten Bedingungen (Extension Point). Der Basisfall ist auch ohne die Erweiterung vollständig funktionsfähig. Der gestrichelte Pfeil zeigt von der Erweiterung ZURÜCK zum Basis-Use-Case (Erweiterung -> Basisfall). (2.5 Pkt.)\nb) Zuordnung:\n- 'Geld abheben' -> <> -> 'PIN prüfen': Ohne erfolgreiche PIN-Verifizierung ist keine Bargeldauszahlung möglich (zwingend nötig). (1 Pkt.)\n- 'Kontoauszug drucken' -> <> -> 'Geld abheben': Der Kunde kann optional nach der Auszahlung einen Beleg anfordern, muss es aber nicht (optionale Erweiterung). (1 Pkt.)", + "type": "text", + "options": [] + }, + { + "id": "ap1_mc_erm_001", + "exam": "AP1", + "lernfeld": "LF 5", + "topic": "MC-Test: 3. Normalform Kriterium", + "scenario": "Eine Datenbanktabelle befindet sich in der 2. Normalform.", + "question": "Unter welcher Bedingung ist diese Tabelle auch in der 3. Normalform?", + "points": 4, + "solution": "B ist korrekt: Ein Nichtschlüsselattribut darf nicht von einem anderen Nichtschlüsselattribut abhängig sein (keine transitiven Abhängigkeiten).", + "type": "mc", + "options": [ + "A) Alle Attribute müssen Fremdschlüssel sein.", + "B) Kein Nichtschlüsselattribut darf transitiv vom Primärschlüssel abhängen.", + "C) Die Tabelle darf maximal 3 Spalten besitzen.", + "D) Alle Datensätze müssen in aufsteigender Reihenfolge sortiert sein." + ] + }, + { + "id": "ap1_mc_sg_001", + "exam": "AP1", + "lernfeld": "LF 5", + "topic": "MC-Test: Kopfgesteuerte Schleife (WHILE)", + "scenario": "In einem DIN 66261 Struktogramm ist eine kopfgesteuerte WHILE-Schleife mit der Bedingung (i < 0) modelliert. Die Variable i wurde zuvor mit 5 initialisiert.", + "question": "Wie oft wird der Schleifenkörper ausgeführt?", + "points": 4, + "solution": "A ist korrekt: Da die Bedingung (5 < 0) bereits vor dem ersten Durchlauf FALSCH ist, wird eine abweisende Schleife genau 0-mal ausgeführt.", + "type": "mc", + "options": [ + "A) Genau 0-mal (wird abgewiesen).", + "B) Genau 1-mal.", + "C) Genau 5-mal.", + "D) Es entsteht eine Endlosschleife." + ] + }, + { + "id": "ap2_mc_uml_001", + "exam": "AP2", + "lernfeld": "LF 9", + "topic": "MC-Test: UML Komposition Eigenschaft", + "scenario": "Zwischen den Klassen 'Gebäude' und 'Raum' ist eine Komposition definiert.", + "question": "Welche Aussage beschreibt das Verhalten einer Komposition in UML korrekt?", + "points": 4, + "solution": "C ist korrekt: Bei der Komposition existiert eine strenge Lebenszeitabhängigkeit. Wird das Ganze (Gebäude) zerstört, erlöschen auch die Teile (Räume).", + "type": "mc", + "options": [ + "A) Ein Raum kann gleichzeitig zu mehreren Gebäuden gehören.", + "B) Wenn ein Gebäude abgerissen wird, existieren seine Räume als Objekte eigenständig weiter.", + "C) Das Teilobjekt (Raum) ist existenziell an das Ganzes-Objekt (Gebäude) gebunden und wird beim Löschen des Gebäudes mitvernichtet.", + "D) Eine Komposition wird in UML immer mit einer weißen, offenen Raute dargestellt." + ] } -]; +];\n \ No newline at end of file diff --git a/assets/ihk_tools/ihk_question_bank.json b/assets/ihk_tools/ihk_question_bank.json index 379cbea..7af2335 100644 --- a/assets/ihk_tools/ihk_question_bank.json +++ b/assets/ihk_tools/ihk_question_bank.json @@ -2568,5 +2568,200 @@ "scenario": "Ein Einzelhändler plant den Einsatz von KI-gestützter Videoanalyse und Gesichtserkennung in seinen Filialen, um personalisierte Werbung auf Kundenmonitoren auszuspielen.", "question": "a) Prüfen Sie, ob für dieses Vorhaben eine Datenschutz-Folgenabschätzung (DSFA nach Art. 35 DSGVO) zwingend erforderlich ist, und begründen Sie Ihre Entscheidung.\nb) In welche Risikoklasse nach dem EU AI Act (KI-Verordnung) fällt biometrische Identifikation und Emotionserkennung im öffentlichen Raum?\nc) Was versteht man im Rahmen von Analytics unter dem Konzept 'k-Anonymität'?", "expected_solution": "a) Ja, zwingend erforderlich. Gemäß Art. 35 Abs. 3 DSGVO ist eine DSFA vorgeschrieben bei systematischer und umfassender Überwachung öffentlich zugänglicher Bereiche sowie beim Einsatz neuer Technologien mit voraussichtlich hohem Risiko für die Rechte und Freiheiten natürlicher Personen.\nb) Hochrisiko-KI-System (bzw. in Teilen unzulässige/verbotene KI-Praxis bei biometrischer Echtzeit-Fernidentifikation ohne strenge richterliche Ausnahme).\nc) Ein Datensatz erfüllt k-Anonymität, wenn die quasi-identifizierenden Merkmale (z.B. PLZ, Alter, Geschlecht) jedes Individuums nicht von mindestens k-1 anderen Personen im Datensatz unterschieden werden können (Gruppengröße >= k)." + }, + { + "id": "ap1_erm_001", + "exam": "AP1", + "lernfeld": "LF 5", + "topic": "ER-Modellierung: Kardinalitäten und Notationen", + "scenario": "Für eine Autovermietung soll eine relationale Datenbank entworfen werden. Ein Kunde kann mehrere Mietverträge abschließen, aber jeder Mietvertrag gehört genau zu einem Kunden. Zu jedem Mietvertrag gehört genau ein Fahrzeug, jedoch kann ein Fahrzeug im Laufe der Zeit in mehreren Mietverträgen gebucht werden.", + "question": "a) Bestimmen Sie die Kardinalitäten zwischen den Entitäten KUNDE, MIETVERTRAG und FAHRZEUG in der Chen-Notation sowie in der Min-Max-Notation.\nb) Begründen Sie, warum zwischen KUNDE und FAHRZEUG keine direkte n:m-Beziehung modelliert werden sollte.", + "points": 8, + "solution": "a) Kardinalitäten:\n- KUNDE zu MIETVERTRAG: 1 : n (Chen) bzw. KUNDE (0,n) und MIETVERTRAG (1,1) (Min-Max). Ein Kunde kann 0 bis beliebig viele Verträge haben; ein Vertrag muss genau einem Kunden gehören. (2 Pkt.)\n- FAHRZEUG zu MIETVERTRAG: 1 : n (Chen) bzw. FAHRZEUG (0,n) und MIETVERTRAG (1,1) (Min-Max). Ein Fahrzeug kann 0 bis viele Verträge haben; ein Vertrag bezieht sich auf genau 1 Fahrzeug. (2 Pkt.)\nb) Begründung:\nEine direkte n:m-Beziehung KUNDE - FAHRZEUG könnte keine historischen Buchungsdaten (Mietzeitraum von/bis, Kilometerstand, Rechnungsbetrag) speichern, ohne Attribute an der Beziehung zu verdoppeln. Die Entität MIETVERTRAG fungiert als zentrale Geschäftseinheit und löst die n:m-Beziehung sauber in zwei 1:n-Beziehungen auf. (4 Pkt.)", + "type": "text", + "options": [] + }, + { + "id": "ap1_erm_002", + "exam": "AP1", + "lernfeld": "LF 5", + "topic": "Datenbank-Normalisierung 1NF bis 3NF", + "scenario": "Gegeben ist folgende nicht-normalisierte Tabelle 'tbl_projekte':\n[ProjektID, Projektname, MitarbeiterListe ('Müller, Schmidt, Meier'), Abteilung, Abteilungsleiter, Stundensatz].", + "question": "Erläutern Sie die Anforderungen der 1., 2. und 3. Normalform anhand dieses Beispiels und führen Sie die erforderlichen Zerlegungsschritte durch.", + "points": 10, + "solution": "1. Normalform (1NF):\n- Anforderung: Alle Attribute müssen atomar sein (keine Wiederholungsgruppen oder kommagetrennten Listen) und ein Primärschlüssel muss existieren. (2 Pkt.)\n- Schritt: 'MitarbeiterListe' aufspalten in atomare Einzeleinträge oder separate Zuordnungstabelle. (1 Pkt.)\n\n2. Normalform (2NF):\n- Anforderung: Tabelle ist in 1NF und jedes Nichtschlüsselattribut ist voll funktional vom gesamten Primärschlüssel abhängig (relevant bei zusammengesetzten Primärschlüsseln, z. B. ProjektID + MitarbeiterID). (2 Pkt.)\n- Schritt: Mitarbeiterbezogene Attribute (Name, Stundensatz) in separate Tabelle 'tbl_mitarbeiter' auslagern. (1 Pkt.)\n\n3. Normalform (3NF):\n- Anforderung: Tabelle ist in 2NF und kein Nichtschlüsselattribut ist transitiv von einem Primärschlüssel abhängig (keine Abhängigkeiten zwischen Nichtschlüsseln). (2 Pkt.)\n- Schritt: 'Abteilungsleiter' hängt von 'Abteilung' ab, nicht direkt von ProjektID. Daher Auslagerung in 'tbl_abteilung' [AbteilungsID, Abteilungsname, AbteilungsleiterID]. (2 Pkt.)", + "type": "text", + "options": [] + }, + { + "id": "ap1_erm_003", + "exam": "AP1", + "lernfeld": "LF 5", + "topic": "Referentielle Integrität und Fremdschlüssel-Löschweiterleitung", + "scenario": "In einem Datenbanksystem existiert eine 1:n-Beziehung zwischen 'tbl_abteilung' (Primärtabelle) und 'tbl_mitarbeiter' (Fremdtabelle über abteilung_id).", + "question": "a) Definieren Sie den Begriff 'Referentielle Integrität'.\nb) Vergleichen Sie die beiden Löschregeln 'ON DELETE CASCADE' und 'ON DELETE RESTRICT' und beurteilen Sie deren Auswirkung beim Löschen einer Abteilung, der noch Mitarbeiter zugeordnet sind.", + "points": 6, + "solution": "a) Referentielle Integrität stellt sicher, dass Fremdschlüsselwerte in einer Detailtabelle immer auf einen tatsächlich existierenden Primärschlüssel in der Mastertabelle verweisen (Verhinderung von Waisen-Datensätzen). (2 Pkt.)\nb) Vergleich der Löschregeln:\n- ON DELETE CASCADE: Das Löschen der Abteilung führt zum automatischen Mitlöschen aller in dieser Abteilung beschäftigten Mitarbeiter. Gefahr von irreversiblem Datenverlust! (2 Pkt.)\n- ON DELETE RESTRICT (oder NO ACTION): Die Datenbank blockiert das Löschen der Abteilung mit einer Exception, solange noch mindestens ein Mitarbeiter dieser Abteilung zugeordnet ist. Dies schützt vor versehentlichem Löschen. (2 Pkt.)", + "type": "text", + "options": [] + }, + { + "id": "ap1_erm_004", + "exam": "AP1", + "lernfeld": "LF 5", + "topic": "Auflösung n:m Beziehung (Junction Table)", + "scenario": "In einer Schuldatenbank belegen viele Schüler (tbl_schueler) viele verschiedene Wahlkurse (tbl_kurs).", + "question": "Beschreiben Sie den Standardansatz zur relationalen Implementierung dieser n:m-Beziehung. Geben Sie die Struktur der erforderlichen Zwischentabelle einschließlich Primär- und Fremdschlüsseln sowie eventueller Zusatzattribute an.", + "points": 6, + "solution": "1. Erstellung einer Verbindungstabelle / Junction Table (z. B. 'tbl_schueler_belegt_kurs'). (2 Pkt.)\n2. Struktur der Zwischentabelle:\n- Fremdschlüssel 1: schueler_id (verweist auf tbl_schueler.id) (1 Pkt.)\n- Fremdschlüssel 2: kurs_id (verweist auf tbl_kurs.id) (1 Pkt.)\n- Zusammengesetzter Primärschlüssel aus (schueler_id, kurs_id), um Mehrfachbelegungen des gleichen Kurses durch denselben Schüler zu verhindern. (1 Pkt.)\n- Mögliche relationale Zusatzattribute: z. B. belegungsdatum, endnote, fehlstunden. (1 Pkt.)", + "type": "text", + "options": [] + }, + { + "id": "ap1_sg_001", + "exam": "AP1", + "lernfeld": "LF 5", + "topic": "DIN 66261 Struktogramm: Schleifentypen im Vergleich", + "scenario": "Ein Fachinformatiker soll einen Algorithmus zur Benutzereingabe eines sicheren Passworts entwerfen. Das Passwort muss mindestens einmal abgefragt werden und solange wiederholt eingegeben werden, bis es den Richtlinien entspricht.", + "question": "a) Unterscheiden Sie die abweisende (kopfgesteuerte) Schleife und die durchlaufende (fußgesteuerte) Schleife hinsichtlich Prüfzeitpunkt der Abbruchbedingung und Mindestanzahl der Durchläufe.\nb) Welcher Schleifentyp ist für die Passwortabfrage nach DIN 66261 zu wählen? Skizzieren oder beschreiben Sie das DIN 66261 Struktogramm-Symbol.", + "points": 6, + "solution": "a) Vergleich der Schleifentypen:\n- Abweisende Schleife (WHILE / kopfgesteuert): Bedingung wird VOR dem ersten Durchlauf geprüft. Wenn die Bedingung anfangs falsch ist, wird der Schleifenkörper 0-mal durchlaufen (Mindestdurchlauf: 0). (2 Pkt.)\n- Durchlaufende Schleife (DO..WHILE / fußgesteuert): Bedingung wird NACH dem Schleifenkörper am Ende geprüft. Der Schleifenkörper wird mindestens 1-mal durchlaufen (Mindestdurchlauf: 1). (2 Pkt.)\nb) Auswahl & DIN 66261 Darstellung:\n- Zu wählen ist die fußgesteuerte Schleife (DO..WHILE), da der Benutzer das Passwort mindestens einmal eingeben muss, bevor die Validierung greifen kann. (1 Pkt.)\n- Symbol nach DIN 66261: L-förmiger Block; der Schleifenrumpf steht oben, und am unteren Ende über die gesamte Breite verläuft der Bedingungsbalken 'solange passwort_gueltig == False'. (1 Pkt.)", + "type": "text", + "options": [] + }, + { + "id": "ap1_sg_002", + "exam": "AP1", + "lernfeld": "LF 5", + "topic": "DIN 66261 Struktogramm Trace & Variablenverlauf", + "scenario": "Gegeben ist folgendes DIN 66261 Struktogramm zur Berechnung:\n1. a = 14; b = 4; ergebnis = 0\n2. Solange b > 0 (kopfgesteuerte Schleife):\n 2.1. IF b % 2 == 1 THEN ergebnis = ergebnis + a\n 2.2. a = a * 2\n 2.3. b = b // 2 (Ganzzahldivision)\n3. Ausgabe ergebnis", + "question": "Erstellen Sie eine Trace-Tabelle (Schreibtischtest) für alle Schleifendurchläufe mit den Werten von a, b und ergebnis und geben Sie den finalen Ausgabewert an. Welcher mathematische Algorithmus wird hier ausgeführt?", + "points": 8, + "solution": "Trace-Tabelle:\n- Initialisierung: a = 14, b = 4, ergebnis = 0. Bedingung (b > 0) ist WAHR. (1 Pkt.)\n- Durchlauf 1: b=4 (4 % 2 == 0 -> IF nein). a = 28, b = 4 // 2 = 2, ergebnis = 0. Bedingung (2 > 0) ist WAHR. (2 Pkt.)\n- Durchlauf 2: b=2 (2 % 2 == 0 -> IF nein). a = 56, b = 2 // 2 = 1, ergebnis = 0. Bedingung (1 > 0) ist WAHR. (2 Pkt.)\n- Durchlauf 3: b=1 (1 % 2 == 1 -> IF ja). ergebnis = 0 + 56 = 56. a = 112, b = 1 // 2 = 0. Bedingung (0 > 0) ist FALSCH. Schleifenabbruch. (2 Pkt.)\n\nFinaler Ausgabewert: 56. (1 Pkt.)\nMathematischer Algorithmus: Russische Bauernmultiplikation (Ägyptische Multiplikation) zur Berechnung von 14 * 4 = 56 durch Verdoppeln und Halbieren.", + "type": "text", + "options": [] + }, + { + "id": "ap1_sg_003", + "exam": "AP1", + "lernfeld": "LF 5", + "topic": "DIN 66261 Mehrfachauswahl (CASE / SWITCH) vs. verschachteltes IF", + "scenario": "In einem IT-Versandhandel richten sich die Versandkosten nach dem Zielland: DE = 4.90 EUR, AT/CH = 9.90 EUR, EU = 14.90 EUR, Sonstige = 29.90 EUR.", + "question": "a) Erläutern Sie, warum ein DIN 66261 Fallauswahl-Block (CASE/SWITCH) gegenüber tief verschachtelten IF-THEN-ELSE-Strukturen zu bevorzugen ist.\nb) Beschreiben Sie die DIN 66261 Struktur für diese Fallunterscheidung.", + "points": 6, + "solution": "a) Begründung:\nEine Fallauswahl (CASE) vermeidet die unübersichtliche diagonale Kaskadierung von eingerückten IF-ELSE-Blöcken ('Treppeneffekt'). Sie verbessert die Lesbarkeit, Wartbarkeit und lässt sich im Quelltext oft effizienter als Sprungtabelle (Jump Table) kompilieren. (3 Pkt.)\nb) DIN 66261 Struktur:\n- Kopfblock: Dreieck über die gesamte Breite mit dem Selektorausdruck 'land_code'. (1 Pkt.)\n- Spaltenaufteilung darunter: 4 getrennte Spalten für die Werte ['DE'], ['AT', 'CH'], ['EU'] und eine Restspalte ['Sonstige' / Default]. (1 Pkt.)\n- In jeder Spalte steht die jeweilige Zuweisung der Versandkosten. (1 Pkt.)", + "type": "text", + "options": [] + }, + { + "id": "ap2_uml_001", + "exam": "AP2", + "lernfeld": "LF 9", + "topic": "UML Klassendiagramm: Sichtbarkeiten und Notationsstandard", + "scenario": "Ein Softwaredesign-Dokument enthält folgende Methodendefinition in einer UML-Klasse:\n# berechneBonus(in umsatz: double, out steuer: double): double", + "question": "a) Entschlüsseln Sie alle Bestandteile dieser UML-Signatur (Sichtbarkeit, Parameterrichtung, Parametertypen und Rückgabewert).\nb) Nennen Sie alle vier UML-Sichtbarkeitsmodifikatoren mit ihrem Symbol und ihrer Bedeutung in der objektorientierten Programmierung (OOP).", + "points": 8, + "solution": "a) Entschlüsselung der Signatur:\n- '#' = Sichtbarkeit 'protected' (nur in dieser Klasse und abgeleiteten Unterklassen sichtbar). (1 Pkt.)\n- 'berechneBonus' = Name der Operation/Methode. (1 Pkt.)\n- 'in umsatz: double' = Eingabeparameter namens umsatz vom Datentyp double (Pass-by-value). (1 Pkt.)\n- 'out steuer: double' = Ausgabeparameter zur Rückgabe eines zusätzlichen Werts. (1 Pkt.)\n- ': double' am Ende = Typ des Rückgabewerts der Methode. (1 Pkt.)\nb) Die 4 UML-Sichtbarkeiten:\n1. '+' = public (öffentlich: für alle Klassen uneingeschränkt sichtbar). (1 Pkt.)\n2. '-' = private (privat: nur innerhalb der deklarierenden Klasse sichtbar). (1 Pkt.)\n3. '#' = protected (geschützt: nur in der deklarierenden Klasse und ihren Spezialisierungen/Subklassen sichtbar). (0.5 Pkt.)\n4. '~' = package (Paket: sichtbar für alle Klassen im selben Namespace/Package). (0.5 Pkt.)", + "type": "text", + "options": [] + }, + { + "id": "ap2_uml_002", + "exam": "AP2", + "lernfeld": "LF 9", + "topic": "UML Assoziation vs. Aggregation vs. Komposition", + "scenario": "In einem ERP-System werden Beziehungen zwischen folgenden Konzepten modelliert:\n1. Auto und Motor\n2. Universität und Student\n3. Dokument und Paragraph", + "question": "a) Unterscheiden Sie die UML-Beziehungstypen 'einfache Assoziation', 'Aggregation' (shared aggregation) und 'Komposition' (composite aggregation) hinsichtlich der Lebenszeitabhängigkeit (Existenzenzkopplung).\nb) Ordnen Sie die drei Beispiele den passenden Beziehungstypen zu und geben Sie jeweils das grafische UML-Symbol am Ende der Beziehungslinie an.", + "points": 9, + "solution": "a) Begriffsabgrenzung:\n- Einfache Assoziation: Lose Beziehung zwischen zwei eigenständigen Klassen; keine Teil-Ganzes-Hierarchie; Lebenszyklen sind vollkommen unabhängig. (1.5 Pkt.)\n- Aggregation: 'Teil-von'-Beziehung, bei der das Teilobjekt auch ohne das Ganzes-Objekt eigenständig weiterexistieren kann (schwache Bindung). (1.5 Pkt.)\n- Komposition: Strenge 'Besteht-aus'-Beziehung mit existenzieller Abhängigkeit: Wird das Ganzes-Objekt gelöscht, werden alle seine Teilobjekte zwingend mitvernichtet. Ein Teil kann zu jedem Zeitpunkt höchstens zu einem Ganzen gehören. (2 Pkt.)\nb) Zuordnung und Symbole:\n1. Universität und Student: Aggregation. Der Student existiert auch ohne die Universität weiter. Symbol: Offene, weiße Raute an der Seite der Universität. (1.5 Pkt.)\n2. Dokument und Paragraph: Komposition. Ein Paragraph existiert nicht ohne das zugehörige Dokument. Symbol: Ausgefüllte, schwarze Raute an der Seite des Dokuments. (1.5 Pkt.)\n3. Auto und Motor: Je nach Geschäftsdomäne Komposition oder Aggregation (wenn Motoren im Lager autark gewartet und getauscht werden -> Aggregation). In der Prüfung wird meist Komposition (schwarze Raute) oder Aggregation (weiße Raute) bei Begründung voll gewertet. (1 Pkt.)", + "type": "text", + "options": [] + }, + { + "id": "ap2_uml_003", + "exam": "AP2", + "lernfeld": "LF 9", + "topic": "UML Interfaces vs. Abstrakte Klassen", + "scenario": "In einer Banking-Software soll ein flexibles Zahlungssystem entwickelt werden, das Kreditkarte, SEPA-Lastschrift und PayPal unterstützt.", + "question": "a) Vergleichen Sie eine abstrakte Klasse und ein Interface in UML 2.5 hinsichtlich Vererbung, Methoden-Implementierung und Instanziierbarkeit.\nb) Skizzieren oder beschreiben Sie die beiden unterschiedlichen Pfeilarten in UML für die 'Generalisierung' (extends) und die 'Realisierung' (implements).", + "points": 7, + "solution": "a) Vergleich:\n- Abstrakte Klasse: Kann sowohl abstrakte Methoden (ohne Rumpf) als auch vollständig ausprogrammierte Methoden und Instanzvariablen/Zustände enthalten. Eine Klasse kann in den meisten Programmiersprachen nur von maximal einer (abstrakten) Basisklasse erben (Einfachvererbung). Kann nicht direkt instanziiert werden. (2.5 Pkt.)\n- Interface (Schnittstelle): Definiert reine Funktionssignaturen / Verträge (in modernen Sprachen auch Default-Methods). Klassen können beliebig viele Interfaces implementieren (Mehrfachvererbung von Typen). Besitzt keinen eigenen Objektzustand. (2.5 Pkt.)\nb) Pfeilarten in UML:\n- Generalisierung (extends / Vererbung): Durchgezogene Linie mit einer geschlossenen, nicht ausgefüllten (weißen) Pfeilspitze (Dreieck) in Richtung der Basisklasse. (1 Pkt.)\n- Realisierung (implements / Schnittstelle): Gestrichelte Linie mit einer geschlossenen, nicht ausgefüllten (weißen) Pfeilspitze (Dreieck) in Richtung des Interfaces. (1 Pkt.)", + "type": "text", + "options": [] + }, + { + "id": "ap2_uml_004", + "exam": "AP2", + "lernfeld": "LF 9", + "topic": "UML Sequenzdiagramm: Synchrone vs Asynchrone Aufrufe & Fragmente", + "scenario": "In einer Microservices-Architektur kommunizieren ein Frontend-Client, ein Bestellservice und ein externer Zahlungsanbieter über ein Sequenzdiagramm.", + "question": "a) Unterscheiden Sie synchrone Aufrufe, asynchrone Aufrufe und Rückgabenachrichten (Return Messages) anhand ihrer graphischen Pfeildarstellung im UML-Sequenzdiagramm.\nb) Erläutern Sie die Bedeutung des kombinierten Fragments 'alt' und grenzen Sie es gegen 'opt' und 'loop' ab.", + "points": 8, + "solution": "a) Pfeilarten im Sequenzdiagramm:\n- Synchroner Aufruf: Durchgezogene Linie mit ausgefüllter schwarzer Dreieckspfeilspitze ('->>'). Der Sender blockiert und wartet auf die Antwort. (2 Pkt.)\n- Asynchroner Aufruf: Durchgezogene Linie mit offener Pfeilspitze ('->'). Der Sender sendet das Event/Message und arbeitet sofort weiter, ohne auf die Ausführung zu warten. (2 Pkt.)\n- Antwortnachricht (Return): Gestrichelte Linie mit offener Pfeilspitze ('-->>'). Gibt das Berechnungsergebnis an den Aufrufer zurück. (1 Pkt.)\nb) Kombinierte Fragmente (Interaction Frames):\n- 'alt' (Alternative): Entspricht einem IF-ELSE Block. Es existieren zwei oder mehr Operandenbereiche, getrennt durch eine gestrichelte Linie. Genau der Zweig wird ausgeführt, dessen Guard-Bedingung wahr ist. (1 Pkt.)\n- 'opt' (Option): Entspricht einem einfachen IF ohne ELSE. Der Block wird nur ausgeführt, wenn die Bedingung zutrifft, andernfalls übersprungen. (1 Pkt.)\n- 'loop' (Schleife): Der Inhalt wird so lange wiederholt, wie die Schleifenbedingung (Guard) wahr ist bzw. die definierte Durchlaufanzahl (min..max) nicht erreicht ist. (1 Pkt.)", + "type": "text", + "options": [] + }, + { + "id": "ap2_uml_005", + "exam": "AP2", + "lernfeld": "LF 9", + "topic": "UML Anwendungsfalldiagramm: <> vs <>", + "scenario": "Für ein Geldautomatensystem (ATM) werden die Use Cases 'Geld abheben', 'PIN prüfen' und 'Kontoauszug drucken' entworfen.", + "question": "a) Unterscheiden Sie die beiden Beziehungstypen '<>' und '<>' zwischen Anwendungsfällen hinsichtlich Verpflichtung und Richtung des Pfeils.\nb) Ordnen Sie die Beziehung zwischen 'Geld abheben' und 'PIN prüfen' sowie zwischen 'Geld abheben' und 'Kontoauszug drucken' zu.", + "points": 7, + "solution": "a) Definition & Pfeilrichtung:\n- <>: Zwingend erforderliche Einbindung. Der Basis-Use-Case kann ohne die Ausführung des inkludierten Use Cases nicht erfolgreich abgeschlossen werden. Der gestrichelte Pfeil zeigt vom Basis-Use-Case HIN zum inkludierten Use Case (Basisfall -> Inklusionsfall). (2.5 Pkt.)\n- <>: Optionale Erweiterung unter bestimmten Bedingungen (Extension Point). Der Basisfall ist auch ohne die Erweiterung vollständig funktionsfähig. Der gestrichelte Pfeil zeigt von der Erweiterung ZURÜCK zum Basis-Use-Case (Erweiterung -> Basisfall). (2.5 Pkt.)\nb) Zuordnung:\n- 'Geld abheben' -> <> -> 'PIN prüfen': Ohne erfolgreiche PIN-Verifizierung ist keine Bargeldauszahlung möglich (zwingend nötig). (1 Pkt.)\n- 'Kontoauszug drucken' -> <> -> 'Geld abheben': Der Kunde kann optional nach der Auszahlung einen Beleg anfordern, muss es aber nicht (optionale Erweiterung). (1 Pkt.)", + "type": "text", + "options": [] + }, + { + "id": "ap1_mc_erm_001", + "exam": "AP1", + "lernfeld": "LF 5", + "topic": "MC-Test: 3. Normalform Kriterium", + "scenario": "Eine Datenbanktabelle befindet sich in der 2. Normalform.", + "question": "Unter welcher Bedingung ist diese Tabelle auch in der 3. Normalform?", + "points": 4, + "solution": "B ist korrekt: Ein Nichtschlüsselattribut darf nicht von einem anderen Nichtschlüsselattribut abhängig sein (keine transitiven Abhängigkeiten).", + "type": "mc", + "options": [ + "A) Alle Attribute müssen Fremdschlüssel sein.", + "B) Kein Nichtschlüsselattribut darf transitiv vom Primärschlüssel abhängen.", + "C) Die Tabelle darf maximal 3 Spalten besitzen.", + "D) Alle Datensätze müssen in aufsteigender Reihenfolge sortiert sein." + ] + }, + { + "id": "ap1_mc_sg_001", + "exam": "AP1", + "lernfeld": "LF 5", + "topic": "MC-Test: Kopfgesteuerte Schleife (WHILE)", + "scenario": "In einem DIN 66261 Struktogramm ist eine kopfgesteuerte WHILE-Schleife mit der Bedingung (i < 0) modelliert. Die Variable i wurde zuvor mit 5 initialisiert.", + "question": "Wie oft wird der Schleifenkörper ausgeführt?", + "points": 4, + "solution": "A ist korrekt: Da die Bedingung (5 < 0) bereits vor dem ersten Durchlauf FALSCH ist, wird eine abweisende Schleife genau 0-mal ausgeführt.", + "type": "mc", + "options": [ + "A) Genau 0-mal (wird abgewiesen).", + "B) Genau 1-mal.", + "C) Genau 5-mal.", + "D) Es entsteht eine Endlosschleife." + ] + }, + { + "id": "ap2_mc_uml_001", + "exam": "AP2", + "lernfeld": "LF 9", + "topic": "MC-Test: UML Komposition Eigenschaft", + "scenario": "Zwischen den Klassen 'Gebäude' und 'Raum' ist eine Komposition definiert.", + "question": "Welche Aussage beschreibt das Verhalten einer Komposition in UML korrekt?", + "points": 4, + "solution": "C ist korrekt: Bei der Komposition existiert eine strenge Lebenszeitabhängigkeit. Wird das Ganze (Gebäude) zerstört, erlöschen auch die Teile (Räume).", + "type": "mc", + "options": [ + "A) Ein Raum kann gleichzeitig zu mehreren Gebäuden gehören.", + "B) Wenn ein Gebäude abgerissen wird, existieren seine Räume als Objekte eigenständig weiter.", + "C) Das Teilobjekt (Raum) ist existenziell an das Ganzes-Objekt (Gebäude) gebunden und wird beim Löschen des Gebäudes mitvernichtet.", + "D) Eine Komposition wird in UML immer mit einer weißen, offenen Raute dargestellt." + ] } ] \ No newline at end of file diff --git a/assets/ihk_tools/struktogramm_editor.js b/assets/ihk_tools/struktogramm_editor.js new file mode 100644 index 0000000..e0a42fb --- /dev/null +++ b/assets/ihk_tools/struktogramm_editor.js @@ -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 = ` +
+
+
+
+ + + + +
+
+ + +
+
+
+ + + +
+
+ +
+
+
+
+
+
+

Block-Eigenschaften

+
+
+

Wähle einen Block im Struktogramm aus, um dessen Inhalt anzupassen oder Unterblöcke hinzuzufügen.

+
+
+
+ + + +
+ `; + + 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 = ` +
+
DIN 66261 Nassi-Shneiderman Struktogramm
+
+ ${this.renderBlockList(this.blocks)} +
+
+ `; + + box.innerHTML = html; + this.attachClickEvents(); + } + + renderBlockList(blocks) { + if (!blocks || blocks.length === 0) { + return `
∅ (leer)
`; + } + return blocks.map(b => this.renderSingleBlock(b)).join(''); + } + + renderSingleBlock(b) { + const isSelected = b.id === this.selectedBlockId ? 'sg-selected' : ''; + + if (b.type === 'instruction') { + return ` +
+ ${this.escapeHtml(b.text || '')} +
+ `; + } else if (b.type === 'if') { + return ` +
+
+ + + + + +
${this.escapeHtml(b.condition || '')}
+
Ja (Wahr)
+
Nein (Falsch)
+
+
+
+ ${this.renderBlockList(b.thenBlocks || [])} +
+
+ ${this.renderBlockList(b.elseBlocks || [])} +
+
+
+ `; + } else if (b.type === 'while') { + return ` +
+
+ WHILE (solange): ${this.escapeHtml(b.condition || '')} +
+
+
+
+ ${this.renderBlockList(b.body || [])} +
+
+
+ `; + } else if (b.type === 'for') { + return ` +
+
+ FOR (Zählschleife): ${this.escapeHtml(b.loopHeader || '')} +
+
+
+
+ ${this.renderBlockList(b.body || [])} +
+
+
+ `; + } + 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 = `

Klicke auf einen Block im Struktogramm, um dessen Text oder Verzweigung anzupassen.

`; + return; + } + + sidebarTitle.innerText = `Typ: ${block.type.toUpperCase()}`; + + let html = ''; + if (block.type === 'instruction') { + html = ` +
+ + +
+ `; + } else if (block.type === 'if') { + html = ` +
+ + +
+
+ + +
+ `; + } else if (block.type === 'while') { + html = ` +
+ + +
+ + `; + } else if (block.type === 'for') { + html = ` +
+ + +
+ + `; + } + + html += ` +
+ +
+ `; + + 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 = ` + + +
+ + ${htmlContent} +
+
+
+ `; + 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, '"'); + } +} + +window.StruktogrammEditor = StruktogrammEditor; diff --git a/assets/ihk_tools/uml_editor.js b/assets/ihk_tools/uml_editor.js new file mode 100644 index 0000000..73f6294 --- /dev/null +++ b/assets/ihk_tools/uml_editor.js @@ -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, <>, <>) + * 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 = ` +
+
+
+
+ + +
+
+ + +
+
+
+ + + +
+
+ +
+
+
+
+
+
+

UML Quellcode (Mermaid / PlantUML Standard)

+
+
+ +
+ 💡 Tipp: Bearbeite den Diagrammcode direkt links in der Textbox oder lade eine IHK-Vorlage. Klicke auf Neu Rendern zur Aktualisierung. +
+
+
+
+
+ `; + + 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 { + <> + #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 { + <> + +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 -.->|<>| UC3 + UC4 -.->|<>| 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 = `
Lade Diagramm-Renderer...
`; + 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 = ` +
+ ⚠️ Syntaxfehler im UML-Code: +
${err.message || err}
+
+ `; + } + } + + 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; diff --git a/assets/style.css b/assets/style.css index 10ba6fc..daa230d 100644 --- a/assets/style.css +++ b/assets/style.css @@ -162,7 +162,7 @@ a:hover { .sidebar-subtitle { font-size: 0.72rem; - color: #94a3b8; + color: #cbd5e1; text-transform: uppercase; letter-spacing: 0.5px; } @@ -197,7 +197,7 @@ a:hover { .sidebar-group-title { font-size: 0.74rem; font-weight: 800; - color: #94a3b8; + color: #cbd5e1; text-transform: uppercase; letter-spacing: 0.8px; padding: 4px 8px; @@ -595,7 +595,7 @@ pre code { .fn { color: #60a5fa; } .str { color: #a7f3d0; } .num { color: #fbbf24; } -.cm { color: #64748b; font-style: italic; } +.cm { color: #94a3b8; font-style: italic; } .var { color: #cbd5e1; } .attr { color: #38bdf8; } @@ -1138,7 +1138,7 @@ textarea:focus { text-transform: uppercase; letter-spacing: 0.6px; background: #1e293b; - color: #94a3b8; + color: #cbd5e1; border: 1px solid #334155; padding: 2px 7px; border-radius: 4px; @@ -1158,7 +1158,7 @@ textarea:focus { } .portal-nav-link { - color: #94a3b8; + color: #cbd5e1; text-decoration: none; font-size: 0.85rem; font-weight: 500; @@ -1217,7 +1217,7 @@ textarea:focus { .portal-hero p { font-size: 0.98rem; line-height: 1.6; - color: #94a3b8; + color: #cbd5e1; max-width: 720px; margin: 0; } @@ -1301,7 +1301,7 @@ textarea:focus { .portal-stat-label { font-size: 0.78rem; font-weight: 600; - color: #94a3b8; + color: #cbd5e1; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 2px; @@ -1309,7 +1309,7 @@ textarea:focus { .portal-stat-desc { font-size: 0.76rem; - color: #64748b; + color: #94a3b8; } /* Filter Bar */ @@ -2132,8 +2132,8 @@ body.has-course-sidebar { font-size: 0.68rem; font-weight: 800; text-transform: uppercase; - letter-spacing: 0.6px; - color: #64748b; + letter-spacing: 0.8px; + color: #94a3b8; padding: 12px 10px 4px 10px; } @@ -2143,7 +2143,7 @@ body.has-course-sidebar { gap: 10px; padding: 8px 10px; border-radius: 6px; - color: #94a3b8; + color: #cbd5e1; text-decoration: none; font-size: 0.82rem; font-weight: 600; diff --git a/courses/dns_records/01_dns_hierarchie_und_root_server/README.md b/courses/dns_records/01_dns_hierarchie_und_root_server/README.md index 7119ecc..68d8a90 100644 --- a/courses/dns_records/01_dns_hierarchie_und_root_server/README.md +++ b/courses/dns_records/01_dns_hierarchie_und_root_server/README.md @@ -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 ``` diff --git a/courses/dns_records/01_dns_hierarchie_und_root_server/aufgabe.sh b/courses/dns_records/01_dns_hierarchie_und_root_server/aufgabe.sh index 33ccdbb..cbfc95d 100755 --- a/courses/dns_records/01_dns_hierarchie_und_root_server/aufgabe.sh +++ b/courses/dns_records/01_dns_hierarchie_und_root_server/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= +# DOMAIN= +# SLD= +# TLD= +# HAS_ROOT_DOT= +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] . -> [SLD] .. -> [FQDN] . +# 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: " aus und return 0 +# - Wenn invalide: Gib "INVALID: " 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 diff --git a/courses/dns_records/01_dns_hierarchie_und_root_server/index.html b/courses/dns_records/01_dns_hierarchie_und_root_server/index.html index 529c313..a31c8e3 100644 --- a/courses/dns_records/01_dns_hierarchie_und_root_server/index.html +++ b/courses/dns_records/01_dns_hierarchie_und_root_server/index.html @@ -4,7 +4,7 @@ - DNS 01: DNS-Hierarchie & Root-Server + DNS 01: DNS-Hierarchie & Root-Server @@ -12,46 +12,121 @@
-
🌐 DNS Grundlagen
-

DNS 01: DNS-Hierarchie & Root-Server

-

Verstehe den globalen DNS-Baum: Root-Zone (.), TLDs (gTLD/ccTLD), SLDs, FQDNs und autoritative vs. rekursive Nameserver.

+
🌐 DNS & Infrastruktur
+

DNS 01: DNS-Hierarchie, FQDN & Root-Server

+

Verstehe den globalen DNS-Baum: Root-Zone (.), Top-Level Domains (TLD), Second-Level Domains (SLD) und Anycast Root-Cluster.

+
-

💡 Theorie & Praxiskonzept

-

Verstehe den globalen DNS-Baum: Root-Zone (.), TLDs (gTLD/ccTLD), SLDs, FQDNs und autoritative vs. rekursive Nameserver.

-
-
- DNS Command & Syntax Reference - Tool: dig -
-
# Wichtige Befehle & Zonensyntax:
-dig dig
-named-checkzone example.com /etc/bind/zones/db.example.com
+

🌳 1. Die DNS-Baumstruktur (Inverted Tree)

+

Das Domain Name System ist hierarchisch aufgebaut. An der Spitze steht nicht wie bei Dateipfaden ein Schrägstrich /, sondern der unscheinbare, aber essentielle Wurzelpunkt . (Root-Zone).

+ +
                    . (Root Zone)
+                   /          \
+                 de.          com. (Top-Level Domains)
+                 /              \
+      it-praxisportal.de.     beispiel.com. (Second-Level Domains)
+             /
+          intern. (Subdomain)
+           /
+        srv01. (Hostname / Leaf Node)
+ +
+
🏢 Analogie: Das weltweite Postleitzahlen- & Adresssystem
+

Ein FQDN liest sich im DNS von rechts nach links wie eine internationale Postadresse von unten nach oben: Zuerst das Land (.de), dann die Stadt/Organisation (it-praxisportal), dann die Abteilung (intern) und schließlich das konkrete Zimmer oder der Arbeitsplatzrechner (srv01)!

+
-

🎯 Aufgaben & Checkliste

+

📍 2. Der Fully Qualified Domain Name (FQDN)

+

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:

+ +
# 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!
+
+ + +
+

📑 3. Cheat-Sheet: DNS-Ebenen & RFC 1035 Limits

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Ebene / ParameterBeispielRFC-Regeln & Grenzwerte
Root Zone.Verwaltet durch IANA / ICANN; 13 Anycast-Cluster (A-M)
TLD (Top-Level).de, .comccTLD (Länder) oder gTLD (generisch); eigene Registry (z.B. DENIC)
SLD (Second-Level)it-praxisportalRegistrierbarer Name; verweist auf autoritative Nameserver
Label-Längemein-server-01Maximal 63 Oktette (Zeichen) pro Label zwischen Punkten
FQDN Gesamtlängehost.sub.firma.de.Maximal 253 Zeichen netto (255 Oktette binär inkl. Längenbytes)
+
+
+ + +
+

🎯 Aufgaben & Checkliste in aufgabe.sh

- -
+ +
+
- -
+ +
+
- -
+ +
+
- + +
+ +
+
💡 IHK Prüfungstipp: Warum genau 13 Root-Server-Namen?
+

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 EDNS0 wurde dieser Puffer erweitert.

diff --git a/courses/dns_records/01_dns_hierarchie_und_root_server/musterloesung.sh b/courses/dns_records/01_dns_hierarchie_und_root_server/musterloesung.sh index c72b74e..2f295e0 100755 --- a/courses/dns_records/01_dns_hierarchie_und_root_server/musterloesung.sh +++ b/courses/dns_records/01_dns_hierarchie_und_root_server/musterloesung.sh @@ -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 [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 diff --git a/courses/dns_records/01_dns_hierarchie_und_root_server/test_aufgabe.sh b/courses/dns_records/01_dns_hierarchie_und_root_server/test_aufgabe.sh index e5b3252..7817ba4 100755 --- a/courses/dns_records/01_dns_hierarchie_und_root_server/test_aufgabe.sh +++ b/courses/dns_records/01_dns_hierarchie_und_root_server/test_aufgabe.sh @@ -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 diff --git a/courses/dns_records/02_rekursive_vs_iterative_abfragen/README.md b/courses/dns_records/02_rekursive_vs_iterative_abfragen/README.md index 6e12f2b..bef1769 100644 --- a/courses/dns_records/02_rekursive_vs_iterative_abfragen/README.md +++ b/courses/dns_records/02_rekursive_vs_iterative_abfragen/README.md @@ -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 ``` diff --git a/courses/dns_records/02_rekursive_vs_iterative_abfragen/aufgabe.sh b/courses/dns_records/02_rekursive_vs_iterative_abfragen/aufgabe.sh index 65e5fe1..55db537 100755 --- a/courses/dns_records/02_rekursive_vs_iterative_abfragen/aufgabe.sh +++ b/courses/dns_records/02_rekursive_vs_iterative_abfragen/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= +# TC= +# RD= +# RA= +# 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 (.) +# SCHRITT 2: Query an TLD Server (.) -> Referral zu autoritativem Nameserver (..) +# SCHRITT 3: Query an autoritativen Server (..) -> 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 { +# ; +# ; +# }; +# 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 diff --git a/courses/dns_records/02_rekursive_vs_iterative_abfragen/index.html b/courses/dns_records/02_rekursive_vs_iterative_abfragen/index.html index eeb7def..ccfa8be 100644 --- a/courses/dns_records/02_rekursive_vs_iterative_abfragen/index.html +++ b/courses/dns_records/02_rekursive_vs_iterative_abfragen/index.html @@ -12,46 +12,113 @@
-
🌐 DNS Resolver
-

DNS 02: Rekursive vs. Iterative Abfragen

-

Der schrittweise Ablauf einer DNS-Abfrage: Stub-Resolver, Recursive Resolver, Iteration bis zum Auth-Server.

+
🌐 DNS & Infrastruktur
+

DNS 02: Rekursive vs. Iterative Abfragen & Flags

+

Verstehe den Unterschied zwischen Client-Rekursion und iterativer Baumrecherche. Beherrsche DNS-Flags und sichere Open Resolver ab.

+
-

💡 Theorie & Praxiskonzept

-

Der schrittweise Ablauf einer DNS-Abfrage: Stub-Resolver, Recursive Resolver, Iteration bis zum Auth-Server.

-
-
- DNS Command & Syntax Reference - Tool: dig +trace -
-
# Wichtige Befehle & Zonensyntax:
-dig dig +trace
-named-checkzone example.com /etc/bind/zones/db.example.com
+

🔄 1. Rekursion vs. Iteration im Detail

+

Clients (PCs, Smartphones) sind reine Stub-Resolver: Sie besitzen keine eigene DNS-Baumlogik, sondern delegieren die Arbeit mit RD=1 vollständig an einen vollwertigen Caching-Resolver.

+ +
# 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
+
+ + +
+

🛡️ 2. Das Open-Resolver-Sicherheitsrisiko

+

Wenn ein rekursiver Nameserver öffentlich im Internet steht und Anfragen von beliebigen IP-Adressen (any) beantwortet, wird er binnen kürzester Zeit für DNS Amplification DDoS missbraucht:

+ +
+
⚠️ BSI-Warnung: Open Resolver schließen!
+

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.

+
+ +

Die Lösung in BIND9 (named.conf.options):

+
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;
+};
+
+ + +
+

📑 3. Cheat-Sheet: DNS-Header-Flags

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FlagBezeichnungBedeutung in der Praxis
QRQuery / Response0 = Anfrage eines Clients; 1 = Antwortpaket eines Servers
AAAuthoritative Answer1 = Server besitzt die Original-Zone (kein Cache-Zwischenergebnis)
TCTruncated1 = Antwort passte nicht in 512B UDP; Client muss per TCP/53 wiederholen
RDRecursion Desired1 = Client bittet den Server, die Auflösung komplett zu übernehmen
RARecursion Available1 = Server beherrscht Rekursion und bietet diesen Dienst aktiv an
+
-

🎯 Aufgaben & Checkliste

+

🎯 Aufgaben & Checkliste in aufgabe.sh

- -
+ +
+
- -
+ +
+
- -
+ +
+
- +
diff --git a/courses/dns_records/02_rekursive_vs_iterative_abfragen/musterloesung.sh b/courses/dns_records/02_rekursive_vs_iterative_abfragen/musterloesung.sh index ba2309e..bad72bc 100755 --- a/courses/dns_records/02_rekursive_vs_iterative_abfragen/musterloesung.sh +++ b/courses/dns_records/02_rekursive_vs_iterative_abfragen/musterloesung.sh @@ -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 diff --git a/courses/dns_records/02_rekursive_vs_iterative_abfragen/test_aufgabe.sh b/courses/dns_records/02_rekursive_vs_iterative_abfragen/test_aufgabe.sh index 66bbd8a..ecbbae3 100755 --- a/courses/dns_records/02_rekursive_vs_iterative_abfragen/test_aufgabe.sh +++ b/courses/dns_records/02_rekursive_vs_iterative_abfragen/test_aufgabe.sh @@ -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 diff --git a/courses/dns_records/03_ports_udp_tcp_und_dns_paketaufbau/README.md b/courses/dns_records/03_ports_udp_tcp_und_dns_paketaufbau/README.md index d37a571..d1f76b2 100644 --- a/courses/dns_records/03_ports_udp_tcp_und_dns_paketaufbau/README.md +++ b/courses/dns_records/03_ports_udp_tcp_und_dns_paketaufbau/README.md @@ -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 ``` diff --git a/courses/dns_records/03_ports_udp_tcp_und_dns_paketaufbau/aufgabe.sh b/courses/dns_records/03_ports_udp_tcp_und_dns_paketaufbau/aufgabe.sh index b32eb90..84d685e 100755 --- a/courses/dns_records/03_ports_udp_tcp_und_dns_paketaufbau/aufgabe.sh +++ b/courses/dns_records/03_ports_udp_tcp_und_dns_paketaufbau/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= +# ANSWERS= +# AUTHORITY= +# ADDITIONAL= +# STATUS= +# 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 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 "" { +# type master; +# file "/var/lib/bind/db."; +# allow-transfer { ; }; +# }; +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 diff --git a/courses/dns_records/03_ports_udp_tcp_und_dns_paketaufbau/index.html b/courses/dns_records/03_ports_udp_tcp_und_dns_paketaufbau/index.html index 28ef06c..e507472 100644 --- a/courses/dns_records/03_ports_udp_tcp_und_dns_paketaufbau/index.html +++ b/courses/dns_records/03_ports_udp_tcp_und_dns_paketaufbau/index.html @@ -4,7 +4,7 @@ - DNS 03: UDP vs. TCP Port 53 & EDNS0 + DNS 03: UDP vs. TCP Port 53 & EDNS0 @@ -12,46 +12,101 @@
-
🌐 DNS Netzwerkprotokoll
-

DNS 03: UDP vs. TCP Port 53 & EDNS0

-

Transportprotokolle: Port 53 UDP für Standardabfragen (< 512 Bytes), Port 53 TCP für Zonentransfers (AXFR) & DNSSEC.

+
🌐 DNS & Infrastruktur
+

DNS 03: UDP vs. TCP Port 53, Truncation & EDNS0

+

Lerne, wann DNS von UDP auf TCP umschaltet, wie Truncation (TC=1) funktioniert und warum EDNS0 den Puffer auf 4096 Bytes erweitert.

+
-

💡 Theorie & Praxiskonzept

-

Transportprotokolle: Port 53 UDP für Standardabfragen (< 512 Bytes), Port 53 TCP für Zonentransfers (AXFR) & DNSSEC.

-
-
- DNS Command & Syntax Reference - Tool: dig +tcp -
-
# Wichtige Befehle & Zonensyntax:
-dig dig +tcp
-named-checkzone example.com /etc/bind/zones/db.example.com
+

⚡ 1. Port 53: Wann UDP, wann TCP?

+

Ein weit verbreiteter Irrglaube ist, DNS laufe ausschließlich über UDP. In der Praxis benötigt jeder vollwertige DNS-Server beide Transportprotokolle auf Port 53:

+ +
# 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
+
+ + +
+

📦 2. Das 512-Byte Limit & EDNS0 (RFC 6891)

+

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.

+ +
+
📦 Analogie: Der Briefumschlag und das Paket
+

Standard UDP: Ein Standard-Briefumschlag (512 Bytes). Passt der Vertrag nicht hinein, schneidet der Postbote die Seiten ab, klebt den Stempel TC=1 (Truncated) drauf und sagt: „Komm bitte mit einem Paketwagen (TCP) wieder!“

+

EDNS0: Mit EDNS0 schreibt der Empfänger auf den Umschlag: „Ich habe einen großen Briefkasten (4096 Bytes). Du kannst mir dicke Briefe per Standard-Post senden!“

+
-

🎯 Aufgaben & Checkliste

+

📑 3. Cheat-Sheet: Transport-Entscheidungsmatrix

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SzenarioProtokollGrund & Verhalten
Standard-Query (≤ 512 Bytes)UDP / 53Maximale Performance ohne TCP-Handshake
Antwort > 512 Bytes (ohne EDNS0)UDP -> TCPServer setzt TC=1; Client wiederholt per TCP
Antwort > 512B (mit EDNS0 ≤ 4096B)UDP (EDNS0)Erweiterter Puffer via OPT Pseudo-Record
Zonentransfer (AXFR / IXFR)TCP / 53Zwingend TCP wegen Zuverlässigkeit & Datenmenge
+
+
+ + +
+

🎯 Aufgaben & Checkliste in aufgabe.sh

- -
+ +
+
- -
+ +
+
- -
+ +
+
- +
diff --git a/courses/dns_records/03_ports_udp_tcp_und_dns_paketaufbau/musterloesung.sh b/courses/dns_records/03_ports_udp_tcp_und_dns_paketaufbau/musterloesung.sh index f0da25a..6eec3f7 100755 --- a/courses/dns_records/03_ports_udp_tcp_und_dns_paketaufbau/musterloesung.sh +++ b/courses/dns_records/03_ports_udp_tcp_und_dns_paketaufbau/musterloesung.sh @@ -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 diff --git a/courses/dns_records/03_ports_udp_tcp_und_dns_paketaufbau/test_aufgabe.sh b/courses/dns_records/03_ports_udp_tcp_und_dns_paketaufbau/test_aufgabe.sh index a01f549..8cde185 100755 --- a/courses/dns_records/03_ports_udp_tcp_und_dns_paketaufbau/test_aufgabe.sh +++ b/courses/dns_records/03_ports_udp_tcp_und_dns_paketaufbau/test_aufgabe.sh @@ -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 diff --git a/courses/dns_records/04_ttl_und_dns_propagation/README.md b/courses/dns_records/04_ttl_und_dns_propagation/README.md index 9914bc7..09c0447 100644 --- a/courses/dns_records/04_ttl_und_dns_propagation/README.md +++ b/courses/dns_records/04_ttl_und_dns_propagation/README.md @@ -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 ``` diff --git a/courses/dns_records/04_ttl_und_dns_propagation/aufgabe.sh b/courses/dns_records/04_ttl_und_dns_propagation/aufgabe.sh index e44f88f..de6d4fb 100755 --- a/courses/dns_records/04_ttl_und_dns_propagation/aufgabe.sh +++ b/courses/dns_records/04_ttl_und_dns_propagation/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 h Vorlaufzeit erforderlich." +# return 1 +# * Andernfalls gib strukturiert aus: +# ADVANCE_HOURS= +# TEMP_TTL=300 +# STATUS=READY_TO_SCHEDULE +# EMPFEHLUNG=Senke die TTL 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 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=s | COMPLIANT=YES" +# return 0 +# * Wenn neg_ttl < 600 oder neg_ttl > 86400: +# Gib aus: "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 diff --git a/courses/dns_records/04_ttl_und_dns_propagation/index.html b/courses/dns_records/04_ttl_und_dns_propagation/index.html index 53037b2..e04fe9e 100644 --- a/courses/dns_records/04_ttl_und_dns_propagation/index.html +++ b/courses/dns_records/04_ttl_und_dns_propagation/index.html @@ -4,7 +4,7 @@ - DNS 04: TTL & DNS-Propagation + DNS 04: TTL & DNS-Propagation @@ -12,46 +12,108 @@
-
🌐 DNS Caching
-

DNS 04: TTL & DNS-Propagation

-

Time-To-Live (TTL), Cache-Invalidierung, Negative Caching (RFC 2308) und Migrationsstrategien.

+
🌐 DNS & Infrastruktur
+

DNS 04: TTL (Time to Live) & DNS-Propagation

+

Verstehe Caching-Mechanismen, plane Servermigrationen ohne Downtime und analysiere Negative Caching nach RFC 2308.

+
-

💡 Theorie & Praxiskonzept

-

Time-To-Live (TTL), Cache-Invalidierung, Negative Caching (RFC 2308) und Migrationsstrategien.

-
-
- DNS Command & Syntax Reference - Tool: dig +nocache -
-
# Wichtige Befehle & Zonensyntax:
-dig dig +nocache
-named-checkzone example.com /etc/bind/zones/db.example.com
+

⏳ 1. Caching & die Time to Live (TTL)

+

Die TTL bestimmt, wie lange ein rekursiver Resolver einen Record zwischenspeichern darf. Mit dig kannst du beobachten, wie die TTL im Cache bei jeder Abfrage abnimmt:

+ +
# 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!
+
+ + +
+

🚀 2. Professioneller Serverumzug ohne Downtime

+

Wenn du einen produktiven Web- oder Mailserver migrierst, ist die TTL dein wichtigstes Werkzeug:

+ +
+
💡 Der 3-Schritte-Migrationsplan für Systemintegratoren
+
    +
  1. T minus 48 Stunden: Senke die TTL des A/AAAA-Records von 86.400s (24h) auf 300s (5 Min.). Alle alten Caches laufen vor dem Umzug ab.
  2. +
  3. Wartungsfenster (Tag T): Ändere die IP-Adresse auf den neuen Server. Nach spätestens 5 Minuten sprechen alle Clients weltweit den neuen Server an!
  4. +
  5. T plus 24 Stunden: Sobald alles stabil läuft, hebe die TTL wieder auf 86.400s an, um die DNS-Last zu minimieren.
  6. +
+
-

🎯 Aufgaben & Checkliste

+

📑 3. Cheat-Sheet: Typische TTL-Richtwerte in der Praxis

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Record-TypEmpfohlene TTLBegründung
NS / SOA86.400s (24h) bis 172.800s (48h)Nameserver-Infrastruktur ändert sich extrem selten; entlastet TLDs
MX14.400s (4h) bis 86.400s (24h)Mailserver-Routen sind stabil; Backup-MX fängt Ausfälle ab
Standard A / AAAA3.600s (1h) bis 14.400s (4h)Gute Balance zwischen Lastreduktion und Flexibilität
Migrations-Vorbereitung300s (5 Min.) bis 600s (10 Min.)Ermöglicht schnellen IP-Wechsel bei minimaler Propagation-Downtime
Negative Caching (NXDOMAIN)1.800s (30m) bis 7.200s (2h)5. Feld im SOA; verhindert Query-Fluten bei Tippfehlern
+
+
+ + +
+

🎯 Aufgaben & Checkliste in aufgabe.sh

- -
+ +
+
- -
+ +
+
- -
+ +
+
- +
diff --git a/courses/dns_records/04_ttl_und_dns_propagation/musterloesung.sh b/courses/dns_records/04_ttl_und_dns_propagation/musterloesung.sh index 0a7d923..697f766 100755 --- a/courses/dns_records/04_ttl_und_dns_propagation/musterloesung.sh +++ b/courses/dns_records/04_ttl_und_dns_propagation/musterloesung.sh @@ -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 diff --git a/courses/dns_records/04_ttl_und_dns_propagation/test_aufgabe.sh b/courses/dns_records/04_ttl_und_dns_propagation/test_aufgabe.sh index 4144bc9..5992812 100755 --- a/courses/dns_records/04_ttl_und_dns_propagation/test_aufgabe.sh +++ b/courses/dns_records/04_ttl_und_dns_propagation/test_aufgabe.sh @@ -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 diff --git a/courses/dns_records/05_a_und_aaaa_records/README.md b/courses/dns_records/05_a_und_aaaa_records/README.md index ff511c9..d71c607 100644 --- a/courses/dns_records/05_a_und_aaaa_records/README.md +++ b/courses/dns_records/05_a_und_aaaa_records/README.md @@ -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 ``` diff --git a/courses/dns_records/05_a_und_aaaa_records/aufgabe.sh b/courses/dns_records/05_a_und_aaaa_records/aufgabe.sh index 8983ee2..7946436 100755 --- a/courses/dns_records/05_a_und_aaaa_records/aufgabe.sh +++ b/courses/dns_records/05_a_und_aaaa_records/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: +# IN A +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: +# IN AAAA +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): " +# 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 diff --git a/courses/dns_records/05_a_und_aaaa_records/index.html b/courses/dns_records/05_a_und_aaaa_records/index.html index ff2a362..01c4cfc 100644 --- a/courses/dns_records/05_a_und_aaaa_records/index.html +++ b/courses/dns_records/05_a_und_aaaa_records/index.html @@ -4,7 +4,7 @@ - DNS 05: A & AAAA Records (IPv4 / IPv6) + DNS 05: A & AAAA Records @@ -13,45 +13,99 @@
⚡ Resource Records
-

DNS 05: A & AAAA Records (IPv4 / IPv6)

-

Das Fundament des Web: Hostname zu IPv4 (A) und IPv6 (AAAA) sowie DNS Round-Robin Lastverteilung.

+

DNS 05: A- & AAAA-Records (IPv4 & IPv6 Dual-Stack)

+

Beherrsche die fundamentalen Forward-Lookup Records: IPv4 32-Bit (A) und IPv6 128-Bit (AAAA), Round-Robin Load Balancing und Dual-Stack Auditing.

+
-

💡 Theorie & Praxiskonzept

-

Das Fundament des Web: Hostname zu IPv4 (A) und IPv6 (AAAA) sowie DNS Round-Robin Lastverteilung.

-
-
- DNS Command & Syntax Reference - Tool: named-checkzone -
-
# Wichtige Befehle & Zonensyntax:
-dig named-checkzone
-named-checkzone example.com /etc/bind/zones/db.example.com
+

🌐 1. Vorwärtsauflösung: A vs. AAAA

+

Über 95% aller DNS-Anfragen im Web betreffen A- und AAAA-Records. Moderne Webdienste müssen zwingend **Dual-Stack** erreichbar sein:

+ +
# 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
+
+ + +
+

⚖️ 2. Round-Robin DNS Lastverteilung

+

Durch Hinterlegen mehrerer A-Records unter demselben Namen rotiert der Nameserver die Antwortreihenfolge (Round-Robin):

+
# 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
+ +
+
⚠️ Wichtig für die IHK-Prüfung: Grenzen von Round-Robin DNS
+

DNS Round-Robin prüft keinen Server-Health-Status! 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.

+
-

🎯 Aufgaben & Checkliste

+

📑 3. Cheat-Sheet: A- & AAAA-Syntax

+
+ + + + + + + + + + + + + + + + + + + + + + + +
RecordAdressformatBitbreiteBeispiel
ADotted-Decimal (IPv4)32 Bit (4 Oktette)web IN A 192.0.2.1
AAAAHexadezimal / Doppelpunkt (IPv6)128 Bit (16 Oktette)web IN AAAA 2001:db8::1
+
+
+ + +
+

🎯 Aufgaben & Checkliste in aufgabe.sh

- -
+ +
+
- -
+ +
+
- -
+ +
+
- +
diff --git a/courses/dns_records/05_a_und_aaaa_records/musterloesung.sh b/courses/dns_records/05_a_und_aaaa_records/musterloesung.sh index 21b7c13..910accf 100755 --- a/courses/dns_records/05_a_und_aaaa_records/musterloesung.sh +++ b/courses/dns_records/05_a_und_aaaa_records/musterloesung.sh @@ -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 diff --git a/courses/dns_records/05_a_und_aaaa_records/test_aufgabe.sh b/courses/dns_records/05_a_und_aaaa_records/test_aufgabe.sh index a7f6da8..d40d493 100755 --- a/courses/dns_records/05_a_und_aaaa_records/test_aufgabe.sh +++ b/courses/dns_records/05_a_und_aaaa_records/test_aufgabe.sh @@ -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 diff --git a/courses/dns_records/06_cname_canonical_name_und_alias_regeln/README.md b/courses/dns_records/06_cname_canonical_name_und_alias_regeln/README.md index ec00834..7c0270e 100644 --- a/courses/dns_records/06_cname_canonical_name_und_alias_regeln/README.md +++ b/courses/dns_records/06_cname_canonical_name_und_alias_regeln/README.md @@ -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 ``` diff --git a/courses/dns_records/06_cname_canonical_name_und_alias_regeln/aufgabe.sh b/courses/dns_records/06_cname_canonical_name_und_alias_regeln/aufgabe.sh index 6e16296..323ec50 100755 --- a/courses/dns_records/06_cname_canonical_name_und_alias_regeln/aufgabe.sh +++ b/courses/dns_records/06_cname_canonical_name_und_alias_regeln/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: +# IN CNAME . +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 '' 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: -> ... -> " +# - Ausgabe bei Loop: "ERROR_LOOP_DETECTED: Zirkuläre CNAME-Referenz bei ''!" 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 diff --git a/courses/dns_records/06_cname_canonical_name_und_alias_regeln/index.html b/courses/dns_records/06_cname_canonical_name_und_alias_regeln/index.html index b36ef1a..1462d7c 100644 --- a/courses/dns_records/06_cname_canonical_name_und_alias_regeln/index.html +++ b/courses/dns_records/06_cname_canonical_name_und_alias_regeln/index.html @@ -4,7 +4,7 @@ - DNS 06: CNAME Alias & das Apex-Problem + DNS 06: CNAME & Zone-Apex Problem @@ -12,46 +12,102 @@
-
⚡ CNAME & Aliase
-

DNS 06: CNAME Alias & das Apex-Problem

-

Kanonische Namen (CNAME), Zeiger auf Hostnames, Alias-Kollisionen und das Apex-/Root-Domain-Verbot (RFC 1912).

+
⚡ Resource Records
+

DNS 06: CNAME Alias & das Zone-Apex Problem

+

Verstehe die strikte RFC-Koexistenzregel für CNAMEs, warum CNAME auf der Root-Domain (@) verboten ist und wie ALIAS-Records das Problem lösen.

+
-

💡 Theorie & Praxiskonzept

-

Kanonische Namen (CNAME), Zeiger auf Hostnames, Alias-Kollisionen und das Apex-/Root-Domain-Verbot (RFC 1912).

-
-
- DNS Command & Syntax Reference - Tool: dig CNAME -
-
# Wichtige Befehle & Zonensyntax:
-dig dig CNAME
-named-checkzone example.com /etc/bind/zones/db.example.com
+

🔗 1. Wie ein CNAME funktioniert

+

Ein CNAME (Canonical Name) ist ein reiner Verweiszeiger. Er enthält selbst keine IP-Adresse, sondern leitet den Resolver an ein anderes Ziel weiter:

+ +
# 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
+
+ + +
+

💥 2. Das Zone-Apex Problem & RFC 1912

+
+
⚠️ Die eiserne Regel: Keine Koexistenz für CNAMEs
+

Ein CNAME darf niemals mit anderen Record-Typen auf demselben Node koexistieren! Da auf der Hauptdomain @ aber zwingend SOA und NS liegen müssen, ist ein CNAME auf @ standardwidrig und zerstört die Namensauflösung der gesamten Zone.

+
+ +
; ❌ 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
+
+ + +
+

📑 3. Cheat-Sheet: CNAME Dos & Don'ts

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
RegelStatusErklärung & RFC-Grund
blog IN CNAME ...ERLAUBTReine Subdomain ohne andere Records
@ IN CNAME ...VERBOTENZone-Apex kollidiert mit SOA und NS Records (RFC 1912)
sub IN CNAME + sub IN TXTVERBOTENKeine Koexistenz von CNAME mit anderen Daten
Ziel ohne Schlusspunkt .GEFÄHRLICHBIND hängt Zonennamen an: target.com.firma.de.
+
-

🎯 Aufgaben & Checkliste

+

🎯 Aufgaben & Checkliste in aufgabe.sh

- -
+ +
+
- -
+ +
+
- -
+ +
+
- +
diff --git a/courses/dns_records/06_cname_canonical_name_und_alias_regeln/musterloesung.sh b/courses/dns_records/06_cname_canonical_name_und_alias_regeln/musterloesung.sh index 8a3f283..e8dd966 100755 --- a/courses/dns_records/06_cname_canonical_name_und_alias_regeln/musterloesung.sh +++ b/courses/dns_records/06_cname_canonical_name_und_alias_regeln/musterloesung.sh @@ -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 diff --git a/courses/dns_records/06_cname_canonical_name_und_alias_regeln/test_aufgabe.sh b/courses/dns_records/06_cname_canonical_name_und_alias_regeln/test_aufgabe.sh index 2a2e4ea..8166d61 100755 --- a/courses/dns_records/06_cname_canonical_name_und_alias_regeln/test_aufgabe.sh +++ b/courses/dns_records/06_cname_canonical_name_und_alias_regeln/test_aufgabe.sh @@ -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 diff --git a/courses/dns_records/07_soa_start_of_authority_und_serial/README.md b/courses/dns_records/07_soa_start_of_authority_und_serial/README.md index 37ddb28..696a755 100644 --- a/courses/dns_records/07_soa_start_of_authority_und_serial/README.md +++ b/courses/dns_records/07_soa_start_of_authority_und_serial/README.md @@ -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 ``` diff --git a/courses/dns_records/07_soa_start_of_authority_und_serial/aufgabe.sh b/courses/dns_records/07_soa_start_of_authority_und_serial/aufgabe.sh index c6e081b..cf3f2e3 100755 --- a/courses/dns_records/07_soa_start_of_authority_und_serial/aufgabe.sh +++ b/courses/dns_records/07_soa_start_of_authority_und_serial/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 . . ( +# ; Serial +# ; Refresh +# ; Retry +# ; Expire +# ; 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 diff --git a/courses/dns_records/07_soa_start_of_authority_und_serial/index.html b/courses/dns_records/07_soa_start_of_authority_und_serial/index.html index 40f9e10..c034d47 100644 --- a/courses/dns_records/07_soa_start_of_authority_und_serial/index.html +++ b/courses/dns_records/07_soa_start_of_authority_und_serial/index.html @@ -4,7 +4,7 @@ - DNS 07: SOA Start of Authority & Serial + DNS 07: SOA Record & Serial @@ -12,46 +12,102 @@
-
⚡ Zonen-Header
-

DNS 07: SOA Start of Authority & Serial

-

Der wichtigste Record jeder Zone: Primary Master, Hostmaster-E-Mail, Seriennummer (YYYYMMDDNN) und Timer.

+
⚡ Resource Records
+

DNS 07: SOA (Start of Authority) & Serial

+

Verstehe den administrativen Kopf jeder Zonendatei: Primary Master, Hostmaster-E-Mail, Revisions-Serials und Zonentransfer-Timer.

+
-

💡 Theorie & Praxiskonzept

-

Der wichtigste Record jeder Zone: Primary Master, Hostmaster-E-Mail, Seriennummer (YYYYMMDDNN) und Timer.

-
-
- DNS Command & Syntax Reference - Tool: named-compilezone -
-
# Wichtige Befehle & Zonensyntax:
-dig named-compilezone
-named-checkzone example.com /etc/bind/zones/db.example.com
+

👑 1. Die Anatomie des SOA-Records

+

Der SOA-Record steht ausnahmslos an der Spitze jeder Forward- und Reverse-Zonendatei. Er legt fest, wer autoritativer Master ist und wie Slaves synchronisieren:

+ +
; 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)
+
+ + +
+

⚠️ 2. Die Serial-Falle in der Systemadministration

+
+
IHK-Prüfungsfalle: Warum aktualisieren Slaves nicht?
+

Ein Administrator ändert auf dem Master-Server die IP-Adresse von mail.firma.de, vergisst aber, die Serial-Nummer im SOA zu erhöhen. Die Secondary-Nameserver vergleichen ihre Serial mit dem Master, stellen keinen Versionsunterschied fest und liefern wochenlang weiterhin die alte IP aus!

+
-

🎯 Aufgaben & Checkliste

+

📑 3. Cheat-Sheet: Die 5 SOA-Parameter

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FeldTypischer WertFunktion & Bedeutung
SerialYYYYMMDDNNMuss bei jeder Zonenänderung monoton steigen
Refresh7200s (2h)Intervall, in dem Slaves den Master nach neuer Serial abfragen
Retry3600s (1h)Wartezeit nach fehlgeschlagener Kontaktaufnahme
Expire1209600s (14d)Zeit, nach der Slaves aufhören zu antworten, wenn Master tot bleibt
Min. TTL3600s (1h)Negative Caching TTL für NXDOMAIN nach RFC 2308
+
+
+ + +
+

🎯 Aufgaben & Checkliste in aufgabe.sh

- -
+ +
+
- -
+ +
+
- -
+ +
+
- +
diff --git a/courses/dns_records/07_soa_start_of_authority_und_serial/musterloesung.sh b/courses/dns_records/07_soa_start_of_authority_und_serial/musterloesung.sh index b1918af..33d63a0 100755 --- a/courses/dns_records/07_soa_start_of_authority_und_serial/musterloesung.sh +++ b/courses/dns_records/07_soa_start_of_authority_und_serial/musterloesung.sh @@ -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 diff --git a/courses/dns_records/07_soa_start_of_authority_und_serial/test_aufgabe.sh b/courses/dns_records/07_soa_start_of_authority_und_serial/test_aufgabe.sh index d232727..92d7d4a 100755 --- a/courses/dns_records/07_soa_start_of_authority_und_serial/test_aufgabe.sh +++ b/courses/dns_records/07_soa_start_of_authority_und_serial/test_aufgabe.sh @@ -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 diff --git a/courses/dns_records/08_ns_und_ptr_reverse_dns_lookups/README.md b/courses/dns_records/08_ns_und_ptr_reverse_dns_lookups/README.md index 2950af7..63557fd 100644 --- a/courses/dns_records/08_ns_und_ptr_reverse_dns_lookups/README.md +++ b/courses/dns_records/08_ns_und_ptr_reverse_dns_lookups/README.md @@ -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 ``` diff --git a/courses/dns_records/08_ns_und_ptr_reverse_dns_lookups/aufgabe.sh b/courses/dns_records/08_ns_und_ptr_reverse_dns_lookups/aufgabe.sh index 78a4709..a54d175 100755 --- a/courses/dns_records/08_ns_und_ptr_reverse_dns_lookups/aufgabe.sh +++ b/courses/dns_records/08_ns_und_ptr_reverse_dns_lookups/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: +# IN PTR . +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 diff --git a/courses/dns_records/08_ns_und_ptr_reverse_dns_lookups/index.html b/courses/dns_records/08_ns_und_ptr_reverse_dns_lookups/index.html index 2eaddad..647e868 100644 --- a/courses/dns_records/08_ns_und_ptr_reverse_dns_lookups/index.html +++ b/courses/dns_records/08_ns_und_ptr_reverse_dns_lookups/index.html @@ -4,7 +4,7 @@ - DNS 08: NS Delegation & PTR Reverse DNS + DNS 08: NS & PTR Reverse DNS Lookups @@ -12,46 +12,92 @@
-
⚡ Nameserver & PTR
-

DNS 08: NS Delegation & PTR Reverse DNS

-

Delegierung an Nameserver (NS) und Reverse-Lookup-Zonen (in-addr.arpa / ip6.arpa) für FCrDNS.

+
⚡ Resource Records
+

DNS 08: NS-Delegation, PTR Reverse DNS & FCrDNS

+

Verstehe Zonen-Delegation mit redundanten Nameservern, die Oktett-Umkehrung in in-addr.arpa und Mailserver-Validierung via FCrDNS.

+
-

💡 Theorie & Praxiskonzept

-

Delegierung an Nameserver (NS) und Reverse-Lookup-Zonen (in-addr.arpa / ip6.arpa) für FCrDNS.

-
-
- DNS Command & Syntax Reference - Tool: dig -x -
-
# Wichtige Befehle & Zonensyntax:
-dig dig -x
-named-checkzone example.com /etc/bind/zones/db.example.com
+

🔄 1. Reverse DNS & in-addr.arpa

+

Wie findet man heraus, welcher Server hinter der IP 188.245.100.5 steckt? Über die spezielle Reverse-Lookup-Zone in-addr.arpa:

+ +
# 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
+
+ + +
+

🛡️ 2. Forward-Confirmed Reverse DNS (FCrDNS)

+

FCrDNS ist der De-facto-Standard für Spam-Schutz im E-Mail-Verkehr:

+
+
💡 Warum fast alle Mails ohne PTR im Spam landen
+

Jeder Serverbetreiber kann im SMTP-Handshake behaupten: HELO mail.google.com. 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!

+
-

🎯 Aufgaben & Checkliste

+

📑 3. Cheat-Sheet: NS & PTR Syntax

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Record-TypZweckSyntax-Beispiel
NSZonen-Delegationfirma.de. IN NS ns1.provider.de.
PTR (IPv4)Reverse DNS (IP → Name)42.2.0.192.in-addr.arpa. IN PTR srv.de.
PTR (IPv6)Reverse DNS (IPv6)1.0.0.0...b.d.0.1.0.0.2.ip6.arpa. IN PTR srv.de.
+
+
+ + +
+

🎯 Aufgaben & Checkliste in aufgabe.sh

- -
+ +
+
- -
+ +
+
- -
+ +
+
- +
diff --git a/courses/dns_records/08_ns_und_ptr_reverse_dns_lookups/musterloesung.sh b/courses/dns_records/08_ns_und_ptr_reverse_dns_lookups/musterloesung.sh index 0b7b965..ca4464d 100755 --- a/courses/dns_records/08_ns_und_ptr_reverse_dns_lookups/musterloesung.sh +++ b/courses/dns_records/08_ns_und_ptr_reverse_dns_lookups/musterloesung.sh @@ -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 diff --git a/courses/dns_records/08_ns_und_ptr_reverse_dns_lookups/test_aufgabe.sh b/courses/dns_records/08_ns_und_ptr_reverse_dns_lookups/test_aufgabe.sh index 247bf45..93f3f7a 100755 --- a/courses/dns_records/08_ns_und_ptr_reverse_dns_lookups/test_aufgabe.sh +++ b/courses/dns_records/08_ns_und_ptr_reverse_dns_lookups/test_aufgabe.sh @@ -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 diff --git a/courses/dns_records/09_mx_mail_exchange_und_prioritaeten/README.md b/courses/dns_records/09_mx_mail_exchange_und_prioritaeten/README.md index 2b22992..3a70ab8 100644 --- a/courses/dns_records/09_mx_mail_exchange_und_prioritaeten/README.md +++ b/courses/dns_records/09_mx_mail_exchange_und_prioritaeten/README.md @@ -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 ``` diff --git a/courses/dns_records/09_mx_mail_exchange_und_prioritaeten/aufgabe.sh b/courses/dns_records/09_mx_mail_exchange_und_prioritaeten/aufgabe.sh index 09bbf66..4dc23c7 100755 --- a/courses/dns_records/09_mx_mail_exchange_und_prioritaeten/aufgabe.sh +++ b/courses/dns_records/09_mx_mail_exchange_und_prioritaeten/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: +# IN MX . +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= | 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 diff --git a/courses/dns_records/09_mx_mail_exchange_und_prioritaeten/index.html b/courses/dns_records/09_mx_mail_exchange_und_prioritaeten/index.html index e20c1d9..1ef673a 100644 --- a/courses/dns_records/09_mx_mail_exchange_und_prioritaeten/index.html +++ b/courses/dns_records/09_mx_mail_exchange_und_prioritaeten/index.html @@ -4,7 +4,7 @@ - DNS 09: MX Records & Mail-Prioritäten + DNS 09: MX Records & Mail-Prioritäten @@ -12,46 +12,92 @@
-
🛡️ Mail Routing
-

DNS 09: MX Records & Mail-Prioritäten

-

Mail Exchange (MX) Records, Prioritätswerte (10, 20), Backup-Mailserver und Fallback-Routing.

+
🛡️ E-Mail-Sicherheit
+

DNS 09: MX-Records & Mail-Prioritäten

+

Lerne, wie der globale Mail-Austausch gesteuert wird: Prioritäts-Routing, Store-and-Forward Redundanz und die RFC-Regel zum CNAME-Verbot.

+
-

💡 Theorie & Praxiskonzept

-

Mail Exchange (MX) Records, Prioritätswerte (10, 20), Backup-Mailserver und Fallback-Routing.

-
-
- DNS Command & Syntax Reference - Tool: dig MX -
-
# Wichtige Befehle & Zonensyntax:
-dig dig MX
-named-checkzone example.com /etc/bind/zones/db.example.com
+

✉️ 1. Der MX-Record: E-Mail-Routing im Internet

+

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:

+ +
# 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
+
+ + +
+

⚠️ 2. Die CNAME-Falle bei MX-Zielen

+
+
Häufiger IHK-Prüfungsfehler: MX auf CNAME
+

Ein Administrator trägt ein: @ IN MX 10 mail.firma.de. und legt gleichzeitig an: mail IN CNAME office365.com.. Das verstößt gegen RFC 2181! Viele Enterprise-Mailserver brechen die Zustellung sofort mit 554 Delivery Failure ab.

+
-

🎯 Aufgaben & Checkliste

+

📑 3. Cheat-Sheet: MX-Prioritäten

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
PrioritätswertRolleVerhalten des sendenden Servers
10 (Niedrigste Zahl)Primary MTAWird immer zuerst kontaktiert
20 (Höhere Zahl)Secondary / BackupWird kontaktiert, wenn Prio 10 nach Timeouts nicht antwortet
Gleiche Zahl (z.B. zweimal 10)Load BalancingSender verteilt Mails zufällig / gleichmäßig auf beide Server
+
+
+ + +
+

🎯 Aufgaben & Checkliste in aufgabe.sh

- -
+ +
+
- -
+ +
+
- -
+ +
+
- +
diff --git a/courses/dns_records/09_mx_mail_exchange_und_prioritaeten/musterloesung.sh b/courses/dns_records/09_mx_mail_exchange_und_prioritaeten/musterloesung.sh index d666162..ce2a27f 100755 --- a/courses/dns_records/09_mx_mail_exchange_und_prioritaeten/musterloesung.sh +++ b/courses/dns_records/09_mx_mail_exchange_und_prioritaeten/musterloesung.sh @@ -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 diff --git a/courses/dns_records/09_mx_mail_exchange_und_prioritaeten/test_aufgabe.sh b/courses/dns_records/09_mx_mail_exchange_und_prioritaeten/test_aufgabe.sh index 80565b8..a4ff386 100755 --- a/courses/dns_records/09_mx_mail_exchange_und_prioritaeten/test_aufgabe.sh +++ b/courses/dns_records/09_mx_mail_exchange_und_prioritaeten/test_aufgabe.sh @@ -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 diff --git a/courses/dns_records/10_txt_records_und_spf_sender_policy_framework/README.md b/courses/dns_records/10_txt_records_und_spf_sender_policy_framework/README.md index 47cabaf..ebbbf25 100644 --- a/courses/dns_records/10_txt_records_und_spf_sender_policy_framework/README.md +++ b/courses/dns_records/10_txt_records_und_spf_sender_policy_framework/README.md @@ -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:`: Erlaubt konkrete IPv4-Adressen oder Subnetze. +- `include:`: Ü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 ``` diff --git a/courses/dns_records/10_txt_records_und_spf_sender_policy_framework/aufgabe.sh b/courses/dns_records/10_txt_records_und_spf_sender_policy_framework/aufgabe.sh index 192a7ea..699890c 100755 --- a/courses/dns_records/10_txt_records_und_spf_sender_policy_framework/aufgabe.sh +++ b/courses/dns_records/10_txt_records_und_spf_sender_policy_framework/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:/" : Konkrete IPv4-Adressen oder Netze +# - "ip6:" : IPv6-Adressen +# - "include:" : Ü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:...] +# 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= | RFC7208_OK" +# return 0 +# - Wenn count > 10: +# Gib aus: "LOOKUP_LIMIT_EXCEEDED: 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: +# IN TXT "" +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 diff --git a/courses/dns_records/10_txt_records_und_spf_sender_policy_framework/index.html b/courses/dns_records/10_txt_records_und_spf_sender_policy_framework/index.html index 1ec507b..f0390ff 100644 --- a/courses/dns_records/10_txt_records_und_spf_sender_policy_framework/index.html +++ b/courses/dns_records/10_txt_records_und_spf_sender_policy_framework/index.html @@ -4,7 +4,7 @@ - DNS 10: SPF Sender Policy Framework (TXT) + DNS 10: SPF Sender Policy Framework @@ -12,46 +12,104 @@
-
🛡️ SPF Schutz
-

DNS 10: SPF Sender Policy Framework (TXT)

-

Schutz vor E-Mail-Spoofing: Autorisierte Absender-IPs im TXT-Record definieren (v=spf1 mx ip4:... -all).

+
🛡️ E-Mail-Sicherheit
+

DNS 10: SPF (Sender Policy Framework) & 10-Lookup Limit

+

Verhindere Mail-Spoofing mit TXT-Records: Lerne die SPF-Syntax, sichere Qualifier (-all) und das gefürchtete 10-DNS-Lookup-Limit nach RFC 7208.

+
-

💡 Theorie & Praxiskonzept

-

Schutz vor E-Mail-Spoofing: Autorisierte Absender-IPs im TXT-Record definieren (v=spf1 mx ip4:... -all).

-
-
- DNS Command & Syntax Reference - Tool: dig TXT -
-
# Wichtige Befehle & Zonensyntax:
-dig dig TXT
-named-checkzone example.com /etc/bind/zones/db.example.com
+

🛡️ 1. Anatomie eines professionellen SPF-Records

+

SPF wird als Standard TXT-Record auf der Domäne hinterlegt. Der String beginnt immer zwingend mit v=spf1:

+ +
# 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!
+
+ + +
+

🛑 2. Das gefürchtete 10-Lookup-Limit

+
+
RFC 7208 Void-Lookup Limit: Max. 10 DNS-Queries
+

Jeder include, a und mx 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 SPF PermError!

+
-

🎯 Aufgaben & Checkliste

+

📑 3. Cheat-Sheet: Die 4 SPF-Qualifier

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
QualifierNameAuswirkung beim EmpfängerEmpfehlung
-allHardFailMails von ungelisteten IPs werden abgewiesenBest Practice
~allSoftFailWird angenommen, aber als Spam markiertNur für Übergangsphase
?allNeutralKeine SchutzwirkungVermeiden
+allPassJeder Server weltweit darf fälschen!VERBOTEN
+
+
+ + +
+

🎯 Aufgaben & Checkliste in aufgabe.sh

- -
+ +
+
- -
+ +
+
- -
+ +
+
- +
diff --git a/courses/dns_records/10_txt_records_und_spf_sender_policy_framework/musterloesung.sh b/courses/dns_records/10_txt_records_und_spf_sender_policy_framework/musterloesung.sh index 310627b..882881c 100755 --- a/courses/dns_records/10_txt_records_und_spf_sender_policy_framework/musterloesung.sh +++ b/courses/dns_records/10_txt_records_und_spf_sender_policy_framework/musterloesung.sh @@ -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 diff --git a/courses/dns_records/10_txt_records_und_spf_sender_policy_framework/test_aufgabe.sh b/courses/dns_records/10_txt_records_und_spf_sender_policy_framework/test_aufgabe.sh index 72fda51..6d5093b 100755 --- a/courses/dns_records/10_txt_records_und_spf_sender_policy_framework/test_aufgabe.sh +++ b/courses/dns_records/10_txt_records_und_spf_sender_policy_framework/test_aufgabe.sh @@ -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 diff --git a/courses/dns_records/11_dkim_domainkeys_identified_mail/README.md b/courses/dns_records/11_dkim_domainkeys_identified_mail/README.md index 6114e7c..cd70bd0 100644 --- a/courses/dns_records/11_dkim_domainkeys_identified_mail/README.md +++ b/courses/dns_records/11_dkim_domainkeys_identified_mail/README.md @@ -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: + `._domainkey..` +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 (`._domainkey..`). +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 ``` diff --git a/courses/dns_records/11_dkim_domainkeys_identified_mail/aufgabe.sh b/courses/dns_records/11_dkim_domainkeys_identified_mail/aufgabe.sh index 60d2328..206f2b4 100755 --- a/courses/dns_records/11_dkim_domainkeys_identified_mail/aufgabe.sh +++ b/courses/dns_records/11_dkim_domainkeys_identified_mail/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: ._domainkey.. +# 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: ._domainkey.. +# - 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 ( Zeichen) ist veraltet! Mindestens 2048 Bit erforderlich." +# return 1 +# - Wenn Länge >= 300: +# Gib aus: "SECURE: 2048-Bit RSA Key ( 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=" länger als 250 Zeichen ist: +# Teile den String in zwei Blöcke auf und formatiere als BIND Multi-String: +# IN TXT ( "" "" ) +# - Andernfalls formatiere einzeilig: +# IN TXT "v=DKIM1; k=rsa; p=" +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= +# KEY_TYPE= +# KEY_PREVIEW=... +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 diff --git a/courses/dns_records/11_dkim_domainkeys_identified_mail/index.html b/courses/dns_records/11_dkim_domainkeys_identified_mail/index.html index 227ef1e..e881dbc 100644 --- a/courses/dns_records/11_dkim_domainkeys_identified_mail/index.html +++ b/courses/dns_records/11_dkim_domainkeys_identified_mail/index.html @@ -4,7 +4,7 @@ - DNS 11: DKIM Kryptografische E-Mail-Signatur + DNS 11: DKIM E-Mail-Signatur @@ -12,46 +12,90 @@
-
🛡️ DKIM Signatur
-

DNS 11: DKIM Kryptografische E-Mail-Signatur

-

Public Key im DNS: Selektoren (s1._domainkey.domain.de), RSA/Ed25519 Schlüssel und Header-Validierung.

+
🛡️ E-Mail-Sicherheit
+

DNS 11: DKIM & Kryptografische E-Mail-Signaturen

+

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.

+
-

💡 Theorie & Praxiskonzept

-

Public Key im DNS: Selektoren (s1._domainkey.domain.de), RSA/Ed25519 Schlüssel und Header-Validierung.

-
-
- DNS Command & Syntax Reference - Tool: opendkim-testkey -
-
# Wichtige Befehle & Zonensyntax:
-dig opendkim-testkey
-named-checkzone example.com /etc/bind/zones/db.example.com
+

🔐 1. Funktionsweise von DKIM (RFC 6376)

+

DKIM schützt vor Man-in-the-Middle Manipulationen. Der Public Key wird unter der Subdomain ._domainkey publiziert:

+ +
# Abfrage eines DKIM-Records mit dig (Selector 'google'):
+dig TXT google._domainkey.gmail.com +short
+
+# Ausgabe:
+# "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA..."
+
+ + +
+

🛡️ 2. Warum 1024-Bit Keys verboten gehören

+
+
Security Alert: 1024-Bit RSA gebrochen
+

1024-Bit RSA-Schlüssel können heute mit Cloud-Rechenleistung in wenigen Tagen faktorisiert werden! Google und Yahoo verlangen seit 2024 zwingend mindestens 2048 Bit. Mails mit 1024-Bit Schlüsseln werden zunehmend gebounced.

+
-

🎯 Aufgaben & Checkliste

+

📑 3. Cheat-Sheet: DKIM-Tags

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
TagBedeutungStandard / Beispiel
v=DKIM-Protokollversionv=DKIM1
k=Schlüssel-Algorithmusk=rsa oder k=ed25519
p=Base64 Public Keyp=MIIBIjANBgkqhki... (mind. 2048 Bit)
+
+
+ + +
+

🎯 Aufgaben & Checkliste in aufgabe.sh

- -
+ +
+
- -
+ +
+
- -
+ +
+
- +
diff --git a/courses/dns_records/11_dkim_domainkeys_identified_mail/musterloesung.sh b/courses/dns_records/11_dkim_domainkeys_identified_mail/musterloesung.sh index c320af7..836b051 100755 --- a/courses/dns_records/11_dkim_domainkeys_identified_mail/musterloesung.sh +++ b/courses/dns_records/11_dkim_domainkeys_identified_mail/musterloesung.sh @@ -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 diff --git a/courses/dns_records/11_dkim_domainkeys_identified_mail/test_aufgabe.sh b/courses/dns_records/11_dkim_domainkeys_identified_mail/test_aufgabe.sh index eb3980d..9ebedc0 100755 --- a/courses/dns_records/11_dkim_domainkeys_identified_mail/test_aufgabe.sh +++ b/courses/dns_records/11_dkim_domainkeys_identified_mail/test_aufgabe.sh @@ -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 diff --git a/courses/dns_records/12_dmarc_und_email_security_suite/README.md b/courses/dns_records/12_dmarc_und_email_security_suite/README.md index 35aaae6..c4879d8 100644 --- a/courses/dns_records/12_dmarc_und_email_security_suite/README.md +++ b/courses/dns_records/12_dmarc_und_email_security_suite/README.md @@ -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..`. +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 ``` diff --git a/courses/dns_records/12_dmarc_und_email_security_suite/aufgabe.sh b/courses/dns_records/12_dmarc_und_email_security_suite/aufgabe.sh index 38c1ebe..367194c 100755 --- a/courses/dns_records/12_dmarc_und_email_security_suite/aufgabe.sh +++ b/courses/dns_records/12_dmarc_und_email_security_suite/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.. +# 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.." sein. +# - Formatiere als BIND TXT Record: +# _dmarc.. 3600 IN TXT "v=DMARC1; p=; rua=mailto:; 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= | RATING= +# 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 diff --git a/courses/dns_records/12_dmarc_und_email_security_suite/index.html b/courses/dns_records/12_dmarc_und_email_security_suite/index.html index e8ba7f6..b06b77c 100644 --- a/courses/dns_records/12_dmarc_und_email_security_suite/index.html +++ b/courses/dns_records/12_dmarc_und_email_security_suite/index.html @@ -4,7 +4,7 @@ - DNS 12: DMARC Policy & Phishing-Abwehr + DNS 12: DMARC Policy & Security @@ -12,46 +12,94 @@
-
🛡️ DMARC Policy
-

DNS 12: DMARC Policy & Phishing-Abwehr

-

Die Krone der Mail-Sicherheit: DMARC Policy (_dmarc.domain.de), SPF/DKIM Alignment und RUA/RUF Reporting.

+
🛡️ E-Mail-Sicherheit
+

DNS 12: DMARC Policy & Phishing-Abwehr

+

Schließe die Sicherheitskette: Kopple SPF und DKIM mit DMARC, erzwinge die Ablehnung gefälschter Mails (p=reject) und analysiere Aggregated Reports.

+
-

💡 Theorie & Praxiskonzept

-

Die Krone der Mail-Sicherheit: DMARC Policy (_dmarc.domain.de), SPF/DKIM Alignment und RUA/RUF Reporting.

-
-
- DNS Command & Syntax Reference - Tool: checkdmarc -
-
# Wichtige Befehle & Zonensyntax:
-dig checkdmarc
-named-checkzone example.com /etc/bind/zones/db.example.com
+

🛡️ 1. Die E-Mail-Security Trilogie im Zusammenspiel

+

Erst wenn SPF, DKIM und DMARC Hand in Hand arbeiten, ist eine Domain vor CEO-Fraud und Identitätsdiebstahl geschützt:

+ +
+-----------+       +------------+       +-------------------+
+|    SPF    |  AND  |    DKIM    |  ==>  |       DMARC       |
+| (IP-Auth) |       | (Signatur) |       | (Policy & Report) |
++-----------+       +------------+       +-------------------+
+
+ + +
+

🎯 2. Der DMARC-Einführungsplan für Unternehmen

+
+
💡 Best-Practice Rollout in 3 Phasen
+
    +
  1. Phase 1 (Woche 1-4): p=none; rua=mailto:... → Nur Berichte sammeln und legitime Absender (CRM, Newsletter) in SPF aufnehmen.
  2. +
  3. Phase 2 (Woche 5-8): p=quarantine; pct=50 → Schrittweise Mails ohne Authentifizierung in den Spam-Ordner leiten.
  4. +
  5. Phase 3 (Finale): p=reject; pct=100 → Vollständige Abweisung gefälschter Mails an der SMTP-Pforte!
  6. +
+
-

🎯 Aufgaben & Checkliste

+

📑 3. Cheat-Sheet: DMARC-Tags

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TagZweckMögliche Werte & Empfehlung
p=Empfänger-Aktion bei Fehlernone, quarantine, reject (Ziel: reject)
rua=Aggregierte Berichtemailto:dmarc-reports@firma.de
pct=Prozentuale Anwendung100 (bei Rollout 20, 50, ...)
aspf= / adkim=Alignment Modusr (Relaxed, Standard) oder s (Strict)
+
+
+ + +
+

🎯 Aufgaben & Checkliste in aufgabe.sh

- -
+ +
+
- -
+ +
+
- -
- - +
diff --git a/courses/dns_records/12_dmarc_und_email_security_suite/musterloesung.sh b/courses/dns_records/12_dmarc_und_email_security_suite/musterloesung.sh index 8558e25..7980ec6 100755 --- a/courses/dns_records/12_dmarc_und_email_security_suite/musterloesung.sh +++ b/courses/dns_records/12_dmarc_und_email_security_suite/musterloesung.sh @@ -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 diff --git a/courses/dns_records/12_dmarc_und_email_security_suite/test_aufgabe.sh b/courses/dns_records/12_dmarc_und_email_security_suite/test_aufgabe.sh index 2b84541..c64a217 100755 --- a/courses/dns_records/12_dmarc_und_email_security_suite/test_aufgabe.sh +++ b/courses/dns_records/12_dmarc_und_email_security_suite/test_aufgabe.sh @@ -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.. 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 diff --git a/courses/dns_records/13_srv_service_records_fuer_ad_und_sip/README.md b/courses/dns_records/13_srv_service_records_fuer_ad_und_sip/README.md index a8dfa60..c39dd42 100644 --- a/courses/dns_records/13_srv_service_records_fuer_ad_und_sip/README.md +++ b/courses/dns_records/13_srv_service_records_fuer_ad_und_sip/README.md @@ -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. diff --git a/courses/dns_records/13_srv_service_records_fuer_ad_und_sip/aufgabe.sh b/courses/dns_records/13_srv_service_records_fuer_ad_und_sip/aufgabe.sh index 4552cc2..5494def 100755 --- a/courses/dns_records/13_srv_service_records_fuer_ad_und_sip/aufgabe.sh +++ b/courses/dns_records/13_srv_service_records_fuer_ad_und_sip/aufgabe.sh @@ -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 +} diff --git a/courses/dns_records/13_srv_service_records_fuer_ad_und_sip/index.html b/courses/dns_records/13_srv_service_records_fuer_ad_und_sip/index.html index d01af62..62867a1 100644 --- a/courses/dns_records/13_srv_service_records_fuer_ad_und_sip/index.html +++ b/courses/dns_records/13_srv_service_records_fuer_ad_und_sip/index.html @@ -1,63 +1,58 @@ - - DNS 13: SRV Service Records (Active Directory & SIP) + Modul 13: SRV-Service Records für Active Directory & SIP -
-
-
-
-
🚀 Service Discovery
-

DNS 13: SRV Service Records (Active Directory & SIP)

-

Dienste automatisch im Netzwerk finden: _service._proto.name TTL Class SRV Priority Weight Port Target.

-
-
- -
-
-

💡 Theorie & Praxiskonzept

-

Dienste automatisch im Netzwerk finden: _service._proto.name TTL Class SRV Priority Weight Port Target.

-
-
- DNS Command & Syntax Reference - Tool: dig SRV -
-
# Wichtige Befehle & Zonensyntax:
-dig dig SRV
-named-checkzone example.com /etc/bind/zones/db.example.com
-
-
- -
-

🎯 Aufgaben & Checkliste

-
- - -
- - -
- - -
- - -
- - -
-
+
+
+
Lehrpfad 4: Erweiterte Records, DNSSEC & Troubleshooting
+

Modul 13: SRV-Service Records für Active Directory & SIP (RFC 2782)

+

Service Discovery im DNS: Wie Clients Server für LDAP, Kerberos und VoIP dynamisch mit Port, Priorität und Lastverteilung finden.

+
-
- - + + +
+
+

1. Warum SRV-Records? Das Problem klassischer A-Records

+

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? RFC 2782 SRV-Records lösen dies durch strukturierte Metadaten direkt in der DNS-Zone.

+
+
🏢 Analogie: Das Firmen-Telefonbuch mit Durchwahlen
+

Ein A-Record ist die Firmenadresse auf dem Briefkopf. Ein SRV-Record ist der Empfang mit Durchwahlliste: Er sagt dir, in welchem Büro (Host), auf welcher Durchwahl (Port) und wer zuerst abhebt (Priorität/Gewichtung).

+
+
+ +
+

2. Active Directory DNS-Discovery Struktur

+
+
SRV-Discovery in Microsoft Windows Server Umgebungen
+
; 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.
+
+
+ +
+

3. 🎯 Deine Teilziele in der Web-IDE

+
    +
  • Teilziel 1: format_srv_record – Standardkonformes BIND SRV-Format mit Trailing Dots.
  • +
  • Teilziel 2: parse_srv_record – Parsing der 6 Kernfelder eines SRV-Eintrags.
  • +
  • Teilziel 3: generate_ad_srv_records – Automatische Erstellung der Kerberos-, LDAP- und GC-Records.
  • +
  • Teilziel 4: select_srv_target_by_prio – Bestimmung des Failover-Ziels mit niedrigster Prioritätszahl.
  • +
+ +
+
- + \ No newline at end of file diff --git a/courses/dns_records/13_srv_service_records_fuer_ad_und_sip/musterloesung.sh b/courses/dns_records/13_srv_service_records_fuer_ad_und_sip/musterloesung.sh index 04bcec9..3ac26eb 100755 --- a/courses/dns_records/13_srv_service_records_fuer_ad_und_sip/musterloesung.sh +++ b/courses/dns_records/13_srv_service_records_fuer_ad_und_sip/musterloesung.sh @@ -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" +} diff --git a/courses/dns_records/13_srv_service_records_fuer_ad_und_sip/test_aufgabe.sh b/courses/dns_records/13_srv_service_records_fuer_ad_und_sip/test_aufgabe.sh index c51f333..f6d6478 100755 --- a/courses/dns_records/13_srv_service_records_fuer_ad_und_sip/test_aufgabe.sh +++ b/courses/dns_records/13_srv_service_records_fuer_ad_und_sip/test_aufgabe.sh @@ -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 " ✓ BESTANDEN" + ((PASSED_TESTS++)) + else + echo -e " ✗ FEHLGESCHLAGEN +" + 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 < 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`. diff --git a/courses/dns_records/14_caa_und_dnssec_sicherheit/aufgabe.sh b/courses/dns_records/14_caa_und_dnssec_sicherheit/aufgabe.sh index eb12ff5..d194aa4 100755 --- a/courses/dns_records/14_caa_und_dnssec_sicherheit/aufgabe.sh +++ b/courses/dns_records/14_caa_und_dnssec_sicherheit/aufgabe.sh @@ -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: . IN CAA "" +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@ +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 +} diff --git a/courses/dns_records/14_caa_und_dnssec_sicherheit/index.html b/courses/dns_records/14_caa_und_dnssec_sicherheit/index.html index 370cae8..fc5e6c0 100644 --- a/courses/dns_records/14_caa_und_dnssec_sicherheit/index.html +++ b/courses/dns_records/14_caa_und_dnssec_sicherheit/index.html @@ -1,63 +1,51 @@ - - DNS 14: CAA Records & DNSSEC Vertrauenskette + Modul 14: CAA & DNSSEC Sicherheit -
-
-
-
-
🚀 DNSSEC & CAA
-

DNS 14: CAA Records & DNSSEC Vertrauenskette

-

CAA zur Autorisierung von Zertifizierungsstellen (Let's Encrypt) und DNSSEC Signierung (RRSIG, DNSKEY, DS).

-
-
- -
-
-

💡 Theorie & Praxiskonzept

-

CAA zur Autorisierung von Zertifizierungsstellen (Let's Encrypt) und DNSSEC Signierung (RRSIG, DNSKEY, DS).

-
-
- DNS Command & Syntax Reference - Tool: delv -
-
# Wichtige Befehle & Zonensyntax:
-dig delv
-named-checkzone example.com /etc/bind/zones/db.example.com
-
-
- -
-

🎯 Aufgaben & Checkliste

-
- - -
- - -
- - -
- - -
- - -
-
+
+
+
Lehrpfad 4: Erweiterte Records, DNSSEC & Troubleshooting
+

Modul 14: CAA & DNSSEC Sicherheit (RFC 8659, RFC 4034)

+

Kryptografischer Schutz vor DNS-Hijacking und unautorisierten SSL/TLS-Zertifikaten.

+
-
- - + + +
+
+

1. CAA: Wer darf Zertifikate für mich ausstellen?

+

CAs sind verpflichtet, vor Ausstellung eines Zertifikats die CAA-Records der Domain im DNS abzufragen. Ist dort issue "letsencrypt.org" definiert, weist DigiCert jeden Zertifikatsantrag für deine Domain automatisch ab.

+
+ +
+

2. DNSSEC: Digitale Signaturen für Record-Sets

+
+
🔏 Analogie: Der notariell beglaubigte Personalausweis
+

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.

+
+
+ +
+

3. 🎯 Deine Teilziele in der Web-IDE

+
    +
  • Teilziel 1: format_caa_record – CAA-Syntax nach RFC 8659 generieren.
  • +
  • Teilziel 2: format_caa_wildcard_block – Absicherung gegen Wildcard-Missbrauch.
  • +
  • Teilziel 3: parse_ds_record – Zerlegung des Parent Delegation Signer Records.
  • +
  • Teilziel 4: verify_dnssec_chain_status – Validierung der Trust Chain.
  • +
+ +
+
- + \ No newline at end of file diff --git a/courses/dns_records/14_caa_und_dnssec_sicherheit/musterloesung.sh b/courses/dns_records/14_caa_und_dnssec_sicherheit/musterloesung.sh index c017497..04a74f0 100755 --- a/courses/dns_records/14_caa_und_dnssec_sicherheit/musterloesung.sh +++ b/courses/dns_records/14_caa_und_dnssec_sicherheit/musterloesung.sh @@ -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 +} diff --git a/courses/dns_records/14_caa_und_dnssec_sicherheit/test_aufgabe.sh b/courses/dns_records/14_caa_und_dnssec_sicherheit/test_aufgabe.sh index c34754c..0249f38 100755 --- a/courses/dns_records/14_caa_und_dnssec_sicherheit/test_aufgabe.sh +++ b/courses/dns_records/14_caa_und_dnssec_sicherheit/test_aufgabe.sh @@ -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 " ✓ BESTANDEN" + ((PASSED_TESTS++)) + else + echo -e " ✗ FEHLGESCHLAGEN +" + 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 " +🎉 ERFOLG: Alle ${PASSED_TESTS}/${TOTAL_TESTS} Tests in DNS 14 bestanden!" exit 0 else - echo -e "${RED}❌ FEHLER: Skript fehlgeschlagen mit Status $RET${NC}" + echo -e " +❌ FEHLER: ${PASSED_TESTS}/${TOTAL_TESTS} Tests bestanden." exit 1 fi diff --git a/courses/dns_records/15_dns_troubleshooting_mit_dig_und_nslookup/README.md b/courses/dns_records/15_dns_troubleshooting_mit_dig_und_nslookup/README.md index f9c0bed..9a6146c 100644 --- a/courses/dns_records/15_dns_troubleshooting_mit_dig_und_nslookup/README.md +++ b/courses/dns_records/15_dns_troubleshooting_mit_dig_und_nslookup/README.md @@ -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`). diff --git a/courses/dns_records/15_dns_troubleshooting_mit_dig_und_nslookup/aufgabe.sh b/courses/dns_records/15_dns_troubleshooting_mit_dig_und_nslookup/aufgabe.sh index ed54669..db8c792 100755 --- a/courses/dns_records/15_dns_troubleshooting_mit_dig_und_nslookup/aufgabe.sh +++ b/courses/dns_records/15_dns_troubleshooting_mit_dig_und_nslookup/aufgabe.sh @@ -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 +} diff --git a/courses/dns_records/15_dns_troubleshooting_mit_dig_und_nslookup/index.html b/courses/dns_records/15_dns_troubleshooting_mit_dig_und_nslookup/index.html index 26bf47e..e2c24c4 100644 --- a/courses/dns_records/15_dns_troubleshooting_mit_dig_und_nslookup/index.html +++ b/courses/dns_records/15_dns_troubleshooting_mit_dig_und_nslookup/index.html @@ -1,63 +1,43 @@ - - DNS 15: DNS-Troubleshooting mit dig & nslookup + Modul 15: DNS-Troubleshooting mit dig & nslookup -
-
-
-
-
🚀 Diagnose & Analyse
-

DNS 15: DNS-Troubleshooting mit dig & nslookup

-

Netzwerkdiagnose im Terminal: dig +trace, +short, Reverse Lookups, Statuscodes (NOERROR, NXDOMAIN, SERVFAIL) & AXFR Leak-Testing.

-
-
- -
-
-

💡 Theorie & Praxiskonzept

-

Netzwerkdiagnose im Terminal: dig +trace, +short, Reverse Lookups, Statuscodes (NOERROR, NXDOMAIN, SERVFAIL) & AXFR Leak-Testing.

-
-
- DNS Command & Syntax Reference - Tool: dig -
-
# Wichtige Befehle & Zonensyntax:
-dig dig
-named-checkzone example.com /etc/bind/zones/db.example.com
-
-
- -
-

🎯 Aufgaben & Checkliste

-
- - -
- - -
- - -
- - -
- - -
-
+
+
+
Lehrpfad 4: Erweiterte Records, DNSSEC & Troubleshooting
+

Modul 15: DNS-Troubleshooting mit dig & nslookup

+

Systematische Fehlersuche im Terminal: RCODEs analysieren, Caching-Probleme identifizieren und Zonen-Synchronisation prüfen.

+
-
- - + + +
+
+

1. RCODEs verstehen: Was sagt der Server wirklich?

+

Wenn ein Ping fehlschlägt, liefert dig die exakte Diagnose. Ob NXDOMAIN oder SERVFAIL macht den Unterschied zwischen einem Tippfehler im Hostnamen und einem kollabierten DNSSEC-Zertifikat.

+
+ +
+

2. 🎯 Deine Teilziele in der Web-IDE

+
    +
  • Teilziel 1: diagnose_dns_rcode – RCODE-Interpretation & Handlungsanweisungen.
  • +
  • Teilziel 2: extract_dig_ips – Präzise IP-Filterung aus Dig Answer Sections.
  • +
  • Teilziel 3: detect_soa_desync – Erkennung von Replikationsverzögerungen zwischen Nameservern.
  • +
  • Teilziel 4: verify_axfr_blocked – Sicherheits-Audit gegen unerlaubten Zonentransfer.
  • +
+ +
+
- + \ No newline at end of file diff --git a/courses/dns_records/15_dns_troubleshooting_mit_dig_und_nslookup/musterloesung.sh b/courses/dns_records/15_dns_troubleshooting_mit_dig_und_nslookup/musterloesung.sh index bd3949d..be17b62 100755 --- a/courses/dns_records/15_dns_troubleshooting_mit_dig_und_nslookup/musterloesung.sh +++ b/courses/dns_records/15_dns_troubleshooting_mit_dig_und_nslookup/musterloesung.sh @@ -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 +} diff --git a/courses/dns_records/15_dns_troubleshooting_mit_dig_und_nslookup/test_aufgabe.sh b/courses/dns_records/15_dns_troubleshooting_mit_dig_und_nslookup/test_aufgabe.sh index ede2f8c..b82d6c6 100755 --- a/courses/dns_records/15_dns_troubleshooting_mit_dig_und_nslookup/test_aufgabe.sh +++ b/courses/dns_records/15_dns_troubleshooting_mit_dig_und_nslookup/test_aufgabe.sh @@ -1,36 +1,60 @@ #!/usr/bin/env bash -# ============================================================================== -# TESTSUITE: DNS 15: DNS-Troubleshooting mit dig & nslookup -# ============================================================================== - -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 15: DNS-Troubleshooting mit dig & nslookup${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 " ✓ BESTANDEN" + ((PASSED_TESTS++)) + else + echo -e " ✗ FEHLGESCHLAGEN +" + fi +} -source "$SCRIPT_DIR/$TARGET_SCRIPT" +run_test "diagnose_dns_rcode identifiziert NXDOMAIN" ' + diagnose_dns_rcode "NXDOMAIN" | grep -q "ERROR_NXDOMAIN" +' -# Führe Funktion aus -diagnose_dns_health "example.com" "$TEST_DIR" -RET=$? +run_test "diagnose_dns_rcode identifiziert SERVFAIL" ' + diagnose_dns_rcode "SERVFAIL" | grep -q "ERROR_SERVFAIL" +' -if [ $RET -eq 0 ]; then - echo -e "${GREEN}✓ Test 1: Funktion diagnose_dns_health 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 15: DNS-Troubleshooting mit dig & nslookup bestanden!${NC}" +run_test "extract_dig_ips filtert IPv4 Adressen sauber" ' + RAW=";; ANSWER SECTION: +www.test.de. 300 IN A 192.0.2.1 +www.test.de. 300 IN A 198.51.100.2" + IPS=$(extract_dig_ips "$RAW") + echo "$IPS" | grep -q "192.0.2.1" && echo "$IPS" | grep -q "198.51.100.2" +' + +run_test "detect_soa_desync erkennt synchrone Zonen" ' + detect_soa_desync 2026091501 2026091501 | grep -q "IN_SYNC" +' + +run_test "detect_soa_desync schlägt Alarm bei veraltetem Slave" ' + detect_soa_desync 2026091505 2026091501 | grep -q "DESYNC" +' + +run_test "verify_axfr_blocked meldet SECURE bei Abweisung" ' + verify_axfr_blocked ";; Transfer failed. REFUSED" | grep -q "SECURE: AXFR" +' + +if [ "$PASSED_TESTS" -eq "$TOTAL_TESTS" ]; then + echo -e " +🎉 ERFOLG: Alle ${PASSED_TESTS}/${TOTAL_TESTS} Tests in DNS 15 bestanden!" exit 0 else - echo -e "${RED}❌ FEHLER: Skript fehlgeschlagen mit Status $RET${NC}" + echo -e " +❌ FEHLER: ${PASSED_TESTS}/${TOTAL_TESTS} Tests bestanden." exit 1 fi diff --git a/courses/dns_records/16_master_abschlussprojekt_dns_zone_auditor/README.md b/courses/dns_records/16_master_abschlussprojekt_dns_zone_auditor/README.md index 069ddc3..c0d1214 100644 --- a/courses/dns_records/16_master_abschlussprojekt_dns_zone_auditor/README.md +++ b/courses/dns_records/16_master_abschlussprojekt_dns_zone_auditor/README.md @@ -1,18 +1,25 @@ -# Master 16: Enterprise DNS Zone & Security Auditor +# Modul 16: Master-Abschlussprojekt – Enterprise DNS Zone Auditor -Willkommen zum Kurs **DNS-Records, Domain Name System & E-Mail-Sicherheit** im IT-Praxisportal! +## 🏆 Willkommen zum Master-Abschlussprojekt des DNS-Kurses! +Du hast die gesamte DNS-Architektur gemeistert: Von den 13 Root-Clustern über CNAME-Apex-Konflikte, SPF/DKIM/DMARC E-Mail-Sicherheit bis hin zu Active Directory SRV-Records und DNSSEC. -## 💡 Das Wichtigste in Kürze -- **Ziel:** Das ultimative Abschlussprojekt: Entwickle einen vollautomatischen DNS-Auditor für Zonendateien & Domain Security. -- **Wichtigstes Tool:** `dns_zone_auditor` +In diesem Abschlussprojekt entwickelst du ein **vollständiges, produktionsreifes DNS-Zone-Auditing-Werkzeug** in Bash. -## 🎯 Deine Aufgaben (Checkliste) -1. **Teilziel 1:** Zonendatei auf RFC-Konformität, doppelte Einträge und CNAME-Apex-Kollisionen prüfen -2. **Teilziel 2:** Vollständige E-Mail-Sicherheitsanalyse (MX, SPF, DKIM, DMARC) durchführen -3. **Teilziel 3:** SOA Serial und NS-Redundanz (mind. 2 getrennte Nameserver) auditieren -4. **Teilziel 4:** Strukturierten JSON- und Terminal-Sicherheitsbericht mit Score generieren +--- -## 🧪 Tests ausführen -```bash -bash test_aufgabe.sh -``` +## 🎯 Anforderungen an den DNS Zone Auditor: + +Der Auditor liest eine BIND-Zonendatei oder simuliert die Überprüfung einer Live-Domain und führt 5 fundamentale Checks durch: + +1. **Check 1: SOA-Record Validierung**: + - Existiert ein SOA-Record? + - Hat die Serial das gültige 10-stellige Datumsformat `YYYYMMDDNN`? +2. **Check 2: NS-Redundanz (RFC 2182)**: + - Sind mindestens **2 verschiedene Nameserver (NS)** definiert? +3. **Check 3: CNAME-Apex Kollisionsprüfung (RFC 1912)**: + - Befindet sich am Zone-Apex (`@`) versehentlich ein CNAME-Record? +4. **Check 4: E-Mail-Sicherheits-Trilogie**: + - Existiert ein SPF TXT Record mit sicherem `-all`? + - Existiert ein DMARC Record mit `p=quarantine` oder `p=reject`? +5. **Check 5: Gesamtbewertung & Score**: + - Berechnet einen Gesamt-Sicherheits-Score (0 bis 100 Punkte) und gibt das Urteil `AUDIT_PASSED` (>=80) oder `AUDIT_FAILED` aus. diff --git a/courses/dns_records/16_master_abschlussprojekt_dns_zone_auditor/aufgabe.sh b/courses/dns_records/16_master_abschlussprojekt_dns_zone_auditor/aufgabe.sh index 7ed1f3f..d0e9915 100755 --- a/courses/dns_records/16_master_abschlussprojekt_dns_zone_auditor/aufgabe.sh +++ b/courses/dns_records/16_master_abschlussprojekt_dns_zone_auditor/aufgabe.sh @@ -1,21 +1,55 @@ #!/usr/bin/env bash # ============================================================================== -# 🏆 DNS Master - Master 16: Enterprise DNS Zone & Security Auditor +# Modul 16: Master-Abschlussprojekt – Enterprise DNS Zone Auditor # ============================================================================== -# 🎯 TEILZIEL 1: Zonendatei auf RFC-Konformität, doppelte Einträge und CNAME-Apex-Kollisionen prüfen -# 🎯 TEILZIEL 2: Vollständige E-Mail-Sicherheitsanalyse (MX, SPF, DKIM, DMARC) durchführen -# 🎯 TEILZIEL 3: SOA Serial und NS-Redundanz (mind. 2 getrennte Nameserver) auditieren -# 🎯 TEILZIEL 4: Strukturierten JSON- und Terminal-Sicherheitsbericht mit Score generieren +# 🎯 TEILZIEL 1: SOA-Record & Serial prüfen +# Prüft, ob ein SOA vorhanden ist und die Serial 10-stellig ist (z. B. 2026091501) +# Rückgabe: "SOA_OK: Serial YYYYMMDDNN valid" oder "SOA_FAIL: Invalid serial" +function audit_soa_record() { + local zone_content="$1" -audit_dns_zone() { - 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: Prüfe SOA Serial + return 1 } -if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then - audit_dns_zone "$@" -fi +# 🎯 TEILZIEL 2: Nameserver-Redundanz prüfen (RFC 2182) +# Zählt alle NS-Records. Wenn >= 2: "NS_OK: Redundancy satisfied (Count: X)" +# Wenn < 2: "NS_FAIL: Critical lack of redundancy (Count: X)" +function audit_ns_redundancy() { + local zone_content="$1" + + # TODO: Zähle NS-Records + return 1 +} + +# 🎯 TEILZIEL 3: Apex CNAME Kollision aufspüren (RFC 1912) +# Sucht nach "@ ... CNAME" oder Zeilen mit Domainname am Anfang und CNAME +# Wenn gefunden: "CRITICAL: CNAME at apex detected" +# Wenn sauber: "APEX_OK: No apex CNAME collision" +function audit_apex_cname_collision() { + local zone_content="$1" + + # TODO: Finde Apex CNAME + return 1 +} + +# 🎯 TEILZIEL 4: E-Mail-Abwehr prüfen (SPF & DMARC) +# Prüft, ob sowohl SPF mit -all als auch DMARC vorhanden sind +function audit_email_defense() { + local zone_content="$1" + + # TODO: Prüfe SPF und DMARC + return 1 +} + +# 🎯 TEILZIEL 5: Komplettes Zonen-Audit ausführen & Score berechnen (0 - 100) +# Gibt den Gesamtreport aus: +# SCORE=/100 +# VERDICT=AUDIT_PASSED oder AUDIT_FAILED +function run_complete_zone_audit() { + local zone_content="$1" + + # TODO: Berechne Gesamtscore und erstelle Abschlussreport + return 1 +} diff --git a/courses/dns_records/16_master_abschlussprojekt_dns_zone_auditor/index.html b/courses/dns_records/16_master_abschlussprojekt_dns_zone_auditor/index.html index 0be7644..fb72d1f 100644 --- a/courses/dns_records/16_master_abschlussprojekt_dns_zone_auditor/index.html +++ b/courses/dns_records/16_master_abschlussprojekt_dns_zone_auditor/index.html @@ -1,63 +1,43 @@ - - Master 16: Enterprise DNS Zone & Security Auditor + Modul 16: Master-Abschlussprojekt DNS Zone Auditor -
-
-
-
-
🏆 DNS Master
-

Master 16: Enterprise DNS Zone & Security Auditor

-

Das ultimative Abschlussprojekt: Entwickle einen vollautomatischen DNS-Auditor für Zonendateien & Domain Security.

-
-
- -
-
-

💡 Theorie & Praxiskonzept

-

Das ultimative Abschlussprojekt: Entwickle einen vollautomatischen DNS-Auditor für Zonendateien & Domain Security.

-
-
- DNS Command & Syntax Reference - Tool: dns_zone_auditor -
-
# Wichtige Befehle & Zonensyntax:
-dig dns_zone_auditor
-named-checkzone example.com /etc/bind/zones/db.example.com
-
-
- -
-

🎯 Aufgaben & Checkliste

-
- - -
- - -
- - -
- - -
- - -
-
+
+
+
Lehrpfad 4: Erweiterte Records, DNSSEC & Troubleshooting
+

Modul 16: Master-Abschlussprojekt – Enterprise DNS Zone Auditor

+

Das finale Meisterstück: Entwickle einen vollautomatisierten BIND-Zonenauditor zur Erkennung von RFC-Verletzungen, Sicherheitslücken und Fehlkonfigurationen.

+
-
- - + + +
+
+

🏆 Die Königsdisziplin der DNS-Administration

+

Ein unbemerkter CNAME am Apex bringt deinen Mailserver zum Schweigen. Ein fehlender zweiter Nameserver lässt bei Serverausfall die gesamte Webpräsenz verschwinden. Dein Auditor prüft Zonen auf Herz und Nieren.

+
+ +
+

2. 🎯 Deine Teilziele in der Web-IDE

+
    +
  • Teilziel 1: audit_soa_record – Validierung der 10-stelligen SOA Serial.
  • +
  • Teilziel 2: audit_ns_redundancy – Durchsetzung von mindestens 2 redundanten Nameservern.
  • +
  • Teilziel 3: audit_apex_cname_collision – Abwehr von illegalen Apex-CNAMEs.
  • +
  • Teilziel 4: audit_email_defense – Kombinierte Prüfung von SPF und DMARC.
  • +
  • Teilziel 5: run_complete_zone_audit – 100-Punkte Audit-Report mit Gesamturteil.
  • +
+ +
+
- + \ No newline at end of file diff --git a/courses/dns_records/16_master_abschlussprojekt_dns_zone_auditor/musterloesung.sh b/courses/dns_records/16_master_abschlussprojekt_dns_zone_auditor/musterloesung.sh index 606189b..40d802a 100755 --- a/courses/dns_records/16_master_abschlussprojekt_dns_zone_auditor/musterloesung.sh +++ b/courses/dns_records/16_master_abschlussprojekt_dns_zone_auditor/musterloesung.sh @@ -1,97 +1,87 @@ #!/usr/bin/env bash # ============================================================================== -# MUSTERLÖSUNG: Master 16: Enterprise DNS Zone & Security Auditor +# Musterlösung Modul 16: Master-Projekt DNS Zone Auditor # ============================================================================== -audit_dns_zone() { - local domain="${1:-it-praxisportal.de}" - local target_dir="${2:-.}" +function audit_soa_record() { + local zone_content="$1" + local serial + serial=$(echo "$zone_content" | grep -A 5 -i "SOA" | grep -E -o "[0-9]{10}" | head -n 1) - echo "▶ Starte professionelle DNS-Operation für $domain..." - - case "16_master_abschlussprojekt_dns_zone_auditor" 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 + if [[ "$serial" =~ ^[0-9]{10}$ ]]; then + echo "SOA_OK: Serial ${serial} valid" + return 0 + else + echo "SOA_FAIL: Invalid serial" + return 1 + fi } -if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then - audit_dns_zone "$@" -fi +function audit_ns_redundancy() { + local zone_content="$1" + local count + count=$(echo "$zone_content" | grep -E -i "\s+IN\s+NS\s+" | wc -l) + + if [ "$count" -ge 2 ]; then + echo "NS_OK: Redundancy satisfied (Count: ${count})" + return 0 + else + echo "NS_FAIL: Critical lack of redundancy (Count: ${count})" + return 1 + fi +} + +function audit_apex_cname_collision() { + local zone_content="$1" + if echo "$zone_content" | grep -E -i "^(@|IN)\s+.*CNAME" || echo "$zone_content" | grep -E -i "^@\s+IN\s+CNAME"; then + echo "CRITICAL: CNAME at apex detected" + return 1 + else + echo "APEX_OK: No apex CNAME collision" + return 0 + fi +} + +function audit_email_defense() { + local zone_content="$1" + local has_spf=false + local has_dmarc=false + + if echo "$zone_content" | grep -i "v=spf1" | grep -q -- "-all"; then + has_spf=true + fi + + if echo "$zone_content" | grep -i "_dmarc" | grep -qiE "p=(reject|quarantine)"; then + has_dmarc=true + fi + + if [ "$has_spf" = true ] && [ "$has_dmarc" = true ]; then + echo "EMAIL_DEFENSE_OK: Both SPF and DMARC enforced" + return 0 + else + echo "EMAIL_DEFENSE_WARN: Incomplete SPF or DMARC protection" + return 1 + fi +} + +function run_complete_zone_audit() { + local zone_content="$1" + local score=0 + + audit_soa_record "$zone_content" > /dev/null && ((score += 25)) + audit_ns_redundancy "$zone_content" > /dev/null && ((score += 25)) + audit_apex_cname_collision "$zone_content" > /dev/null && ((score += 25)) + audit_email_defense "$zone_content" > /dev/null && ((score += 25)) + + echo "==================================================" + echo "📋 ENTERPRISE DNS ZONE AUDIT REPORT" + echo "==================================================" + echo "SCORE=${score}/100" + if [ "$score" -ge 75 ]; then + echo "VERDICT=AUDIT_PASSED" + return 0 + else + echo "VERDICT=AUDIT_FAILED" + return 1 + fi +} diff --git a/courses/dns_records/16_master_abschlussprojekt_dns_zone_auditor/test_aufgabe.sh b/courses/dns_records/16_master_abschlussprojekt_dns_zone_auditor/test_aufgabe.sh index 8c158e7..66babd5 100755 --- a/courses/dns_records/16_master_abschlussprojekt_dns_zone_auditor/test_aufgabe.sh +++ b/courses/dns_records/16_master_abschlussprojekt_dns_zone_auditor/test_aufgabe.sh @@ -1,36 +1,80 @@ #!/usr/bin/env bash -# ============================================================================== -# TESTSUITE: Master 16: Enterprise DNS Zone & Security Auditor -# ============================================================================== - -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: Master 16: Enterprise DNS Zone & Security Auditor${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 " ✓ BESTANDEN" + ((PASSED_TESTS++)) + else + echo -e " ✗ FEHLGESCHLAGEN +" + fi +} -source "$SCRIPT_DIR/$TARGET_SCRIPT" +SAMPLE_ZONE_CLEAN=$(cat <<'EOF' +@ IN SOA ns1.firma.de. hostmaster.firma.de. ( + 2026091501 ; serial + 7200 ; refresh + 3600 ; retry + 1209600 ; expire + 3600 ) ; minimum +@ IN NS ns1.firma.de. +@ IN NS ns2.firma.de. +@ IN A 192.0.2.1 +www IN CNAME @ +@ IN TXT "v=spf1 mx -all" +_dmarc IN TXT "v=DMARC1; p=reject; rua=mailto:dmarc@firma.de" +EOF +) -# Führe Funktion aus -audit_dns_zone "example.com" "$TEST_DIR" -RET=$? +SAMPLE_ZONE_BROKEN=$(cat <<'EOF' +@ IN SOA ns1.firma.de. hostmaster.firma.de. ( 42 ) +@ IN NS ns1.firma.de. +@ IN CNAME server.extern.de. +EOF +) -if [ $RET -eq 0 ]; then - echo -e "${GREEN}✓ Test 1: Funktion audit_dns_zone 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 Master 16: Enterprise DNS Zone & Security Auditor bestanden!${NC}" +run_test "audit_soa_record bestätigt 10-stellige Serial" ' + audit_soa_record "$SAMPLE_ZONE_CLEAN" | grep -q "SOA_OK" +' + +run_test "audit_soa_record schlägt bei 42 fehl" ' + audit_soa_record "$SAMPLE_ZONE_BROKEN" | grep -q "SOA_FAIL" +' + +run_test "audit_ns_redundancy erkennt 2 Nameserver" ' + audit_ns_redundancy "$SAMPLE_ZONE_CLEAN" | grep -q "NS_OK" +' + +run_test "audit_apex_cname_collision findet illegalen CNAME" ' + audit_apex_cname_collision "$SAMPLE_ZONE_BROKEN" | grep -q "CRITICAL: CNAME at apex" +' + +run_test "audit_email_defense bestätigt SPF und DMARC" ' + audit_email_defense "$SAMPLE_ZONE_CLEAN" | grep -q "EMAIL_DEFENSE_OK" +' + +run_test "run_complete_zone_audit vergibt 100/100 an saubere Zone" ' + OUT=$(run_complete_zone_audit "$SAMPLE_ZONE_CLEAN") + echo "$OUT" | grep -q "SCORE=100/100" && echo "$OUT" | grep -q "VERDICT=AUDIT_PASSED" +' + +if [ "$PASSED_TESTS" -eq "$TOTAL_TESTS" ]; then + echo -e " +🎉 ERFOLG: Alle ${PASSED_TESTS}/${TOTAL_TESTS} Tests im Master-Projekt DNS 16 bestanden!" exit 0 else - echo -e "${RED}❌ FEHLER: Skript fehlgeschlagen mit Status $RET${NC}" + echo -e " +❌ FEHLER: ${PASSED_TESTS}/${TOTAL_TESTS} Tests bestanden." exit 1 fi diff --git a/courses/ihk_ap1/05_lf5_software_zur_datenverwaltung/index.html b/courses/ihk_ap1/05_lf5_software_zur_datenverwaltung/index.html index e3dd886..3b6981b 100644 --- a/courses/ihk_ap1/05_lf5_software_zur_datenverwaltung/index.html +++ b/courses/ihk_ap1/05_lf5_software_zur_datenverwaltung/index.html @@ -25,6 +25,18 @@ Lernfeld 5 umfasst alle Programmier- und Datenbank-Kompetenzen: Algorithmen, DIN-Struktogramme, zeilenweise Trace-Tabellen, UML-Klassendiagramme, Normalisierung (1NF-3NF) und relationales SQL.

+ +
+
+ Interaktives Praxistool +

📐 IHK Diagramm-Studio: Struktogramme (DIN 66261) & ERM

+

Baue Nassi-Shneiderman Struktogramme mit automatischem Python-Transpiler oder teste ER-Datenbankmodelle.

+
+ + Struktogramm-Editor öffnen → + +
+

1. Trace-Tabellen & Schleifenlogik

In IHK-Prüfungen müssen Variablenwerte zeilenweise für jeden Schleifendurchlauf exakt tabelliert werden. Typische Operationen: Ganzzahldivision (DIV bzw. //) und Restwertberechnung (MOD bzw. %). diff --git a/courses/ihk_ap1/10_relationales_datenmodell_und_erm/index.html b/courses/ihk_ap1/10_relationales_datenmodell_und_erm/index.html index 2384116..1f64bbd 100644 --- a/courses/ihk_ap1/10_relationales_datenmodell_und_erm/index.html +++ b/courses/ihk_ap1/10_relationales_datenmodell_und_erm/index.html @@ -29,6 +29,18 @@

+ +
+
+ Interaktives Praxistool +

📐 Interaktiver ER-Modell Editor

+

Entwerfe Entitäten, setze Primär- und Fremdschlüssel und teste Kardinalitäten (Chen, Krähenfuß, Min-Max) direkt im visuellen Canvas.

+
+ + ER-Editor starten → + +
+

🗂️ 1. Entity-Relationship-Modellierung (ERM)

Das ER-Modell beschreibt die konzeptionelle Struktur einer Datenbank unabhängig vom konkreten Datenbank-Managementsystem (DBMS). diff --git a/courses/ihk_ap2_fiae/02_uml_modellierung_und_softwareentwurf/index.html b/courses/ihk_ap2_fiae/02_uml_modellierung_und_softwareentwurf/index.html index f612740..a7d50e7 100644 --- a/courses/ihk_ap2_fiae/02_uml_modellierung_und_softwareentwurf/index.html +++ b/courses/ihk_ap2_fiae/02_uml_modellierung_und_softwareentwurf/index.html @@ -25,6 +25,18 @@ Die Unified Modeling Language (UML) ist das universelle Dokumentations- und Modellierungswerkzeug in allen IHK-Abschlussprüfungen für Anwendungsentwickler.

+ +
+
+ Interaktives Praxistool +

🔷 Interaktives UML 2.5 Diagramm-Studio

+

Erstelle Klassendiagramme, Sequenzdiagramme und Use-Cases mit Live-Rendering und Mermaid-Syntax.

+
+ + UML-Studio starten → + +
+

1. UML-Klassendiagramm: Sichtbarkeiten & Relationen

diff --git a/dashboard.html b/dashboard.html index 6c23203..b794b4e 100644 --- a/dashboard.html +++ b/dashboard.html @@ -246,6 +246,7 @@ ÜbersichtKurseIHK-Prüfung + 📐 DiagrammeWeb-IDEZertifikate @@ -275,6 +276,7 @@ ÜbersichtKurseIHK-Prüfungsvorbereitung + 📐 Diagramm-Studio (ER, SG, UML)ZertifikateStartseiteImpressum @@ -297,7 +299,7 @@ Heute

Willkommen, Teilnehmer

-

+

Zentrales Ausbildungs- und Lernportal für IT-Fachinformatiker. Praxisnahe Fachmodule, interaktive Monaco Web-IDE und bundeseinheitliche Prüfungsvorbereitung nach IHK AO 2020.

@@ -345,9 +347,9 @@ 💻
-
Zuletzt bearbeitet
+
Zuletzt bearbeitet

Python 3: Vom Einsteiger zum Entwickler

-

Setze deine letzte praktische Übung direkt an der gleichen Stelle in der Web-IDE fort.

+

Setze deine letzte praktische Übung direkt an der gleichen Stelle in der Web-IDE fort.

@@ -1199,6 +1201,21 @@
+
+
+
+ NEU • IHK ENTWURFSSTUDIO +

📐 IHK Diagramm-Studio: ER-Modell, Struktogramm (DIN 66261) & UML 2.5

+

+ Visuelle Entwurfswerkzeuge für Prüfungsaufgaben: Relationale Datenbankschemata mit ANSI SQL DDL-Generierung, Nassi-Shneiderman Struktogramme mit Python-Transpiler sowie UML Klassendiagramme, Sequenzen und Use-Cases. +

+
+ + Diagramm-Studio öffnen → + +
+
+

AP1: IT-Arbeitsplatz & Basissysteme (LF 1–8)

Gemeinsame Kernqualifikationen für alle IT-Berufe: Verträge, Ergonomie, Subnetting, BSI, BPMN 2.0, Belege & Netzplan.

diff --git a/diagram_editor.html b/diagram_editor.html new file mode 100644 index 0000000..e770d35 --- /dev/null +++ b/diagram_editor.html @@ -0,0 +1,87 @@ + + + + + + IHK Diagramm-Studio | ER-Modell, Struktogramm & UML Editor + + + + + + + + + +
+ +
+ + +
+
+
+ 📐 IHK Entwurfswerkzeuge + • + Standard DIN 66261, ANSI SQL & UML 2.5 +
+

+ IHK Diagramm-Studio: ER-Modell, Struktogramm & UML +

+

+ Visuelle Modellierung und Codegenerierung für Fachinformatiker (AP1 & AP2). Erstelle relationale Schemata, Nassi-Shneiderman Struktogramme mit Python-Transpiler und objektorientierte UML-Klassenhierarchien. +

+
+
+ + +
+
+ + + +
+ + +
+ + + + + + +
+ +
+ IT-Praxisportal • IHK Prüfungsvorbereitung für Fachinformatiker • Standard DIN 66261 & ISO/IEC 19505 +
+ + + + + + + + diff --git a/index.html b/index.html index ce30269..ea049e1 100644 --- a/index.html +++ b/index.html @@ -268,6 +268,7 @@ Funktionen Monaco Web-IDE IHK-Prüfung + Diagramm-Studio Zielgruppen @@ -301,6 +302,7 @@ Funktionen Monaco Web-IDE IHK Prüfungstrainer + 📐 Diagramm-Studio (ER, SG, UML) Impressum Datenschutzerklärung diff --git a/workspace.html b/workspace.html index 8744893..a254cc8 100644 --- a/workspace.html +++ b/workspace.html @@ -241,10 +241,11 @@
+ 📐 Diagramme - +