generated from coulomb/repo-seed
Some checks failed
Build and Publish Container Image / build-and-push (push) Has been cancelled
195 lines
6.9 KiB
Markdown
195 lines
6.9 KiB
Markdown
---
|
|
id: KEY-WP-0003
|
|
type: workplan
|
|
title: "Bootstrap Console OIDC Login and MFA Verification"
|
|
domain: netkingdom
|
|
repo: key-cape
|
|
status: finished
|
|
owner: codex
|
|
topic_slug: netkingdom
|
|
created: "2026-05-24"
|
|
updated: "2026-05-24"
|
|
---
|
|
|
|
# KEY-WP-0003 - Bootstrap Console OIDC Login and MFA Verification
|
|
|
|
## Problem
|
|
|
|
The NetKingdom security bootstrap console now acts as a local OIDC client
|
|
callback so the operator can verify the dedicated platform-root login before
|
|
approving custody mode. The current live KeyCape deployment rejects that flow
|
|
with:
|
|
|
|
```json
|
|
{
|
|
"error": "invalid_profile_usage",
|
|
"description": "redirect_uri does not match any registered URI",
|
|
"feature": "redirect_uri"
|
|
}
|
|
```
|
|
|
|
That error is correct profile enforcement: KeyCape only accepts exact
|
|
registered redirect URIs. The live `demo-app` registration has not yet been
|
|
updated to allow the local bootstrap console callback:
|
|
|
|
- `http://127.0.0.1:8876/oidc/callback`
|
|
- `http://localhost:8876/oidc/callback`
|
|
|
|
After that is fixed, there is a second usability/security gap. KeyCape checks
|
|
privacyIDEA MFA after the Authelia callback, but the browser flow currently
|
|
expects an `mfa_token` query parameter instead of presenting a proper OTP
|
|
challenge page to the human operator.
|
|
|
|
## Goal
|
|
|
|
Make the bootstrap console's "Start demo OIDC login" button a real
|
|
end-to-end verification path for the current lightweight IAM stack:
|
|
|
|
1. KeyCape accepts the bootstrap console callback URI by exact registration.
|
|
2. The browser leaves KeyCape for the public Authelia login URL.
|
|
3. After password login, KeyCape presents a minimal MFA challenge when
|
|
privacyIDEA requires one.
|
|
4. KeyCape issues an OIDC authorization code to the bootstrap console callback.
|
|
5. The console can exchange the code and let the operator mark
|
|
`OIDC login verified` without exposing tokens or secrets.
|
|
|
|
This keeps KeyCape's security posture intact: no wildcard redirect URIs, no
|
|
dynamic client registration, no token display, and no storage of OTP material.
|
|
|
|
## Design Notes
|
|
|
|
- Prefer a dedicated public client named `netkingdom-bootstrap-console` for
|
|
long-lived clarity. Reusing `demo-app` is acceptable for the immediate
|
|
unblock only if the deployment/runbook clearly labels it as a bootstrap test
|
|
client.
|
|
- The bootstrap callback is local-only and operator-attended. It must be an
|
|
exact URI in config, not a wildcard or dynamic registration exception.
|
|
- Browser-facing Authelia redirects must use the public Authelia base URL
|
|
(`https://auth.coulomb.social`) so the human login page opens correctly.
|
|
- KeyCape may still need an internal service URL for back-channel token
|
|
exchange. If so, split the current single Authelia URL into browser-facing
|
|
authorize URL and internal token URL instead of making the browser use an
|
|
in-cluster hostname.
|
|
- The MFA prompt should collect only a one-time code, post it back to KeyCape,
|
|
validate with privacyIDEA, and then continue the normal OIDC code flow.
|
|
- This work unblocks the NetKingdom custody gate in
|
|
`NET-WP-0015-platform-root-custody-and-openbao-identity-bootstrap`.
|
|
|
|
## Implementation Notes
|
|
|
|
**2026-05-24:** Implemented in source:
|
|
|
|
- added `netkingdom-bootstrap-console` as a public OIDC client in the sample
|
|
KeyCape config, while keeping the local callback registered on `demo-app`
|
|
for compatibility,
|
|
- split Authelia browser redirects from server-side token exchange via
|
|
`browserBaseURL` and `tokenBaseURL`,
|
|
- added a browser MFA challenge page at `POST /authorize/callback` that
|
|
validates the one-time code with privacyIDEA before issuing the downstream
|
|
OIDC authorization code,
|
|
- updated NetKingdom's `keycape-config` generation template and bootstrap
|
|
console to use the dedicated client,
|
|
- added regression tests for callback registration, split Authelia URLs, MFA
|
|
challenge rendering, valid OTP continuation, and invalid OTP failure.
|
|
|
|
Live use still requires deployment: build/publish the updated KeyCape image,
|
|
refresh the live `keycape-config` Secret through the custodian age-key unlock
|
|
ceremony, and restart the KeyCape deployment.
|
|
|
|
---
|
|
|
|
## T01 - Register the bootstrap console callback client
|
|
|
|
```task
|
|
id: KEY-WP-0003-T01
|
|
status: done
|
|
priority: high
|
|
```
|
|
|
|
Add a KeyCape client registration for the bootstrap console. Either create a
|
|
dedicated `netkingdom-bootstrap-console` public client or update `demo-app`
|
|
temporarily with these exact redirect URIs:
|
|
|
|
- `http://127.0.0.1:8876/oidc/callback`
|
|
- `http://localhost:8876/oidc/callback`
|
|
|
|
Update the sample config, tests, and deployment/runbook references so the
|
|
registered client is reproducible and not just a live-cluster patch.
|
|
|
|
Gate: an authorize request using the local callback no longer returns
|
|
`invalid_profile_usage` for `redirect_uri`.
|
|
|
|
## T02 - Separate browser-facing and internal Authelia URLs if needed
|
|
|
|
```task
|
|
id: KEY-WP-0003-T02
|
|
status: done
|
|
priority: high
|
|
```
|
|
|
|
Confirm whether the current `authelia.baseURL` is safe to use for both browser
|
|
redirects and server-side token exchange. If not, add explicit configuration
|
|
for the browser authorize base URL and internal token/userinfo base URL.
|
|
|
|
Gate: the first browser redirect leaves `https://kc.coulomb.social` for
|
|
`https://auth.coulomb.social/...`; server-side token exchange still works from
|
|
inside the deployment.
|
|
|
|
## T03 - Add a browser MFA challenge step
|
|
|
|
```task
|
|
id: KEY-WP-0003-T03
|
|
status: done
|
|
priority: high
|
|
```
|
|
|
|
When `CheckMFARequired` returns true after the Authelia callback, render a
|
|
minimal KeyCape MFA challenge page instead of requiring `mfa_token` in the
|
|
callback query string. The page should:
|
|
|
|
- show the authenticated username and client display name,
|
|
- collect only the OTP code,
|
|
- preserve the pending OIDC state server-side,
|
|
- validate with privacyIDEA,
|
|
- continue to issue the normal authorization code on success,
|
|
- fail closed with the existing telemetry on invalid MFA.
|
|
|
|
Gate: a user enrolled in privacyIDEA can complete password + OTP in the
|
|
browser and is returned to the registered downstream callback.
|
|
|
|
## T04 - Add end-to-end profile tests for the bootstrap login path
|
|
|
|
```task
|
|
id: KEY-WP-0003-T04
|
|
status: done
|
|
priority: medium
|
|
```
|
|
|
|
Add tests that cover:
|
|
|
|
- local bootstrap callback registration,
|
|
- rejection of unregistered callbacks remains intact,
|
|
- Authelia browser redirect uses the expected public URL,
|
|
- MFA-required login presents a challenge instead of immediate failure,
|
|
- invalid OTP fails closed,
|
|
- valid OTP produces an authorization code bound to the original PKCE session.
|
|
|
|
Gate: `make test` passes and the negative redirect URI tests remain green.
|
|
|
|
## T05 - Document the live rollout ceremony
|
|
|
|
```task
|
|
id: KEY-WP-0003-T05
|
|
status: done
|
|
priority: medium
|
|
```
|
|
|
|
Document the deployment path for updating live KeyCape config without
|
|
regenerating unrelated secrets. The runbook must fit the NetKingdom custodian
|
|
age-key model: decrypt or unlock only during an attended ceremony, apply the
|
|
updated client registration/config, restart KeyCape, and remove plaintext
|
|
secret material afterward.
|
|
|
|
Gate: an operator can update the live `keycape-config` Secret and verify the
|
|
bootstrap console OIDC login without printing or committing secrets.
|