docs: PRD und Implementierungsplan für das VPN-Konfig-Portal
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NBHF4R9EAejDJUMdwr6C68
This commit is contained in:
commit
bb84607bf8
2 changed files with 9294 additions and 0 deletions
293
PRD.md
Normal file
293
PRD.md
Normal file
|
|
@ -0,0 +1,293 @@
|
|||
# PRD: VPN-Konfig-Portal für OPNsense
|
||||
|
||||
## 1. Überblick
|
||||
|
||||
Self-Service-Webportal, über das Endnutzer ihre persönliche OpenVPN-Client-Konfiguration von einer OPNsense-Firewall beziehen. Das Portal läuft als statisches Single-Binary (Go) auf einer Linux-VM oder einem Raspberry Pi im Kundennetz (on-premise, eine Installation pro Kunde). Authentifizierung erfolgt ausschließlich gegen das Active Directory des Kunden. Das Portal ist ein reiner **Read-only-Verteiler**: Es erstellt, ändert oder widerruft keine Zertifikate und schreibt nichts auf die Firewall.
|
||||
|
||||
**Zielgruppe Endnutzer:** Mitarbeitende des Kunden (deutschsprachig).
|
||||
**Zielgruppe Betreiber:** IT-Admins des Kunden bzw. der Dienstleister, der das Portal installiert.
|
||||
|
||||
## 2. Ziele / Nicht-Ziele
|
||||
|
||||
### Ziele (v1)
|
||||
- Endnutzer melden sich mit ihren AD-Zugangsdaten an und laden ihre OpenVPN-Konfiguration(en) herunter.
|
||||
- Nur Mitglieder einer konfigurierbaren AD-Gruppe erhalten Zugang.
|
||||
- Revozierte und abgelaufene Zertifikate werden niemals ausgeliefert.
|
||||
- Vollständiges, strukturiertes Audit-Log (wer hat wann welche Config bezogen).
|
||||
- Inbetriebnahme in unter 30 Minuten durch `check`-Kommando und mitgelieferte systemd-Unit.
|
||||
|
||||
### Nicht-Ziele (bewusst out of scope für v1)
|
||||
- **WireGuard** (geplant für v2, anderes Key-Modell)
|
||||
- Zertifikats-Self-Service (Erstellen, Erneuern, Revozieren)
|
||||
- Admin-Web-UI (Logs per SSH/Monitoring, `/healthz` für externes Monitoring)
|
||||
- RADIUS- oder lokale Portal-User (Kundschaft ist ausschließlich AD)
|
||||
- Multi-Tenancy / mehrere OPNsense-Instanzen pro Portal
|
||||
- Offizieller Windows-Support (Linux-only; Windows-Cross-Build allenfalls experimentell)
|
||||
- Passwortänderung im Portal
|
||||
- Export-Overrides per API — es gelten immer die auf der OPNsense hinterlegten Export-Einstellungen
|
||||
- Mehrsprachigkeit (UI rein Deutsch; `strings`-Map-Struktur trotzdem anlegen, damit i18n später nachrüstbar ist)
|
||||
- Auto-Update (nur optionaler, abschaltbarer Read-only-Versionscheck)
|
||||
|
||||
## 3. Architektur & Plattform
|
||||
|
||||
- **Sprache:** Go, `CGO_ENABLED=0`, statisch gelinkt. Keine Libraries mit CGO-Zwang.
|
||||
- **Targets:** `linux-amd64` und `linux-arm64` (Raspberry Pi), beide aus derselben CI-Pipeline.
|
||||
- **Deployment:** Ein Binary + eine YAML-Config. Lieferumfang des Release-Archivs:
|
||||
- Binary
|
||||
- Beispiel-`config.yaml` mit auskommentierten Optionen
|
||||
- systemd-Unit (gehärtet: dedizierter User oder `DynamicUser=`, `ProtectSystem=strict`, `LoadCredential=` für Secrets)
|
||||
- README/Installationsanleitung
|
||||
- **State:** Sessions ausschließlich im RAM (Neustart = alle abgemeldet, akzeptiert). Keine Datenbank. Disaster Recovery = Config-Datei + Binary.
|
||||
- **Schreiblast minimal halten** (SD-Karten auf dem Pi): nur Audit-Log wird geschrieben, Rotation mit Größenlimit, kein Debug-Level im Normalbetrieb.
|
||||
- **Releases:** signierte Binaries, Versionsnummer sichtbar via `vpnportal version`, im Startup-Log und im Footer der Login-Seite.
|
||||
|
||||
### CLI-Kommandos
|
||||
- `vpnportal serve` — Portal starten (Default)
|
||||
- `vpnportal check` — komplette Kette testen (siehe 7. Startup-Validierung)
|
||||
- `vpnportal version` — Version, Commit, Build-Datum
|
||||
|
||||
## 4. Konfiguration
|
||||
|
||||
Format: **YAML**, Strict-Parsing (unbekannte Felder = Startabbruch mit klarer Fehlermeldung, `yaml.KnownFields(true)`).
|
||||
|
||||
```yaml
|
||||
portal:
|
||||
listen: "0.0.0.0:8443"
|
||||
tls_cert: "/etc/vpnportal/portal.crt" # HTTPS ist Pflicht
|
||||
tls_key: "/etc/vpnportal/portal.key"
|
||||
session_ttl: "10m" # Default: 10m
|
||||
title: "VPN-Portal Firma GmbH" # White-Label
|
||||
logo_file: "/etc/vpnportal/logo.png" # optional
|
||||
support_contact: "it-helpdesk@firma.de" # wird bei "kein Zertifikat" angezeigt
|
||||
update_check: false # optionaler Read-only-Versionscheck, Default aus
|
||||
|
||||
opnsense:
|
||||
url: "https://fw01.firma.local"
|
||||
api_key: "..." # alternativ: api_key_file
|
||||
api_secret: "..." # alternativ: api_secret_file
|
||||
ca_file: "/etc/vpnportal/firma-ca.pem" # optional
|
||||
# insecure_skip_verify: true # nur für Tests; loggt bei jedem Start eine Warnung
|
||||
|
||||
ad:
|
||||
domain: "firma.local" # Base-DN wird abgeleitet (DC=firma,DC=local)
|
||||
# base_dn: "..." # optionaler Override
|
||||
servers: # Hostnamen, keine IPs (LDAPS-Zertifikatsprüfung!)
|
||||
- "dc01.firma.local"
|
||||
- "dc02.firma.local"
|
||||
# port: 636 # LDAPS ist Default; StartTLS auf 389 als Option
|
||||
bind_user: "svc-vpnportal@firma.local" # unprivilegierter Domain-User genügt
|
||||
bind_password: "..." # alternativ: bind_password_file
|
||||
vpn_group: "VPN-Users" # Gruppen-DN wird beim Start aufgelöst und gecacht
|
||||
ca_file: "/etc/vpnportal/firma-ca.pem"
|
||||
|
||||
matching:
|
||||
cn_pattern: "{username}" # Default: CN == sAMAccountName (lowercase)
|
||||
# Templates wie "{username}@firma.de" möglich
|
||||
# cn_regex: "..." # Notnagel für Bestandsumgebungen
|
||||
|
||||
logging:
|
||||
level: "info"
|
||||
audit_log: "/var/log/vpnportal/audit.log"
|
||||
max_size_mb: 50 # eingebaute Rotation
|
||||
max_backups: 5
|
||||
compress: true
|
||||
```
|
||||
|
||||
### Secrets-Handling
|
||||
- Jede Secret-Option zusätzlich als `*_file`-Variante (kompatibel mit systemd `LoadCredential`, Ansible Vault, Docker Secrets).
|
||||
- Env-Var-Overrides: `VPNPORTAL_AD_BIND_PASSWORD`, `VPNPORTAL_OPNSENSE_API_SECRET` etc.
|
||||
- **Startabbruch**, wenn die Config-Datei group-/world-readable ist (mehr als 0640). Gleiches gilt für Secret-Dateien und das Audit-Log-Verzeichnis.
|
||||
- Keine verschlüsselte Config (Key läge daneben = Sicherheitstheater). Schutz = Dateirechte + Doku.
|
||||
|
||||
## 5. Authentifizierung (Active Directory)
|
||||
|
||||
### Login-Flow (Zwei-Schritt-Bind)
|
||||
1. Service-Account bindet, sucht den User:
|
||||
`(&(objectClass=user)(|(sAMAccountName=%s)(userPrincipalName=%s)))`
|
||||
— User-Eingabe zwingend mit `ldap.EscapeFilter` escapen (LDAP-Injection).
|
||||
2. Bind mit gefundenem DN und eingegebenem Passwort → Erfolg = authentifiziert.
|
||||
3. Auf der Service-Verbindung: Gruppenprüfung inkl. verschachtelter Gruppen per Matching-Rule-Filter:
|
||||
`(&(sAMAccountName=%s)(memberOf:1.2.840.113556.1.4.1941:=<Gruppen-DN>))`
|
||||
4. Kanonische Identität: **`sAMAccountName` aus dem Verzeichnis gelesen** (nie die User-Eingabe), **lowercase**.
|
||||
|
||||
### Anforderungen
|
||||
- Library: `github.com/go-ldap/ldap/v3`.
|
||||
- LDAPS (636) Default, StartTLS (389) als Option. TLS-Verify immer an; interne CA per `ca_file`. Keine Insecure-Option für LDAP.
|
||||
- DC-Liste mit Failover; Dial-/Request-Timeout 5–10 s, dann nächster DC.
|
||||
- Referrals ignorieren (Single-Domain; Multi-Domain-Forest dokumentiert nicht unterstützt).
|
||||
- Gruppen-DN beim Start aus dem konfigurierten Namen auflösen und cachen.
|
||||
- Rollen-Vorbereitung im Datenmodell: `Identity{Username string; Groups []string}` — eine spätere `VPN-Portal-Admins`-Gruppe muss ohne Umbau möglich sein.
|
||||
|
||||
### Fehlerbehandlung (AD Error 49, `data`-Codes)
|
||||
| data | Bedeutung | Anzeige an User | Audit-Log `reason` |
|
||||
|---|---|---|---|
|
||||
| 52e | Falsche Credentials | generisch: „Anmeldung fehlgeschlagen" | `invalid_credentials` |
|
||||
| 533 | Account deaktiviert | generisch | `account_disabled` |
|
||||
| 775 | Account gesperrt (Lockout) | generisch | `account_locked` |
|
||||
| 532 | Passwort abgelaufen | **Ausnahme:** „Ihr Passwort ist abgelaufen" | `password_expired` |
|
||||
| 773 | Passwortänderung erforderlich | generisch; Fall in Doku beschreiben | `password_change_required` |
|
||||
| — | Nicht in VPN-Gruppe | generisch | `not_in_vpn_group` |
|
||||
|
||||
- Identische Fehlermeldung und möglichst gleiche Antwortzeit für „User existiert nicht" vs. „Passwort falsch" (kein User-Enumeration-Orakel).
|
||||
|
||||
### Brute-Force-Schutz
|
||||
- Rate-Limiting pro Username **und** pro Quell-IP (NAT-Fall).
|
||||
- Exponentieller Backoff statt hartem Lockout (kein Aussperren von Kollegen durch Dritte).
|
||||
- Ereignis `rate_limited` ins Audit-Log.
|
||||
|
||||
## 6. OPNsense-Anbindung
|
||||
|
||||
### Grundsätze
|
||||
- Dedizierter API-User auf der OPNsense mit **ausschließlich** dem Export-Privileg („VPN: OpenVPN Client Export"). Niemals Admin-Key.
|
||||
- Read-only: Portal nutzt nur die Export-API, keine schreibenden Endpunkte.
|
||||
- **Keine Export-Overrides:** Format-/Optionseinstellungen der Firewall sind die Quelle der Wahrheit. Das Portal bietet dem User nur die Formatwahl an (inline `.ovpn`, Viscosity), alles Weitere konfiguriert der Admin auf der OPNsense.
|
||||
- Minimal unterstützte OPNsense-Version pinnen und dokumentieren; `check` prüft die erwarteten Endpunkte.
|
||||
|
||||
### Flow pro Anfrage (nach Login)
|
||||
1. `GET /api/openvpn/export/providers` — Liste der exportierbaren VPN-Instanzen. Einzige erlaubte Cache-Ausnahme: wenige Minuten TTL.
|
||||
2. Pro Instanz `GET /api/openvpn/export/accounts/{vpnid}` — Zertifikate mit CN. **Immer live, kein Cache** (Revocation muss sofort greifen).
|
||||
3. Filter:
|
||||
- CN matcht `cn_pattern` (bzw. `cn_regex`) gegen den kanonischen Username.
|
||||
- Zertifikat ist **nicht revoked** und **nicht abgelaufen** (Ablauf zusätzlich selbst gegen Systemzeit prüfen — Feldverhalten der API pro Version in Integrationstests verifizieren).
|
||||
4. Treffer anzeigen; bei mehreren Zertifikaten Auswahl mit VPN-Instanzname + Ablaufdatum.
|
||||
5. Download: Export-Endpoint aufrufen, Datei **durchstreamen** — niemals auf Platte zwischenspeichern, kein Caching.
|
||||
|
||||
### Autorisierung des Downloads
|
||||
- **Serverseitige Prüfung zwingend:** Vor jedem Download erneut verifizieren, dass das angeforderte Zertifikat zum eingeloggten User gehört (UI-Auswahl ist keine Autorisierung). Verstoß → HTTP 403 + Audit-Event `download_denied`.
|
||||
|
||||
### Fehlerfälle
|
||||
- OPNsense nicht erreichbar → eigene Fehlerseite („Dienst vorübergehend nicht verfügbar"), klar getrennt vom Fall „kein Zertifikat".
|
||||
- Kein passendes Zertifikat → handlungsleitende Meldung mit `support_contact`; Audit-Event `no_cert_found` mit Username und angewendetem Pattern. Die verglichenen CNs nur auf Debug-Level loggen.
|
||||
|
||||
## 7. Startup-Validierung (`vpnportal check`)
|
||||
|
||||
Läuft beim Start und als eigenständiges Kommando; gibt eine Checkliste aus:
|
||||
|
||||
1. Config parsen, unbekannte Felder ablehnen.
|
||||
2. Dateirechte von Config, Secret-Dateien, Log-Verzeichnis prüfen.
|
||||
3. TLS-Handshake zur OPNsense; Test-Call auf Export-Endpoint (prüft Key, Secret **und** Berechtigung).
|
||||
4. LDAPS zu jedem DC, Bind mit Service-Account, Gruppen-DN auflösen.
|
||||
5. Plausibilitätsprüfung Systemzeit (z. B. `Date`-Header der OPNsense-Antwort; >2 min Abweichung → NTP-Warnung).
|
||||
6. Ausgabe: `✓ / ✗` pro Punkt mit konkreter Fehlermeldung (bei TLS-Fehlern Zertifikatskette ausgeben).
|
||||
|
||||
`--test-auth <user>`-Option: spielt Suche + Gruppenprüfung für einen konkreten User durch (ohne Passwort-Bind), zeigt gefundenen DN, sAMAccountName und Gruppenergebnis.
|
||||
|
||||
## 8. Web-UI
|
||||
|
||||
### Technologie
|
||||
- Server-Side Rendering mit Go `html/template`. **Kein SPA-Framework, kein npm, kein Build-Step.**
|
||||
- Alle Assets (CSS, Logo, Font) per `go:embed` im Binary.
|
||||
- JavaScript optional und minimal (z. B. Passwort-anzeigen-Toggle); UI muss ohne JS voll funktionieren.
|
||||
- Sprache: Deutsch. Strings über zentrale `strings`-Map (i18n-vorbereitet, nur DE befüllt).
|
||||
|
||||
### Seiten
|
||||
1. **Login:** Username (Hint: „mmueller oder m.mueller@firma.de"), Passwort. Konfigurierbarer Titel + Logo. Generische Fehlermeldung (Ausnahme: Passwort abgelaufen). Versionsnummer dezent im Footer.
|
||||
2. **Übersicht (nach Login):** Pro Zertifikat eine Karte: VPN-Instanzname (von der Firewall), CN, Gültig-bis, Download-Buttons pro Format. Drei Zustände:
|
||||
- genau ein Zertifikat → direkt Download-Buttons
|
||||
- mehrere → Liste, Unterscheidung über Instanzname + Ablaufdatum
|
||||
- keines → Meldung mit `support_contact`
|
||||
- Hinweis „Zertifikat läuft in X Tagen ab" bei < 30 Tagen Restlaufzeit.
|
||||
3. **Anleitungen:** Statische Kurzanleitungen pro Plattform (Windows: OpenVPN Community Client; macOS: Tunnelblick/Viscosity; iOS/Android: OpenVPN Connect). OS-Erkennung per User-Agent nur als Vorschlag, nie als Filter.
|
||||
4. **Fehlerseite:** „Dienst vorübergehend nicht verfügbar" (OPNsense down) — strikt getrennt von „kein Zertifikat".
|
||||
|
||||
### Downloads
|
||||
- **POST mit CSRF-Token** (Token in Session, Hidden-Field, Vergleich) — keine GET-Download-Links (History-/Proxy-/Prefetch-Leak).
|
||||
- Header: `Content-Disposition: attachment; filename="vpn-<instanz>-<user>.ovpn"`, `Cache-Control: no-store`.
|
||||
- `Cache-Control: no-store` auf **allen** Seiten nach Login.
|
||||
|
||||
### Sessions
|
||||
- TTL **10 Minuten**, serverseitig, im RAM. Cookie: `HttpOnly`, `Secure`, `SameSite=Strict`.
|
||||
- Sichtbarer Logout-Button; nach Download aktive Abmelde-Aufforderung.
|
||||
- Session-Ablauf → Redirect auf Login mit Hinweis „Sitzung abgelaufen".
|
||||
|
||||
## 9. Audit-Logging
|
||||
|
||||
### Format
|
||||
- **JSON Lines**, eine Zeile pro Ereignis. Zeitstempel RFC 3339 **mit Zeitzone**.
|
||||
- Gekürzte Session-ID zur Korrelation von Login und Downloads.
|
||||
|
||||
```json
|
||||
{"ts":"2026-08-14T09:32:11+02:00","event":"login_success","user":"mmueller","src_ip":"10.1.20.34","session":"a3f9"}
|
||||
{"ts":"2026-08-14T09:32:40+02:00","event":"config_download","user":"mmueller","src_ip":"10.1.20.34","session":"a3f9","vpn_instance":"VPN Homeoffice","cert_cn":"mmueller","cert_expiry":"2027-03-01","format":"ovpn"}
|
||||
```
|
||||
|
||||
### Ereignisse
|
||||
- Audit: `login_success`, `login_failed` (mit `reason`, siehe Tabelle oben), `logout`, `session_expired`, `config_download`, `download_denied`
|
||||
- Betrieb: `no_cert_found`, `opnsense_unreachable`, `ldap_failover`, `rate_limited`, `startup` (mit Version + Config-Checksumme)
|
||||
|
||||
### Verbotene Log-Inhalte
|
||||
- **Passwörter — niemals.** Schutz gegen „Passwort im Username-Feld": Bei `login_failed` mit **unbekanntem** User den eingegebenen Namen nicht im Klartext loggen (`user: "<unknown>"` + IP genügt für Brute-Force-Erkennung). Klartext-Username nur bei im AD gefundenen Usern.
|
||||
- Config-Inhalte, Zertifikate, private Keys.
|
||||
- Session-Tokens in voller Länge.
|
||||
- Vollständige CN-Listen der Firewall auf Info-Level (nur Debug; Debug im Normalbetrieb aus).
|
||||
|
||||
### Rotation & Weiterleitung
|
||||
- Eingebaute größenbasierte Rotation mit Kompression (Default; SD-Karten-schonend).
|
||||
- SIGHUP-Handling für logrotate-Umgebungen.
|
||||
- Parallel alles nach stdout (systemd-Journal) → Kunde kann per Journal/Syslog zentral weiterleiten.
|
||||
- Doku-Satz: „Für revisionssichere Aufbewahrung Log an zentrales Logsystem weiterleiten." Empfehlung Aufbewahrung: 90 Tage (kundenseitig anpassbar).
|
||||
- Log-Dateien mit restriktiven Rechten (enthalten personenbezogene Daten).
|
||||
|
||||
## 10. Betrieb & Monitoring
|
||||
|
||||
- `GET /healthz` (ohne Auth, ohne sensible Details): Status Portal, OPNsense-Erreichbarkeit, DC-Erreichbarkeit — für Zabbix/CheckMK etc.
|
||||
- Kein Auto-Update. Optionaler, per Config abschaltbarer Read-only-Versionscheck gegen die Release-URL (Default: aus; in Doku transparent machen, dass er nach Hause telefoniert).
|
||||
- Doku-Hinweise: Pi in abschließbaren Netzwerkschrank (SD-Karte trägt Secrets), SSD statt SD-Karte empfehlen, NTP-Pflicht.
|
||||
|
||||
## 11. Sicherheitsanforderungen (Zusammenfassung)
|
||||
|
||||
- HTTPS-Pflicht am Portal; TLS-Verify gegen OPNsense und AD immer an (interne CA konfigurierbar; OPNsense-Insecure-Option nur mit permanenter Start-Warnung, für LDAP gar nicht).
|
||||
- LDAP-Injection-Schutz (`ldap.EscapeFilter`).
|
||||
- CSRF-Schutz auf allen zustandsändernden Requests inkl. Downloads.
|
||||
- Serverseitige Autorisierung jedes Downloads (Cert gehört zum User).
|
||||
- Kein Persistieren von Configs/Zertifikaten auf dem Portal (Streaming only, `no-store`).
|
||||
- Rate-Limiting + Backoff; keine User-Enumeration über Fehlermeldungen oder Timing.
|
||||
- Config-/Secret-/Log-Dateirechte werden erzwungen (Startabbruch).
|
||||
- Security-Header: `Strict-Transport-Security`, `X-Content-Type-Options: nosniff`, restriktive `Content-Security-Policy` (SSR ohne externe Ressourcen macht das trivial), `Referrer-Policy: no-referrer`.
|
||||
|
||||
## 12. Akzeptanzkriterien (Auswahl)
|
||||
|
||||
1. User in `VPN-Users` mit gültigem Zertifikat (CN = sAMAccountName) kann sich anmelden und erhält eine funktionierende `.ovpn`.
|
||||
2. User mit korrektem AD-Passwort, aber ohne Gruppenmitgliedschaft, wird mit generischer Meldung abgewiesen; Log enthält `not_in_vpn_group`.
|
||||
3. Ein auf der OPNsense revoziertes Zertifikat erscheint **sofort** (ohne Neustart, ohne Cache-Verzögerung) nicht mehr im Portal.
|
||||
4. Deaktivierter AD-Account kann sich nicht anmelden.
|
||||
5. Manipulierte Download-Anfrage auf ein fremdes Zertifikat → 403 + `download_denied` im Log.
|
||||
6. `vpnportal check` erkennt und benennt: falsches API-Secret, fehlende API-Berechtigung, nicht erreichbaren DC, falsche CA, zu offene Config-Dateirechte, unbekannte Config-Felder (Tippfehler).
|
||||
7. Ausfall von dc01 → Login funktioniert via dc02; Log enthält `ldap_failover`.
|
||||
8. Passwort versehentlich ins Username-Feld eingegeben → erscheint nirgends im Log.
|
||||
9. Download-Antworten tragen `Cache-Control: no-store`; Download nur per POST mit gültigem CSRF-Token.
|
||||
10. Binary läuft identisch konfiguriert auf amd64-VM und Raspberry Pi (arm64).
|
||||
11. Portal-Neustart: alle Sessions beendet, keine weiteren Datenverluste.
|
||||
12. UI ist ohne JavaScript vollständig bedienbar.
|
||||
|
||||
## 13. Implementierungshinweise
|
||||
|
||||
- LDAP: `github.com/go-ldap/ldap/v3`
|
||||
- YAML: Strict-Parsing (`KnownFields(true)` / `DisallowUnknownFields`)
|
||||
- Keine CGO-Dependencies (Cross-Build arm64)
|
||||
- Vorgeschlagene Package-Struktur:
|
||||
- `cmd/vpnportal/` — main, CLI (serve/check/version)
|
||||
- `internal/config/` — Laden, Validierung, Dateirechte-Check
|
||||
- `internal/auth/` — `Authenticator`-Interface + AD-Implementierung
|
||||
- `internal/opnsense/` — API-Client (providers, accounts, export)
|
||||
- `internal/web/` — Handler, Templates (embed), Sessions, CSRF, Rate-Limiting
|
||||
- `internal/audit/` — JSONL-Logger, Rotation
|
||||
- `Authenticator`-Interface schmal halten (Vorbereitung OIDC/Entra als mögliches v1.x):
|
||||
|
||||
```go
|
||||
type Authenticator interface {
|
||||
Authenticate(ctx context.Context, username, password string) (*Identity, error)
|
||||
}
|
||||
|
||||
type Identity struct {
|
||||
Username string // kanonisiert: sAMAccountName, lowercase
|
||||
Groups []string
|
||||
}
|
||||
```
|
||||
|
||||
- Integrationstests gegen eine echte OPNsense-Testinstanz für: Feldverhalten revozierter/abgelaufener Zertifikate, Export-Formate, API-Versionsdrift.
|
||||
|
||||
## 14. Roadmap-Notizen (nicht v1)
|
||||
|
||||
- **v1.x:** OIDC/Entra ID als zweites Auth-Backend; ggf. Admin-Ansicht (Gruppe `VPN-Portal-Admins` ist im Datenmodell vorbereitet)
|
||||
- **v2:** WireGuard-Support (clientseitige Key-Erzeugung, eigenes Konzept nötig); ggf. mehrere Export-Quellen pro Portal (Zweigstellen-Szenario), explizit **kein** Mandantenmodell
|
||||
Loading…
Add table
Add a link
Reference in a new issue