docs: technische Dokumentation + ADRs
- docs/ARCHITEKTUR.md: Module, Datenmodell, Request-Fluss, RBAC-Matrix, ITIL-Abbildung, Sicherheitsarchitektur - docs/BETRIEB.md: ENV-Referenz, Deployment-/Update-Prozedur (SRV1361746), Backup/Retention, Troubleshooting (inkl. Werkzeug-Hash-Diagnose, Rate-Limit-Entsperrung), Monitoring-Punkte - docs/adr/: ADR-001..008 (Rust-Rewrite, Server-Sessions, Hash-Migration inkl. scrypt-Lehre, ITIL-Rollen/Change-Gate, idempotente Migrationen, Multi-Stage-Build, CSP, SSRF-Policy) + Index - README: Verweis auf docs/ und Prozess-Doku in mscadm/AES projects/itsm (dev)
This commit is contained in:
parent
f73239211c
commit
97facdce18
|
|
@ -45,6 +45,14 @@ werden beim ersten Login transparent auf Argon2id umgestellt.
|
||||||
- DSGVO-Retention: automatische Bereinigung von Tickets/Audit-Log gemaess
|
- DSGVO-Retention: automatische Bereinigung von Tickets/Audit-Log gemaess
|
||||||
Mandanten-Fristen; Login-Versuche nach 7 Tagen, abgelaufene Sessions sofort.
|
Mandanten-Fristen; Login-Versuche nach 7 Tagen, abgelaufene Sessions sofort.
|
||||||
|
|
||||||
|
## Dokumentation
|
||||||
|
|
||||||
|
- `docs/ARCHITEKTUR.md` — technische Architektur (Module, Datenmodell, Request-Fluss, RBAC-Matrix, ITIL-Abbildung)
|
||||||
|
- `docs/BETRIEB.md` — Betriebshandbuch (ENV, Deployment/Update, Backup, Troubleshooting)
|
||||||
|
- `docs/adr/` — Architecture Decision Records (ADR-001 bis ADR-008)
|
||||||
|
- Prozess-Doku nach AES-Regelwerk (Projektbeschreibung, Phasen-/Forensik-Reports, Tasks):
|
||||||
|
`mscadm/AES`, Branch `dev`, Verzeichnis `projects/itsm/`
|
||||||
|
|
||||||
## Betrieb
|
## Betrieb
|
||||||
|
|
||||||
# Entwicklung
|
# Entwicklung
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,104 @@
|
||||||
|
# ITSM — Technische Architektur
|
||||||
|
|
||||||
|
Stand: 2026-07-15 (Rust-Rewrite, Version 1.0). Verbindliche Entscheidungen: siehe `docs/adr/`.
|
||||||
|
|
||||||
|
## 1. Überblick
|
||||||
|
|
||||||
|
Mandantenfähige ITSM-Plattform (Tickets, Service-Katalog, Wissensdatenbank, CMDB)
|
||||||
|
als einzelnes Rust-Binary mit PostgreSQL. AES ist ein buchbarer Service im
|
||||||
|
Katalog (HTTP-Integration, kein Code-Import). Repo-Bearbeitung aus Tickets
|
||||||
|
läuft über die Gitea-kompatible Forge-Contents-API.
|
||||||
|
|
||||||
|
Browser ──HTTP──► itsm (axum, Port 8090) ──► PostgreSQL 16
|
||||||
|
│
|
||||||
|
├──► AES-Dashboard (Erreichbarkeits-Check, Katalog)
|
||||||
|
└──► Forge-API (Repo-Edit aus Change-Tickets)
|
||||||
|
|
||||||
|
## 2. Modulstruktur (src/)
|
||||||
|
|
||||||
|
| Modul | Verantwortung |
|
||||||
|
|---|---|
|
||||||
|
| `main.rs` | App-Aufbau, Routing, Retention-Hintergrundtask |
|
||||||
|
| `config.rs` | ENV-Konfiguration + Startvalidierung |
|
||||||
|
| `web.rs` | AppState, Middleware (Session, CSRF, Client-IP, Security-Header), Fehlertyp, RBAC-Helfer, PageCtx |
|
||||||
|
| `db.rs` | Datenzugriff (deadpool/tokio-postgres), alle Queries parametrisiert und tenant-gescoped |
|
||||||
|
| `security.rs` | Passwort-Hashing/-Verifikation (Argon2id + Werkzeug pbkdf2/scrypt), Token, Policy, SSRF-Guard |
|
||||||
|
| `itil.rs` | ITIL-Logik: Rollen, Statusmodell, Prioritätsmatrix, Change-Gate (reine Funktionen, unit-getestet) |
|
||||||
|
| `auth.rs` | Login (Rate-Limit, Rehash-Migration), Logout, Ersteinrichtung |
|
||||||
|
| `tickets.rs` | Listen/KPIs/Sparklines, Ticket-API, Statuswechsel, Change-Freigabe, Problem/CI-Links, Repo-Edit, Kanban |
|
||||||
|
| `services.rs` | Service-Katalog, paralleler Erreichbarkeits-Check (30 s Cache) |
|
||||||
|
| `kb.rs` | Wissensdatenbank inkl. Freigabe-Workflow |
|
||||||
|
| `cmdb.rs` | Configuration Items + Beziehungen |
|
||||||
|
| `admin.rs` | Mandanten-Einstellungen, Audit-Ansicht, Benutzerverwaltung |
|
||||||
|
| `dashboard.rs` | CSI-Kennzahlen (SLA-Erfüllung, MTTR, Verteilungen) |
|
||||||
|
| `forge.rs` | Forge-Contents-Client (get/update, Sha-Locking) |
|
||||||
|
|
||||||
|
Frontend: Askama-Templates (`templates/`), ein statisches CSS + ein JS
|
||||||
|
(`static/`), kein Framework, kein Inline-JS (CSP).
|
||||||
|
|
||||||
|
## 3. Request-Fluss
|
||||||
|
|
||||||
|
1. `ctx_middleware` (web.rs): Session-Cookie → SHA-256 → DB-Lookup (`sessions`
|
||||||
|
JOIN `users`/`tenants`) → `AuthUser` in Request-Extensions. Client-IP nur
|
||||||
|
aus X-Forwarded-For, wenn `ITSM_TRUSTED_PROXY_COUNT > 0`.
|
||||||
|
2. CSRF: jede zustandsändernde Methode braucht Token aus Session (Formular-
|
||||||
|
Feld `_csrf` oder Header `X-CSRF-Token`); Ausnahmen: `/login`, `/setup/new`.
|
||||||
|
3. Handler: RBAC-Check (`need_auth`/`need_operative`/`need_admin`/
|
||||||
|
`need_change_approver`) → Fachlogik → Audit-Log-Eintrag.
|
||||||
|
4. Antwort: Security-Header (CSP, nosniff, X-Frame-Options DENY, Referrer-
|
||||||
|
Policy, HSTS bei `ITSM_HTTPS=1`).
|
||||||
|
|
||||||
|
## 4. Datenmodell (PostgreSQL, schema.sql)
|
||||||
|
|
||||||
|
Alle fachlichen Tabellen tragen `tenant_id`; jede Query filtert darauf.
|
||||||
|
|
||||||
|
- `tenants` — Mandanten inkl. SLA-Defaults + DSGVO-Retention-Fristen
|
||||||
|
- `users` — global eindeutige E-Mail (Login löst Mandant auf), `role`
|
||||||
|
(admin/change_manager/agent/user), `auth_source` ('local', 'ldap' reserviert)
|
||||||
|
- `tickets` — inkl. ITIL-Feldern: `impact`/`urgency` (Priorität via Matrix),
|
||||||
|
`change_typ`/`approval_status`/`approved_by/-_at` (Change Enablement),
|
||||||
|
`problem_id`/`known_error` (Problem Mgmt), `first_response_at`/`resolved_at`/
|
||||||
|
`closed_at` (SLA-Zeitstempel)
|
||||||
|
- `ticket_timeline` — Worklog (Status, Kommentare, Repo-Edits: dokumentationspflichtig)
|
||||||
|
- `ticket_ci_links` — Ticket↔CI (SACM)
|
||||||
|
- `services`, `kb_articles` (mit `status` Entwurf/Freigegeben),
|
||||||
|
`configuration_items` + `ci_relationships`
|
||||||
|
- `audit_log` — alle sicherheitsrelevanten Aktionen (ISO 27001 A.12.4 / DSGVO Art. 30)
|
||||||
|
- `login_attempts` — Rate-Limit-Basis (7 Tage aufbewahrt)
|
||||||
|
- `sessions` — serverseitige Sessions, nur SHA-256-Hash des Cookie-Tokens
|
||||||
|
|
||||||
|
Migrationen: idempotenter Block am Ende von `schema.sql`, wird bei jedem
|
||||||
|
App-Start per `include_str!` ausgeführt (ADR-005).
|
||||||
|
|
||||||
|
## 5. RBAC-Matrix
|
||||||
|
|
||||||
|
| Aktion | admin | change_manager | agent | user |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| Eigene Tickets (Incident/Service Request) anlegen/sehen | ✓ | ✓ | ✓ | ✓ |
|
||||||
|
| Alle Tickets sehen/bearbeiten, Status wechseln | ✓ | ✓ | ✓ | – |
|
||||||
|
| Probleme/Änderungen/Releases/Kanban/CMDB | ✓ | ✓ | ✓ | – |
|
||||||
|
| KB lesen | alle | alle | alle | nur Freigegeben |
|
||||||
|
| KB anlegen/bearbeiten | ✓ | ✓ | ✓ | – |
|
||||||
|
| KB freigeben, Change-Freigabe (CAB) | ✓ | ✓ | – | – |
|
||||||
|
| Repo-Edit aus Ticket (nur umsetzbarer Change) | ✓ | ✓ | – | – |
|
||||||
|
| Services anlegen, Administration, Audit-Log | ✓ | – | – | – |
|
||||||
|
|
||||||
|
## 6. ITIL-Abbildung (v3-Prozesse / v4-Practices)
|
||||||
|
|
||||||
|
- Statusmodell: Offen → In Bearbeitung → Warten → Gelöst → Geschlossen;
|
||||||
|
Reopen Gelöst→Offen; Geschlossen final. Übergänge serverseitig erzwungen.
|
||||||
|
- Priorität = Matrix(Impact × Urgency), 3×3 nach v3 SO 4.2.5.4.
|
||||||
|
- Change Enablement: Standard (vorautorisiert) / Normal (CAB) / Emergency
|
||||||
|
(sofort, nachträgliche ECAB-Freigabe). Umsetzung + Repo-Edit erst wenn
|
||||||
|
`itil::change_may_be_implemented`.
|
||||||
|
- Problem Management: Incident↔Problem-Verknüpfung, Known-Error-Flag.
|
||||||
|
- SLA: Fristen aus Mandanten-Defaults, Zeitstempel bei Statuswechseln,
|
||||||
|
Überfälligkeits- und Restzeit-Berechnung serverseitig.
|
||||||
|
- Continual Improvement: /dashboard (SLA-Erfüllung 30 T, MTTR, Verteilungen).
|
||||||
|
|
||||||
|
## 7. Sicherheitsarchitektur
|
||||||
|
|
||||||
|
Siehe ADR-002/003/007/008 und README-Abschnitt "Sicherheit". Kurzfassung:
|
||||||
|
serverseitige widerrufbare Sessions, CSRF überall, DB-gestütztes Login-Rate-
|
||||||
|
Limit (E-Mail+IP), Argon2id mit transparenter Migration von Werkzeug-pbkdf2
|
||||||
|
UND -scrypt (ADR-003!), SSRF-Guard, CSP ohne Inline-JS, Audit-Log.
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
# ITSM — Betriebshandbuch
|
||||||
|
|
||||||
|
Stand: 2026-07-15. Produktivinstanz: SRV1361746 (`/docker/itsm/`), Port 8090.
|
||||||
|
|
||||||
|
## 1. ENV-Variablen
|
||||||
|
|
||||||
|
| Variable | Pflicht | Bedeutung |
|
||||||
|
|---|---|---|
|
||||||
|
| `DATABASE_URL` | ja | `postgresql://itsm:...@postgres:5432/itsm` |
|
||||||
|
| `ITSM_BIND` | – | Bind-Adresse (Default `0.0.0.0:8090`) |
|
||||||
|
| `ITSM_HTTPS` | – | `1` = Secure-Cookies + HSTS (hinter TLS-Terminierung setzen) |
|
||||||
|
| `ITSM_TRUSTED_PROXY_COUNT` | – | Anzahl vertrauenswürdiger Reverse-Proxies; nur dann zählt X-Forwarded-For |
|
||||||
|
| `ITSM_SESSION_HOURS` | – | Session-Lebensdauer (Default 8) |
|
||||||
|
| `ITSM_LOGIN_MAX_ATTEMPTS` / `ITSM_LOGIN_WINDOW_MINUTES` | – | Rate-Limit (Default 5 / 15) |
|
||||||
|
| `ITSM_PASSWORD_MIN_LENGTH` | – | Default 12 |
|
||||||
|
| `AES_DASHBOARD_URL` | – | AES-Service im Katalog |
|
||||||
|
| `FORGE_BASE_URL` / `FORGE_SERVICE_TOKEN` | – | Repo-Edit aus Change-Tickets; ohne = Feature deaktiviert |
|
||||||
|
| `RUST_LOG` | – | z. B. `info` |
|
||||||
|
|
||||||
|
Hinweis: `ITSM_SECRET_KEY` (Flask-Ära) wird nicht mehr genutzt und kann aus
|
||||||
|
der Server-`.env` entfernt werden.
|
||||||
|
|
||||||
|
## 2. Deployment / Update (SRV1361746)
|
||||||
|
|
||||||
|
cd /docker/itsm
|
||||||
|
rm -rf src && git clone --depth 1 https://git1.mrmoe.de/mscadm/ITSM.git src
|
||||||
|
docker compose build itsm # Multi-Stage-Build, ~3 min
|
||||||
|
docker compose up -d # Postgres läuft weiter, nur App wird getauscht
|
||||||
|
curl -s localhost:8090/health # {"db":"ok","status":"ok"}
|
||||||
|
|
||||||
|
Es wird KEIN Code mehr beim Container-Start gezogen (früherer bootstrap.sh-
|
||||||
|
Mechanismus ist abgeschafft) — ein Neustart startet exakt das gebaute Image.
|
||||||
|
Schema-Migrationen laufen idempotent beim App-Start.
|
||||||
|
|
||||||
|
Rollback: `docker-compose.yml.flask-backup` liegt als letzter Flask-Stand in
|
||||||
|
`/docker/itsm/`; für das Rust-Image genügt `git clone` eines älteren Tags/
|
||||||
|
Commits nach `src/` + erneutes `build`.
|
||||||
|
|
||||||
|
## 3. Backup & Retention
|
||||||
|
|
||||||
|
- Täglicher pg_dump: `deploy/backup.sh` via Cron `0 3 * * *` nach
|
||||||
|
`/docker/itsm/backups/` (14 Tage Rotation). Live-Daten: Bind-Mount
|
||||||
|
`/docker/itsm/pgdata` (kein anonymes Docker-Volume).
|
||||||
|
- DSGVO-Retention: Hintergrundtask löscht gelöste/geschlossene Tickets und
|
||||||
|
Audit-Einträge nach Mandanten-Fristen; Sessions/Login-Versuche werden
|
||||||
|
mitbereinigt. Manuell: Admin → Einstellungen → "Jetzt manuell bereinigen".
|
||||||
|
|
||||||
|
## 4. Troubleshooting
|
||||||
|
|
||||||
|
| Symptom | Ursache / Abhilfe |
|
||||||
|
|---|---|
|
||||||
|
| Login schlägt für Bestandskonto fehl | Hash-Prefix prüfen: `docker exec itsm_postgres psql -U itsm -d itsm -c "SELECT email,left(password_hash,16) FROM users;"` — `pbkdf2:`/`scrypt:` = Werkzeug-Altbestand (wird unterstützt, ab Commit f732392 auch scrypt), `$argon2id$` = migriert. |
|
||||||
|
| "Zu viele Fehlversuche" (429) | Rate-Limit-Sperre; wartet 15 min oder `DELETE FROM login_attempts;` |
|
||||||
|
| 500er | `docker logs itsm_app --tail 50` (tracing-Log) |
|
||||||
|
| Health rot | DB-Verbindung prüfen (`docker ps`, `pg_isready`) |
|
||||||
|
| Repo-Edit-Button fehlt | FORGE_BASE_URL nicht gesetzt, Rolle < change_manager, oder Ticket ist kein freigegebener Change (gewollt, ADR-004) |
|
||||||
|
| Alle Sessions ungültig nach Deploy | erwartungsgemäß NICHT der Fall (Sessions liegen in der DB); falls doch: sessions-Tabelle prüfen |
|
||||||
|
|
||||||
|
## 5. Monitoring-Punkte
|
||||||
|
|
||||||
|
- `GET /health` (Docker-HEALTHCHECK eingebaut, curl-basiert)
|
||||||
|
- Audit-Log (Admin-UI) für login_failed/login_rate_limited-Häufungen
|
||||||
|
- Backup-Log `/docker/itsm/backup.log`
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
# ADR-001: Rust/axum statt Python/Flask (In-Place-Rewrite)
|
||||||
|
|
||||||
|
**Datum:** 2026-07-15 · **Status:** Akzeptiert · **Entscheider:** Moe (Vorgabe), umgesetzt via Claude
|
||||||
|
|
||||||
|
## Kontext
|
||||||
|
Die Flask-Version (single-file, ungepinnte Dependencies) hatte 8 Sicherheits-
|
||||||
|
Review-Befunde; Nutzer-Vorgabe: alle Schwachstellen beseitigen, technisch
|
||||||
|
aktuellster Stand, ITIL v3/v4, Umstellung auf Rust. Es existierte ein
|
||||||
|
Parallelbau-Plan (AES projects/itsm-rust) mit späterem Cutover.
|
||||||
|
|
||||||
|
## Entscheidung
|
||||||
|
Kompletter In-Place-Rewrite in Rust mit axum 0.7 + tokio-postgres/deadpool +
|
||||||
|
askama — gleicher Stack wie forge-web (einheitliche Technologierichtung).
|
||||||
|
Der Parallelbau-Plan wurde auf explizite Nutzer-Anweisung übersprungen.
|
||||||
|
|
||||||
|
## Konsequenzen
|
||||||
|
+ Ein Binary, kompilierte Templates, keine Laufzeit-Dependency-Drift mehr.
|
||||||
|
+ Stack-Konsistenz mit Forge (Wartbarkeit, Wissenstransfer).
|
||||||
|
− Kein paralleles Rollback-System; kompensiert durch: identisches Schema
|
||||||
|
(nur additiv migriert), Werkzeug-Hash-Kompatibilität (ADR-003), E2E-Tests
|
||||||
|
vor Push, Compose-Backup als Rollback-Punkt.
|
||||||
|
− Lehre aus Produktion (Forensik phase-010 B4): Kompatibilitätsannahmen
|
||||||
|
gegen ECHTE Produktionsdaten testen, nicht nur gegen eigene Vektoren.
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
# ADR-002: Serverseitige Sessions in PostgreSQL
|
||||||
|
|
||||||
|
**Datum:** 2026-07-15 · **Status:** Akzeptiert
|
||||||
|
|
||||||
|
## Kontext
|
||||||
|
Flask nutzte Client-Side-Sessions (signierte Cookies) mit optionalem Secret;
|
||||||
|
ohne gesetztes Secret wurde pro Prozessstart ein Zufallswert erzeugt
|
||||||
|
(Sessions bei Neustart weg, mehrere Worker inkompatibel). Sessions waren
|
||||||
|
nicht widerrufbar.
|
||||||
|
|
||||||
|
## Entscheidung
|
||||||
|
Sessions liegen in der DB (`sessions`-Tabelle). Cookie enthält nur ein
|
||||||
|
256-Bit-Zufallstoken; die DB speichert dessen SHA-256. CSRF-Token wird pro
|
||||||
|
Session serverseitig gehalten.
|
||||||
|
|
||||||
|
## Konsequenzen
|
||||||
|
+ Widerrufbar (Logout, Kontosperrung löscht Sessions), neustart- und
|
||||||
|
multi-instanz-fest, kein Secret-Management (ITSM_SECRET_KEY entfällt).
|
||||||
|
+ DB-Leak kompromittiert keine laufenden Sessions (nur Hashes).
|
||||||
|
− Ein DB-Roundtrip pro Request (Middleware); bei aktueller Last irrelevant.
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
# ADR-003: Argon2id + Werkzeug-Kompatibilitätsschicht (pbkdf2 UND scrypt)
|
||||||
|
|
||||||
|
**Datum:** 2026-07-15 · **Status:** Akzeptiert (ergänzt um scrypt nach Prod-Befund B4)
|
||||||
|
|
||||||
|
## Kontext
|
||||||
|
Bestandskonten wurden von Werkzeug (Flask) gehasht. Zwangs-Passwort-Reset
|
||||||
|
für den Produktiv-Mandanten war inakzeptabel. Werkzeug erzeugt je nach
|
||||||
|
Version pbkdf2:sha256- ODER scrypt-Hashes (>= 3.0 Default: scrypt) — die
|
||||||
|
Flask-Ära installierte ungepinnt, in Produktion lagen scrypt-Hashes.
|
||||||
|
|
||||||
|
## Entscheidung
|
||||||
|
Neue Hashes: Argon2id (RustCrypto-Defaults). verify_password() erkennt
|
||||||
|
Werkzeug-Formate (`pbkdf2:sha256:*` und `scrypt:n:r:p$salt$hex`), verifiziert
|
||||||
|
in Konstantzeit und rehasht beim ersten erfolgreichen Login transparent auf
|
||||||
|
Argon2id (schleichende Migration, kein Reset).
|
||||||
|
|
||||||
|
## Konsequenzen
|
||||||
|
+ Bestandslogins funktionieren unverändert; Bestand härtet sich selbst nach.
|
||||||
|
− Werkzeug-Kompatibilitätscode bleibt, bis alle Konten migriert sind
|
||||||
|
(prüfbar: `SELECT count(*) FROM users WHERE password_hash NOT LIKE '$argon2%'`).
|
||||||
|
− Lehre: Der erste Wurf unterstützte nur pbkdf2 und fiel in Produktion auf
|
||||||
|
(Login-Ausfall + Rate-Limit-Sperre). Hash-Kompatibilität ist künftig gegen
|
||||||
|
reale Prod-Hash-Prefixe zu verifizieren (Forensik phase-010, Befund B4).
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
# ADR-004: ITIL-Rollenmodell + Change-Gate für Repo-Edits
|
||||||
|
|
||||||
|
**Datum:** 2026-07-15 · **Status:** Akzeptiert · **Vorgabe:** "Rollen gemäß ITIL"
|
||||||
|
|
||||||
|
## Kontext
|
||||||
|
Vorher nur admin/agent; Repo-Edit aus Tickets stand JEDEM eingeloggten
|
||||||
|
Nutzer offen (mit gemeinsamem Forge-Admin-Token) — Review-Befund.
|
||||||
|
|
||||||
|
## Entscheidung
|
||||||
|
Vier Rollen: admin (Service Owner), change_manager (Change Enablement/CAB),
|
||||||
|
agent (Service Desk), user (Requester/Self-Service: eigene Tickets, nur
|
||||||
|
freigegebene KB). Repo-Edits gelten als Change-Implementierung: erlaubt nur
|
||||||
|
für admin/change_manager UND nur aus einem umsetzbaren Change-Ticket
|
||||||
|
(Standard = vorautorisiert; Normal = nach CAB-Genehmigung; Emergency =
|
||||||
|
sofort mit nachträglicher ECAB-Freigabe; Abgelehnt = nie). Jede Repo-
|
||||||
|
Änderung bleibt worklog-pflichtig (phase-008-Garantie unverändert).
|
||||||
|
|
||||||
|
## Konsequenzen
|
||||||
|
+ Least-Privilege; auditierbarer Change-Pfad für Produktions-Repos.
|
||||||
|
− Agents müssen für Repo-Änderungen einen Change anlegen und freigeben
|
||||||
|
lassen — gewollte Prozesshürde.
|
||||||
|
− Feingranulare Forge-Repo-Rechte (statt Admin-Token) bleiben offen, bis
|
||||||
|
Forge Repo-Scopes kennt (Backlog).
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
# ADR-005: Idempotente Schema-Migrationen statt Migrationstool
|
||||||
|
|
||||||
|
**Datum:** 2026-07-15 · **Status:** Akzeptiert
|
||||||
|
|
||||||
|
## Kontext
|
||||||
|
Bestehende Prod-DB (Flask-Ära) musste ohne Migrationsschritt weiterverwendet
|
||||||
|
werden; das Projekt hat genau eine schema.sql-Historie und wenige Releases.
|
||||||
|
|
||||||
|
## Entscheidung
|
||||||
|
`schema.sql` bleibt die einzige Quelle: CREATE TABLE IF NOT EXISTS +
|
||||||
|
ADD COLUMN IF NOT EXISTS + einmalige, selbstneutralisierende UPDATEs.
|
||||||
|
Wird beim App-Start per `include_str!` komplett ausgeführt (batch_execute).
|
||||||
|
|
||||||
|
## Konsequenzen
|
||||||
|
+ Kein Migrationstool, keine Versionstabelle, Deploy = Start.
|
||||||
|
+ Kompilierzeit-Einbettung: Binary und Schema sind untrennbar konsistent.
|
||||||
|
− Destruktive Änderungen (DROP/RENAME) brauchen künftig explizite Sorgfalt
|
||||||
|
oder den Wechsel auf ein echtes Migrationstool — akzeptiert für die
|
||||||
|
aktuelle Projektgröße. Additiv-only ist Konvention.
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
# ADR-006: Multi-Stage-Docker-Build statt Clone-on-Start
|
||||||
|
|
||||||
|
**Datum:** 2026-07-15 · **Status:** Akzeptiert
|
||||||
|
|
||||||
|
## Kontext
|
||||||
|
Der frühere bootstrap.sh klonte bei JEDEM Container-Start main und
|
||||||
|
installierte ungepinnte pip-Pakete: unreproduzierbar, ungetesteter Stand
|
||||||
|
konnte durch bloßen Neustart live gehen; forge_client.py wurde dabei sogar
|
||||||
|
vergessen (Feature in Prod defekt).
|
||||||
|
|
||||||
|
## Entscheidung
|
||||||
|
Multi-Stage-Dockerfile (rust:1-slim-bookworm → debian:bookworm-slim,
|
||||||
|
USER nobody, HEALTHCHECK). deploy/docker-compose.yml baut per `build:` aus
|
||||||
|
dem geklonten Quellstand; Neustart startet exakt das gebaute Image.
|
||||||
|
|
||||||
|
## Konsequenzen
|
||||||
|
+ Reproduzierbare, testbare Deployments; Rollback = altes Image/älterer Commit.
|
||||||
|
− Update erfordert bewussten Build (~3 min auf SRV1361746) — gewollt.
|
||||||
|
− postgres:16-alpine wird beibehalten (pgdata-Kompatibilität); Major-Upgrade
|
||||||
|
wäre ein eigener Change mit pg_upgrade/dump+restore.
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
# ADR-007: CSP ohne Inline-JavaScript
|
||||||
|
|
||||||
|
**Datum:** 2026-07-15 · **Status:** Akzeptiert
|
||||||
|
|
||||||
|
## Kontext
|
||||||
|
Die Flask-Version renderte onclick-Handler und <script>-Blöcke inline;
|
||||||
|
eine wirksame Content-Security-Policy war so unmöglich.
|
||||||
|
|
||||||
|
## Entscheidung
|
||||||
|
`script-src 'self'`: alles JavaScript liegt in static/app.js (Event-
|
||||||
|
Delegation, data-Attribute); zustandsändernde fetches senden X-CSRF-Token.
|
||||||
|
`style-src 'self' 'unsafe-inline'`: Inline-STYLE-ATTRIBUTE bleiben erlaubt
|
||||||
|
(dynamische Balkenbreiten etc.) — bewusste, dokumentierte Ausnahme; Inline-
|
||||||
|
<script> bleibt verboten. Zusätzlich: X-Frame-Options DENY, nosniff,
|
||||||
|
Referrer-Policy same-origin, HSTS bei ITSM_HTTPS=1.
|
||||||
|
|
||||||
|
## Konsequenzen
|
||||||
|
+ XSS-Einschlag drastisch reduziert (kein Skript außerhalb /static ausführbar).
|
||||||
|
− style-src-Ausnahme ist ein Rest-Risiko (CSS-Injection); akzeptiert, da
|
||||||
|
alle Ausgaben escaped werden (Askama-Autoescape + esc() im JS).
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
# ADR-008: SSRF-Policy für Service-Endpoints
|
||||||
|
|
||||||
|
**Datum:** 2026-07-15 · **Status:** Akzeptiert
|
||||||
|
|
||||||
|
## Kontext
|
||||||
|
Der Service-Katalog prüft hinterlegte Endpoint-URLs serverseitig auf
|
||||||
|
Erreichbarkeit. Interne Dienste (AES-Dashboard, Forge) sind der Hauptzweck —
|
||||||
|
ein pauschales Verbot privater Adressen wäre selbstzerstörerisch; völlige
|
||||||
|
Freiheit wäre eine SSRF-Fläche.
|
||||||
|
|
||||||
|
## Entscheidung
|
||||||
|
Dreistufig: (1) Nur http/https. (2) Link-Local-/Cloud-Metadaten-Bereiche
|
||||||
|
(169.254.0.0/16, fe80::/10) sind immer blockiert, auch nach DNS-Auflösung.
|
||||||
|
(3) Private Adressen bleiben erlaubt, ABER Anlegen/Ändern von Services ist
|
||||||
|
admin-only. Checks laufen parallel mit 3 s Timeout und 30 s Cache (kein
|
||||||
|
Blockieren des Katalogs, kein Amplification-Hebel).
|
||||||
|
|
||||||
|
## Konsequenzen
|
||||||
|
+ Interner Hauptzweck bleibt nutzbar; Metadaten-Endpoints unerreichbar.
|
||||||
|
− Ein böswilliger ADMIN könnte interne Adressen sondieren — akzeptiertes
|
||||||
|
Restrisiko (Admin ist ohnehin höchstprivilegiert, Aktionen auditiert).
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
# Architecture Decision Records (ADRs)
|
||||||
|
|
||||||
|
Format: Kontext → Entscheidung → Konsequenzen. Status: Akzeptiert, sofern
|
||||||
|
nicht anders vermerkt. Nummerierung chronologisch, Änderungen nur durch
|
||||||
|
neue, ersetzende ADRs (nie stillschweigend editieren).
|
||||||
|
|
||||||
|
| Nr | Titel | Datum |
|
||||||
|
|---|---|---|
|
||||||
|
| 001 | Rust/axum statt Python/Flask (In-Place-Rewrite) | 2026-07-15 |
|
||||||
|
| 002 | Serverseitige Sessions in PostgreSQL | 2026-07-15 |
|
||||||
|
| 003 | Argon2id + Werkzeug-Kompatibilitätsschicht (pbkdf2 UND scrypt) | 2026-07-15 |
|
||||||
|
| 004 | ITIL-Rollenmodell + Change-Gate für Repo-Edits | 2026-07-15 |
|
||||||
|
| 005 | Idempotente Schema-Migrationen statt Migrationstool | 2026-07-15 |
|
||||||
|
| 006 | Multi-Stage-Docker-Build statt Clone-on-Start | 2026-07-15 |
|
||||||
|
| 007 | CSP ohne Inline-JavaScript | 2026-07-15 |
|
||||||
|
| 008 | SSRF-Policy für Service-Endpoints | 2026-07-15 |
|
||||||
Loading…
Reference in New Issue