Merge branch 'feature/oidc-validation'
This commit is contained in:
@ -10,6 +10,11 @@ By: Codex (GPT-5)
|
|||||||
| 28.12.2025 | 🏗️ Planning | ID: EPIC_000007 und US_000020-US_000023 dokumentiert. By: Codex (GPT-5) |
|
| 28.12.2025 | 🏗️ Planning | ID: EPIC_000007 und US_000020-US_000023 dokumentiert. By: Codex (GPT-5) |
|
||||||
| 28.12.2025 | 🏗️ Planning | ID: US_000024 dokumentiert; OIDC- und Login-Stories praezisiert. By: Codex (GPT-5) |
|
| 28.12.2025 | 🏗️ Planning | ID: US_000024 dokumentiert; OIDC- und Login-Stories praezisiert. By: Codex (GPT-5) |
|
||||||
| 28.12.2025 | 🏗️ Planning | ID: US_000024 Theme-Assets unter assets/design vorbereitet. By: Codex (GPT-5) |
|
| 28.12.2025 | 🏗️ Planning | ID: US_000024 Theme-Assets unter assets/design vorbereitet. By: Codex (GPT-5) |
|
||||||
|
| 28.12.2025 | 🏗️ Planning | ID: US_000025 und TASK_000025-TASK_000026 fuer OIDC-Validierung gestartet. By: Codex (GPT-5) |
|
||||||
|
| 28.12.2025 | 📝 Req | ID: US_000025 Runbook/Validierungsschritte dokumentiert. By: Codex (GPT-5) |
|
||||||
|
| 28.12.2025 | 📝 Req | ID: US_000025 Validation blocked (Service/IdP nicht bereit). By: Codex (GPT-5) |
|
||||||
|
| 28.12.2025 | 🏗️ Planning | ID: PR-Vorbereitung fuer feature/oidc-validation geplant. By: Codex (GPT-5) |
|
||||||
|
| 28.12.2025 | 🏗️ Planning | ID: PAM immer aktiv; OIDC optional mit deaktivierter UI-Option dokumentiert. By: Codex (GPT-5) |
|
||||||
|
|
||||||
---
|
---
|
||||||
## Legende
|
## Legende
|
||||||
|
|||||||
35
README.md
35
README.md
@ -16,11 +16,11 @@ cd /opt/sk
|
|||||||
./scripts/install.sh
|
./scripts/install.sh
|
||||||
sudo systemctl status skd.service
|
sudo systemctl status skd.service
|
||||||
```
|
```
|
||||||
Then open `http://localhost:8000/` and set the API token in the UI.
|
Then open `http://localhost:8000/` and log in via PAM (default) to start quickly.
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
Set in `/etc/skd/env` (see `env.example`):
|
Set in `/etc/skd/env` (see `env.example`):
|
||||||
- `SKD_AUTH_MODE`: `pam` (default) or `oidc`.
|
- PAM-Login ist immer aktiv. OIDC wird zusaetzlich angeboten, wenn konfiguriert.
|
||||||
- `SKD_AUTH_SECRET`: HMAC secret for bearer tokens/cookies (set a strong value).
|
- `SKD_AUTH_SECRET`: HMAC secret for bearer tokens/cookies (set a strong value).
|
||||||
- `SKD_TOKEN_TTL_SECONDS`: token lifetime (default 900s).
|
- `SKD_TOKEN_TTL_SECONDS`: token lifetime (default 900s).
|
||||||
- `SKD_AUTH_ALLOWED_USERS`: optional comma list of accounts allowed to log in (used for PAM and as an allowlist for OIDC claims).
|
- `SKD_AUTH_ALLOWED_USERS`: optional comma list of accounts allowed to log in (used for PAM and as an allowlist for OIDC claims).
|
||||||
@ -34,6 +34,31 @@ Set in `/etc/skd/env` (see `env.example`):
|
|||||||
Notes:
|
Notes:
|
||||||
- `./scripts/install.sh` will create `/etc/skd/env` from `env.example` if missing (edit afterwards) and ensure the `skd` service user/group exist.
|
- `./scripts/install.sh` will create `/etc/skd/env` from `env.example` if missing (edit afterwards) and ensure the `skd` service user/group exist.
|
||||||
|
|
||||||
|
## OIDC Setup
|
||||||
|
OIDC ist optional. Wenn der Provider noch nicht bereit ist, bleibe bei `SKD_AUTH_MODE=pam`.
|
||||||
|
1. Issuer muss der externen URL des Providers entsprechen (TLS trust erforderlich).
|
||||||
|
2. OIDC Client registrieren (DCR), z.B.:
|
||||||
|
```bash
|
||||||
|
export SKD_OIDC_ISSUER="https://auth.example.org"
|
||||||
|
export SKD_OIDC_REDIRECT_URI="https://<device-host>[:port]/login/oidc/callback"
|
||||||
|
export OIDC_INITIAL_ACCESS_TOKEN="<initial-access-token>"
|
||||||
|
./scripts/register_oidc_client.sh
|
||||||
|
```
|
||||||
|
3. Danach in `/etc/skd/env` setzen:
|
||||||
|
```
|
||||||
|
SKD_AUTH_MODE=oidc
|
||||||
|
SKD_OIDC_ISSUER=...
|
||||||
|
SKD_OIDC_CLIENT_ID=...
|
||||||
|
SKD_OIDC_CLIENT_SECRET=...
|
||||||
|
SKD_OIDC_REDIRECT_URI=...
|
||||||
|
SKD_OIDC_SCOPES=openid profile email
|
||||||
|
SKD_SESSION_COOKIE_SECURE=true
|
||||||
|
```
|
||||||
|
Hinweise:
|
||||||
|
- Redirect-URI muss exakt sein (keine Wildcards).
|
||||||
|
- Bei Host/Port-Aenderung neu registrieren und neue Credentials setzen.
|
||||||
|
- Allowlist fuer OIDC: `SKD_AUTH_ALLOWED_USERS` prueft `preferred_username`, `email` oder `sub`.
|
||||||
|
|
||||||
## Running
|
## Running
|
||||||
- Service: managed by systemd; `./scripts/install.sh` writes the unit dynamically to `/etc/systemd/system/skd.service` with the current repo path and restarts it.
|
- Service: managed by systemd; `./scripts/install.sh` writes the unit dynamically to `/etc/systemd/system/skd.service` with the current repo path and restarts it.
|
||||||
- Manual run: `./scripts/run.sh` (uses `.venv`, defaults to `0.0.0.0:8000`).
|
- Manual run: `./scripts/run.sh` (uses `.venv`, defaults to `0.0.0.0:8000`).
|
||||||
@ -41,6 +66,12 @@ Notes:
|
|||||||
- Login (OIDC): open `http://localhost:8000/login/oidc/start` → provider → redirected back with session cookie set.
|
- Login (OIDC): open `http://localhost:8000/login/oidc/start` → provider → redirected back with session cookie set.
|
||||||
- Health: `curl -H "Authorization: Bearer <token>" http://localhost:8000/health`
|
- Health: `curl -H "Authorization: Bearer <token>" http://localhost:8000/health`
|
||||||
|
|
||||||
|
## OIDC Validation & Fallbacks
|
||||||
|
- Validierungsschritte: `docs/oidc-validation.md` (State, Token-Exchange, Claims, Cookie).
|
||||||
|
- Falls Discovery/JWKS nicht verfuegbar: OIDC deaktivieren und PAM nutzen.
|
||||||
|
- Falls DCR nicht verfuegbar: Client manuell im IdP anlegen und `SKD_OIDC_*` setzen.
|
||||||
|
- Bei Self-Signed TLS: CA im System trusten oder in Dev PAM nutzen.
|
||||||
|
|
||||||
## API (Bearer token via `/login`)
|
## API (Bearer token via `/login`)
|
||||||
- `GET /users` → `[{user, logged_in}]` (manageable system users; excludes root)
|
- `GET /users` → `[{user, logged_in}]` (manageable system users; excludes root)
|
||||||
- `POST /users/{name}/disable` with JSON `{countdown?, sound?, message?}`
|
- `POST /users/{name}/disable` with JSON `{countdown?, sound?, message?}`
|
||||||
|
|||||||
@ -29,10 +29,10 @@ templates = Jinja2Templates(directory="backend/templates")
|
|||||||
|
|
||||||
|
|
||||||
def get_oidc_client(settings: Settings = Depends(get_settings)) -> OIDCClient:
|
def get_oidc_client(settings: Settings = Depends(get_settings)) -> OIDCClient:
|
||||||
if settings.auth_mode != "oidc":
|
if not settings.oidc_enabled:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||||
detail="OIDC auth not enabled",
|
detail="OIDC not configured",
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
return OIDCClient(settings)
|
return OIDCClient(settings)
|
||||||
@ -59,7 +59,8 @@ def whoami(
|
|||||||
current_user: str = Depends(get_current_admin),
|
current_user: str = Depends(get_current_admin),
|
||||||
settings: Settings = Depends(get_settings),
|
settings: Settings = Depends(get_settings),
|
||||||
) -> dict:
|
) -> dict:
|
||||||
return {"user": current_user, "auth_mode": settings.auth_mode}
|
auth_mode = "pam+oidc" if settings.oidc_enabled else "pam"
|
||||||
|
return {"user": current_user, "auth_mode": auth_mode}
|
||||||
|
|
||||||
|
|
||||||
@app.post("/login", response_model=LoginResponse)
|
@app.post("/login", response_model=LoginResponse)
|
||||||
@ -115,7 +116,7 @@ def oidc_callback(
|
|||||||
username = oidc.extract_username(claims)
|
username = oidc.extract_username(claims)
|
||||||
if not username:
|
if not username:
|
||||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Missing username claim")
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Missing username claim")
|
||||||
if not is_authorized_admin(username, settings):
|
if not is_authorized_admin(username, settings, mode="oidc"):
|
||||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="User not authorized to log in")
|
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="User not authorized to log in")
|
||||||
|
|
||||||
token = issue_token(username, settings)
|
token = issue_token(username, settings)
|
||||||
@ -132,6 +133,11 @@ def oidc_callback(
|
|||||||
return redirect
|
return redirect
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/login/oidc/status")
|
||||||
|
def oidc_status(settings: Settings = Depends(get_settings)) -> dict:
|
||||||
|
return {"enabled": settings.oidc_enabled}
|
||||||
|
|
||||||
|
|
||||||
@app.get("/users", response_model=List[UserStatus], dependencies=[Depends(get_current_admin)])
|
@app.get("/users", response_model=List[UserStatus], dependencies=[Depends(get_current_admin)])
|
||||||
def users(settings: Settings = Depends(get_settings)) -> List[UserStatus]:
|
def users(settings: Settings = Depends(get_settings)) -> List[UserStatus]:
|
||||||
logged_in = set(actions.list_logged_in_users())
|
logged_in = set(actions.list_logged_in_users())
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import datetime as dt
|
import datetime as dt
|
||||||
import grp
|
import grp
|
||||||
import pwd
|
import pwd
|
||||||
from typing import List, Set
|
from typing import List, Set, Optional
|
||||||
|
|
||||||
import jwt
|
import jwt
|
||||||
import pam
|
import pam
|
||||||
@ -23,8 +23,8 @@ def _is_member_of(username: str, groups: Set[str]) -> bool:
|
|||||||
return bool(user_groups & groups)
|
return bool(user_groups & groups)
|
||||||
|
|
||||||
|
|
||||||
def is_authorized_admin(username: str, settings: Settings) -> bool:
|
def is_authorized_admin(username: str, settings: Settings, mode: Optional[str] = None) -> bool:
|
||||||
if settings.auth_mode == "oidc":
|
if mode == "oidc":
|
||||||
allowed_users = set(settings.auth_allowed_users)
|
allowed_users = set(settings.auth_allowed_users)
|
||||||
if allowed_users and username not in allowed_users:
|
if allowed_users and username not in allowed_users:
|
||||||
return False
|
return False
|
||||||
@ -46,12 +46,7 @@ def is_authorized_admin(username: str, settings: Settings) -> bool:
|
|||||||
|
|
||||||
|
|
||||||
def authenticate_admin_user(username: str, password: str, settings: Settings) -> None:
|
def authenticate_admin_user(username: str, password: str, settings: Settings) -> None:
|
||||||
if settings.auth_mode != "pam":
|
if not is_authorized_admin(username, settings, mode="pam"):
|
||||||
raise HTTPException(
|
|
||||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
|
||||||
detail="Password login disabled; OIDC is configured",
|
|
||||||
)
|
|
||||||
if not is_authorized_admin(username, settings):
|
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_403_FORBIDDEN,
|
status_code=status.HTTP_403_FORBIDDEN,
|
||||||
detail="User not authorized to log in",
|
detail="User not authorized to log in",
|
||||||
@ -82,7 +77,12 @@ def decode_token(token: str, settings: Settings) -> str:
|
|||||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token") from exc
|
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token") from exc
|
||||||
|
|
||||||
username = payload.get("sub")
|
username = payload.get("sub")
|
||||||
if not username or not is_authorized_admin(username, settings):
|
if not username:
|
||||||
|
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token")
|
||||||
|
if not (
|
||||||
|
is_authorized_admin(username, settings, mode="pam")
|
||||||
|
or is_authorized_admin(username, settings, mode="oidc")
|
||||||
|
):
|
||||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Unauthorized user")
|
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Unauthorized user")
|
||||||
return username
|
return username
|
||||||
|
|
||||||
|
|||||||
@ -32,6 +32,9 @@ class Settings:
|
|||||||
self.oidc_state_cookie_name: str = os.getenv(
|
self.oidc_state_cookie_name: str = os.getenv(
|
||||||
"SKD_OIDC_STATE_COOKIE_NAME", "skd_oidc_state"
|
"SKD_OIDC_STATE_COOKIE_NAME", "skd_oidc_state"
|
||||||
)
|
)
|
||||||
|
self.oidc_enabled: bool = bool(
|
||||||
|
self.oidc_issuer and self.oidc_client_id and self.oidc_client_secret
|
||||||
|
)
|
||||||
self.default_countdown: int = int(os.getenv("SKD_DEFAULT_COUNTDOWN", "60"))
|
self.default_countdown: int = int(os.getenv("SKD_DEFAULT_COUNTDOWN", "60"))
|
||||||
self.default_sound: bool = os.getenv("SKD_DEFAULT_SOUND", "false").lower() == "true"
|
self.default_sound: bool = os.getenv("SKD_DEFAULT_SOUND", "false").lower() == "true"
|
||||||
self.notify_timeout: int = int(os.getenv("SKD_NOTIFY_TIMEOUT", "5"))
|
self.notify_timeout: int = int(os.getenv("SKD_NOTIFY_TIMEOUT", "5"))
|
||||||
|
|||||||
@ -18,11 +18,11 @@
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<h3>Login (nur Root-User)</h3>
|
<h3>Login</h3>
|
||||||
<p>Bevorzugt OIDC nutzen, falls konfiguriert. Die Anmeldung öffnet den Identity Provider und setzt eine Session-Cookie.</p>
|
<p>Bevorzugt OIDC nutzen, falls konfiguriert. Die Anmeldung öffnet den Identity Provider und setzt eine Session-Cookie.</p>
|
||||||
<button id="oidcLogin" type="button">Login via OIDC</button>
|
<button id="oidcLogin" type="button">Login via OIDC</button>
|
||||||
<hr />
|
<hr />
|
||||||
<p>Lokale Anmeldung (PAM) nur falls OIDC nicht verfügbar:</p>
|
<p>Lokale Anmeldung (PAM) ist immer moeglich:</p>
|
||||||
<form id="loginForm">
|
<form id="loginForm">
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div>
|
<div>
|
||||||
@ -136,6 +136,20 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function checkOidcStatus() {
|
||||||
|
const button = document.getElementById('oidcLogin');
|
||||||
|
try {
|
||||||
|
const data = await api('/login/oidc/status');
|
||||||
|
if (!data.enabled) {
|
||||||
|
button.disabled = true;
|
||||||
|
button.title = 'OIDC nicht konfiguriert';
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
button.disabled = true;
|
||||||
|
button.title = 'OIDC-Status nicht erreichbar';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function refreshUsers() {
|
async function refreshUsers() {
|
||||||
statusDiv.textContent = 'Lade...';
|
statusDiv.textContent = 'Lade...';
|
||||||
try {
|
try {
|
||||||
@ -206,6 +220,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
checkSession();
|
checkSession();
|
||||||
|
checkOidcStatus();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
38
docs/oidc-validation.md
Normal file
38
docs/oidc-validation.md
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
ID: DOC_000003 | Version: 0.1.0 | Status: Draft
|
||||||
|
By: Codex (GPT-5)
|
||||||
|
|
||||||
|
# OIDC End-to-End Validation (Kiddo)
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
Validiere den OIDC-Login-Flow gegen einen realen oder Stub-Provider und dokumentiere Ergebnisse.
|
||||||
|
|
||||||
|
## Preconditions
|
||||||
|
- Kiddo laeuft und ist erreichbar (z.B. `http://localhost:8000`).
|
||||||
|
- OIDC Provider oder Stub erreichbar.
|
||||||
|
- `SKD_AUTH_MODE=oidc` und `SKD_OIDC_*` gesetzt.
|
||||||
|
- Redirect-URI: `https://<device-host>[:port]/login/oidc/callback` ist registriert.
|
||||||
|
|
||||||
|
## Validation Steps
|
||||||
|
1. OIDC Start
|
||||||
|
- Aufruf: `GET /login/oidc/start`
|
||||||
|
- Erwartet: Redirect zum Provider, State-Cookie gesetzt.
|
||||||
|
2. Callback
|
||||||
|
- Provider ruft `GET /login/oidc/callback?code=...&state=...` auf.
|
||||||
|
- Erwartet: State valid, Token-Exchange erfolgreich, Session-Cookie gesetzt, Redirect `/`.
|
||||||
|
3. Session Check
|
||||||
|
- Aufruf: `GET /me` mit Cookie oder Bearer.
|
||||||
|
- Erwartet: `user` und `auth_mode=oidc`.
|
||||||
|
4. Allowlist
|
||||||
|
- Falls `SKD_AUTH_ALLOWED_USERS` gesetzt: nicht erlaubte User werden mit 403 abgewiesen.
|
||||||
|
|
||||||
|
## Results
|
||||||
|
- Datum: 28.12.2025
|
||||||
|
- Provider: nicht konfiguriert (IdP noch nicht bereit)
|
||||||
|
- Host/Redirect: n/a
|
||||||
|
- Ergebnis: Blocked (Service nicht erreichbar unter http://localhost:8000/health)
|
||||||
|
- Fehlerbilder: curl (7) Couldn't connect to server
|
||||||
|
|
||||||
|
## Fallbacks bei unvollstaendigem IdP
|
||||||
|
- Discovery/JWKS fehlt: OIDC deaktivieren und PAM nutzen.
|
||||||
|
- DCR fehlt: Client manuell im IdP anlegen und `SKD_OIDC_*` setzen.
|
||||||
|
- TLS-Probleme: CA trusten oder PAM fuer Dev nutzen.
|
||||||
@ -3,7 +3,8 @@
|
|||||||
SKD_ALLOWED_USERS=child1,child2
|
SKD_ALLOWED_USERS=child1,child2
|
||||||
SKD_AUTH_SECRET=change-me-secret
|
SKD_AUTH_SECRET=change-me-secret
|
||||||
SKD_TOKEN_TTL_SECONDS=900
|
SKD_TOKEN_TTL_SECONDS=900
|
||||||
# Auth mode: pam (default) or oidc
|
# PAM ist immer aktiv; OIDC wird zusaetzlich angeboten, wenn konfiguriert.
|
||||||
|
# SKD_AUTH_MODE bleibt optional und wird derzeit nicht erzwungen.
|
||||||
SKD_AUTH_MODE=pam
|
SKD_AUTH_MODE=pam
|
||||||
SKD_AUTH_ALLOWED_USERS=
|
SKD_AUTH_ALLOWED_USERS=
|
||||||
SKD_AUTH_ALLOWED_GROUPS=sudo
|
SKD_AUTH_ALLOWED_GROUPS=sudo
|
||||||
|
|||||||
@ -45,6 +45,9 @@ Sicheres, remote steuerbares System zum Sperren/Entsperren lokaler Nutzerkonten.
|
|||||||
- [x] TASK_000008: OIDC auth callback
|
- [x] TASK_000008: OIDC auth callback
|
||||||
- [x] US_000009: Autorisierung und /me-Identitaet
|
- [x] US_000009: Autorisierung und /me-Identitaet
|
||||||
- [x] TASK_000009: Authorization /me gate
|
- [x] TASK_000009: Authorization /me gate
|
||||||
|
- [ ] US_000025: OIDC End-to-End Validierung und Runbook
|
||||||
|
- [ ] TASK_000025: OIDC E2E validation
|
||||||
|
- [ ] TASK_000026: OIDC runbook update
|
||||||
|
|
||||||
### EPIC_000004: Web UI
|
### EPIC_000004: Web UI
|
||||||
- [x] US_000010: Index-Seite ausliefern
|
- [x] US_000010: Index-Seite ausliefern
|
||||||
@ -84,7 +87,9 @@ Sicheres, remote steuerbares System zum Sperren/Entsperren lokaler Nutzerkonten.
|
|||||||
|
|
||||||
## Offene Risiken / Abhaengigkeiten
|
## Offene Risiken / Abhaengigkeiten
|
||||||
- Betrieb erfordert Root/sudo und lokale System-Tools (notify-send, sound player, uvicorn).
|
- Betrieb erfordert Root/sudo und lokale System-Tools (notify-send, sound player, uvicorn).
|
||||||
|
- OIDC-Validierung blockiert bis IdP bereit und Service laeuft.
|
||||||
|
|
||||||
## Naechste Schritte
|
## Naechste Schritte
|
||||||
- Anforderungen beim naechsten Feature-Start erweitern.
|
- Anforderungen beim naechsten Feature-Start erweitern.
|
||||||
- Tests fuer kritische Pfade evaluieren.
|
- OIDC-Validierung abschliessen und US_000025 auf Done setzen.
|
||||||
|
- PR vorbereiten: feature/oidc-validation (Summary, Risiko, Testschritte).
|
||||||
|
|||||||
@ -34,7 +34,8 @@ Stelle einen sicheren Admin-Login bereit, der Token oder Session-Cookies ausstel
|
|||||||
- Abhaengigkeit von PAM und OIDC-Provider-Verfuegbarkeit.
|
- Abhaengigkeit von PAM und OIDC-Provider-Verfuegbarkeit.
|
||||||
- Cookie-Sicherheit muss korrekt konfiguriert sein.
|
- Cookie-Sicherheit muss korrekt konfiguriert sein.
|
||||||
|
|
||||||
## Zugeordnete User Stories (Done)
|
## Zugeordnete User Stories
|
||||||
- US_000007: PAM-Login mit Token
|
- US_000007: PAM-Login mit Token
|
||||||
- US_000008: OIDC-Login Flow
|
- US_000008: OIDC-Login Flow
|
||||||
- US_000009: Autorisierung und /me-Identitaet
|
- US_000009: Autorisierung und /me-Identitaet
|
||||||
|
- US_000025: OIDC End-to-End Validierung und Runbook
|
||||||
|
|||||||
@ -13,6 +13,7 @@ Als Admin moechte ich mich per PAM-Login anmelden, damit ich ein Session-Token e
|
|||||||
- Then die Antwort enthaelt `token` und `expires_in`
|
- Then die Antwort enthaelt `token` und `expires_in`
|
||||||
- And ein Session-Cookie mit dem Token wird gesetzt
|
- And ein Session-Cookie mit dem Token wird gesetzt
|
||||||
- And der Login ist ohne OIDC-Konfiguration als Schnellstart moeglich
|
- And der Login ist ohne OIDC-Konfiguration als Schnellstart moeglich
|
||||||
|
- And PAM-Login bleibt auch bei aktivem OIDC verfuegbar
|
||||||
|
|
||||||
## Task-Platzhalter
|
## Task-Platzhalter
|
||||||
- TASK_000007: PAM login token (Details bei Story-Start)
|
- TASK_000007: PAM login token (Details bei Story-Start)
|
||||||
|
|||||||
@ -8,12 +8,13 @@ Status: Done
|
|||||||
Als Admin moechte ich mich per OIDC anmelden, damit ich ohne Passwort-Login zugreifen kann.
|
Als Admin moechte ich mich per OIDC anmelden, damit ich ohne Passwort-Login zugreifen kann.
|
||||||
|
|
||||||
## Akzeptanzkriterien
|
## Akzeptanzkriterien
|
||||||
- Given `SKD_AUTH_MODE=oidc` und ein erreichbarer OIDC-Provider
|
- Given OIDC ist konfiguriert und ein erreichbarer OIDC-Provider
|
||||||
- When ein GET auf `/login/oidc/start` erfolgt
|
- When ein GET auf `/login/oidc/start` erfolgt
|
||||||
- Then der Nutzer wird zum Provider umgeleitet und ein State-Cookie gesetzt
|
- Then der Nutzer wird zum Provider umgeleitet und ein State-Cookie gesetzt
|
||||||
- When der Provider auf `/login/oidc/callback` mit Code und State zurueckleitet
|
- When der Provider auf `/login/oidc/callback` mit Code und State zurueckleitet
|
||||||
- Then der State wird validiert und ein Session-Cookie gesetzt
|
- Then der State wird validiert und ein Session-Cookie gesetzt
|
||||||
- And bei ungueltigem State erfolgt eine 400-Antwort
|
- And bei ungueltigem State erfolgt eine 400-Antwort
|
||||||
|
- And die OIDC-Option wird deaktiviert, wenn keine OIDC-Konfiguration vorliegt
|
||||||
|
|
||||||
## Task-Platzhalter
|
## Task-Platzhalter
|
||||||
- TASK_000008: OIDC auth callback (Details bei Story-Start)
|
- TASK_000008: OIDC auth callback (Details bei Story-Start)
|
||||||
|
|||||||
@ -10,7 +10,7 @@ Als Operator moechte ich Konfigurationen per ENV setzen, damit Verhalten und Def
|
|||||||
## Akzeptanzkriterien
|
## Akzeptanzkriterien
|
||||||
- Given Umgebungsvariablen aus `env.example`
|
- Given Umgebungsvariablen aus `env.example`
|
||||||
- When der Service startet
|
- When der Service startet
|
||||||
- Then Auth- und Session-Settings werden aus ENV geladen (`SKD_AUTH_MODE`, `SKD_AUTH_SECRET`, `SKD_TOKEN_TTL_SECONDS`, `SKD_AUTH_ALLOWED_USERS`, `SKD_AUTH_ALLOWED_GROUPS`, `SKD_AUTH_PAM_SERVICE`, `SKD_SESSION_COOKIE_NAME`, `SKD_SESSION_COOKIE_SECURE`, `SKD_OIDC_STATE_COOKIE_NAME`)
|
- Then Auth- und Session-Settings werden aus ENV geladen (`SKD_AUTH_SECRET`, `SKD_TOKEN_TTL_SECONDS`, `SKD_AUTH_ALLOWED_USERS`, `SKD_AUTH_ALLOWED_GROUPS`, `SKD_AUTH_PAM_SERVICE`, `SKD_SESSION_COOKIE_NAME`, `SKD_SESSION_COOKIE_SECURE`, `SKD_OIDC_STATE_COOKIE_NAME`)
|
||||||
- And OIDC-Settings werden aus ENV geladen (`SKD_OIDC_ISSUER`, `SKD_OIDC_CLIENT_ID`, `SKD_OIDC_CLIENT_SECRET`, `SKD_OIDC_REDIRECT_URI`, `SKD_OIDC_SCOPES`)
|
- And OIDC-Settings werden aus ENV geladen (`SKD_OIDC_ISSUER`, `SKD_OIDC_CLIENT_ID`, `SKD_OIDC_CLIENT_SECRET`, `SKD_OIDC_REDIRECT_URI`, `SKD_OIDC_SCOPES`)
|
||||||
- And Allowlist/Defaults werden aus ENV geladen (`SKD_ALLOWED_USERS`, `SKD_DEFAULT_COUNTDOWN`, `SKD_DEFAULT_SOUND`, `SKD_NOTIFY_TIMEOUT`, `SKD_DRY_RUN`)
|
- And Allowlist/Defaults werden aus ENV geladen (`SKD_ALLOWED_USERS`, `SKD_DEFAULT_COUNTDOWN`, `SKD_DEFAULT_SOUND`, `SKD_NOTIFY_TIMEOUT`, `SKD_DRY_RUN`)
|
||||||
- And Sound/Notify-Pfade sind ueber ENV ueberschreibbar (`SKD_SOUND_PLAYER`, `SKD_SOUND_FILE`, `SKD_NOTIFY_SEND_PATH`)
|
- And Sound/Notify-Pfade sind ueber ENV ueberschreibbar (`SKD_SOUND_PLAYER`, `SKD_SOUND_FILE`, `SKD_NOTIFY_SEND_PATH`)
|
||||||
|
|||||||
@ -14,6 +14,7 @@ Als Admin moechte ich mich im Web-UI anmelden, Nutzer laden und Aktionen ausfueh
|
|||||||
- When ich Nutzer lade und eine Aktion sende
|
- When ich Nutzer lade und eine Aktion sende
|
||||||
- Then die Aktionsergebnisse (Steps/Status) werden als Text angezeigt
|
- Then die Aktionsergebnisse (Steps/Status) werden als Text angezeigt
|
||||||
- And Fehlerantworten werden als Text angezeigt
|
- And Fehlerantworten werden als Text angezeigt
|
||||||
|
- And die OIDC-Option ist deaktiviert, wenn keine Konfiguration vorliegt
|
||||||
|
|
||||||
## Task-Platzhalter
|
## Task-Platzhalter
|
||||||
- TASK_000022: UI login and actions (Details bei Story-Start)
|
- TASK_000022: UI login and actions (Details bei Story-Start)
|
||||||
|
|||||||
19
project-management/requirements/stories/US_000025.md
Normal file
19
project-management/requirements/stories/US_000025.md
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
ID: US_000025 | Version: 0.1.0 | Status: Draft
|
||||||
|
By: Codex (GPT-5)
|
||||||
|
|
||||||
|
# US_000025: OIDC End-to-End Validierung und Runbook
|
||||||
|
|
||||||
|
Status: Blocked
|
||||||
|
|
||||||
|
Als Betreiber moechte ich den OIDC-Login Ende-zu-Ende validieren und dokumentieren, damit der Betrieb auch mit einem noch nicht vollstaendigen IdP planbar ist.
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
- Given ein erreichbarer OIDC-Provider oder ein Stub/Test-Provider
|
||||||
|
- When der OIDC-Flow ueber `/login/oidc/start` und `/login/oidc/callback` durchlaufen wird
|
||||||
|
- Then Login, Token-Validierung und Session-Cookie werden erfolgreich nachvollzogen
|
||||||
|
- And die erforderlichen ENV-Variablen und Redirect-URIs sind im Runbook dokumentiert
|
||||||
|
- And Fallback-Optionen fuer nicht verfuegbare OIDC-Funktionen sind beschrieben
|
||||||
|
|
||||||
|
## Task-Platzhalter
|
||||||
|
- TASK_000025: OIDC E2E validation (Details bei Story-Start)
|
||||||
|
- TASK_000026: OIDC runbook update (Details bei Story-Start)
|
||||||
20
project-management/requirements/tasks/TASK_000025.md
Normal file
20
project-management/requirements/tasks/TASK_000025.md
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
ID: TASK_000025 | Version: 0.1.0 | Status: Blocked
|
||||||
|
By: Codex (GPT-5)
|
||||||
|
|
||||||
|
# TASK_000025: OIDC E2E validation
|
||||||
|
|
||||||
|
## Outcome
|
||||||
|
OIDC-Login Flow ist Ende-zu-Ende gegen einen realen oder Stub-Provider verifiziert.
|
||||||
|
|
||||||
|
## Story-Bezug
|
||||||
|
US_000025
|
||||||
|
|
||||||
|
## Beschreibung
|
||||||
|
- Teste `/login/oidc/start` und `/login/oidc/callback` mit einem Provider (oder Stub).
|
||||||
|
- Validierung: State, Token-Exchange, Claims, Allowlist, Session-Cookie.
|
||||||
|
- Dokumentiere Abweichungen bei nicht verfuegbaren IdP-Funktionen.
|
||||||
|
- Testplan liegt in `docs/oidc-validation.md`.
|
||||||
|
|
||||||
|
## Definition of Done (DoD)
|
||||||
|
- Testschritte sind dokumentiert.
|
||||||
|
- Ergebnisse inklusive Fehlerbilder sind festgehalten.
|
||||||
20
project-management/requirements/tasks/TASK_000026.md
Normal file
20
project-management/requirements/tasks/TASK_000026.md
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
ID: TASK_000026 | Version: 0.1.0 | Status: Blocked
|
||||||
|
By: Codex (GPT-5)
|
||||||
|
|
||||||
|
# TASK_000026: OIDC runbook update
|
||||||
|
|
||||||
|
## Outcome
|
||||||
|
Runbook beschreibt OIDC-Setup, Redirect-URIs und Fallbacks fuer einen nicht vollstaendigen IdP.
|
||||||
|
|
||||||
|
## Story-Bezug
|
||||||
|
US_000025
|
||||||
|
|
||||||
|
## Beschreibung
|
||||||
|
- Ergaenze README mit klaren OIDC-ENV-Beispielen.
|
||||||
|
- Beschreibe exakte Redirect-URI und Host/Port-Wechsel.
|
||||||
|
- Fuege Fallbacks hinzu (PAM-Login, Hinweise zu fehlenden IdP-Features).
|
||||||
|
- Verweise auf `docs/oidc-validation.md` fuer die Testschritte.
|
||||||
|
|
||||||
|
## Definition of Done (DoD)
|
||||||
|
- README enthaelt konkrete OIDC-Setup-Schritte.
|
||||||
|
- Fallbacks sind fuer Operatoren nachvollziehbar.
|
||||||
Reference in New Issue
Block a user