From 520c7ea2c04ced056c74e655bef307f4cfb08f96 Mon Sep 17 00:00:00 2001 From: tegwick Date: Fri, 19 Jun 2026 21:13:08 +0200 Subject: [PATCH] fix(openbao-ui): serve standalone KeyCape login at /ui/vault/auth Ember's auth route bounces between ?with=netkingdom/ and ?with=token when OIDC mounts are hidden from the unauthenticated listing. Bypass Ember on the bare auth path with a static login page that calls auth_url directly; OIDC callbacks still proxy to the OpenBao UI. --- docs/openbao.md | 7 +- helm/openbao-ui-overlay/README.md | 5 +- helm/openbao-ui-overlay/login.css | 85 ++++++++++++++++++++++++ helm/openbao-ui-overlay/login.html | 22 +++++++ helm/openbao-ui-overlay/login.js | 86 +++++++++++++++++++++++++ helm/openbao-ui-overlay/nginx.conf | 7 ++ scripts/openbao-ui-overlay-apply.sh | 5 +- scripts/openbao-verify-login-overlay.sh | 13 ++++ 8 files changed, 225 insertions(+), 5 deletions(-) create mode 100644 helm/openbao-ui-overlay/login.css create mode 100644 helm/openbao-ui-overlay/login.html create mode 100644 helm/openbao-ui-overlay/login.js diff --git a/docs/openbao.md b/docs/openbao.md index 162b976..82814fd 100644 --- a/docs/openbao.md +++ b/docs/openbao.md @@ -338,8 +338,11 @@ OPENBAO_TOKEN_FILE=~/.local/openbao/platform-admin.token \ scripts/openbao-tune-auth-listing.sh ``` -The login overlay also redirects to `?with=netkingdom/` and starts KeyCape OIDC -directly when the operator clicks **Sign in with KeyCape**. +The gateway serves a standalone KeyCape login page at `/ui/vault/auth` so Ember +never handles the bare auth route (avoids `?with=token` / `?with=netkingdom/` +bounce when OIDC mounts are hidden from the unauthenticated listing). Clicking +**Sign in with KeyCape** calls `auth_url` and redirects to KeyCape directly. +OIDC callbacks under `/ui/vault/auth//oidc/` still proxy to the OpenBao UI. The OpenBao UI redirects the browser to KeyCape at `kc.coulomb.social`, then returns to: diff --git a/helm/openbao-ui-overlay/README.md b/helm/openbao-ui-overlay/README.md index 1c26c47..44e6e19 100644 --- a/helm/openbao-ui-overlay/README.md +++ b/helm/openbao-ui-overlay/README.md @@ -31,8 +31,9 @@ behaviour. | `VERSION` | OpenBao image tag this overlay targets (`openbao-values.yaml`) | | `presets.json` | Hidden login defaults (`netkingdom`, `platform-admin`, …) | | `overlay.css` | Hide raw OpenBao login fields | -| `overlay.js` | Apply presets, branding, direct KeyCape OIDC sign-in | -| `nginx.conf` | Gateway proxy + HTML injection | +| `overlay.js` | Apply presets, branding on post-login Ember pages | +| `login.html` / `login.js` / `login.css` | Standalone KeyCape login at `/ui/vault/auth` | +| `nginx.conf` | Gateway proxy + standalone auth page + HTML injection | | `patches//manifest.sha256` | Upstream UI fingerprints for drift detection | ## Deploy diff --git a/helm/openbao-ui-overlay/login.css b/helm/openbao-ui-overlay/login.css new file mode 100644 index 0000000..d9880ca --- /dev/null +++ b/helm/openbao-ui-overlay/login.css @@ -0,0 +1,85 @@ +:root { + color-scheme: light; + --bg: #f0f2f5; + --card: #ffffff; + --text: #1f2a37; + --muted: #5b6b7c; + --border: #d9dee3; + --accent: #1565c0; + --accent-hover: #0d47a1; +} + +* { + box-sizing: border-box; +} + +body { + margin: 0; + min-height: 100vh; + font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; + background: var(--bg); + color: var(--text); + display: flex; + align-items: center; + justify-content: center; + padding: 1.5rem; +} + +.login-card { + width: min(100%, 26rem); + background: var(--card); + border: 1px solid var(--border); + border-radius: 8px; + box-shadow: 0 8px 24px rgba(16, 24, 40, 0.08); + padding: 2rem 1.75rem 1.75rem; +} + +.login-card h1 { + margin: 0 0 0.75rem; + font-size: 1.45rem; + font-weight: 600; + line-height: 1.3; +} + +.login-card p { + margin: 0 0 1.5rem; + color: var(--muted); + font-size: 0.9rem; + line-height: 1.45; +} + +.login-button { + width: 100%; + border: 0; + border-radius: 6px; + padding: 0.8rem 1rem; + font-size: 0.95rem; + font-weight: 600; + color: #fff; + background: var(--accent); + cursor: pointer; +} + +.login-button:hover:not(:disabled) { + background: var(--accent-hover); +} + +.login-button:disabled { + opacity: 0.7; + cursor: wait; +} + +.login-error { + display: none; + margin-top: 1rem; + padding: 0.75rem 0.9rem; + border-radius: 6px; + background: #fdecea; + color: #8a1c13; + font-size: 0.85rem; + line-height: 1.4; +} + +.login-error.is-visible { + display: block; +} \ No newline at end of file diff --git a/helm/openbao-ui-overlay/login.html b/helm/openbao-ui-overlay/login.html new file mode 100644 index 0000000..54a4a09 --- /dev/null +++ b/helm/openbao-ui-overlay/login.html @@ -0,0 +1,22 @@ + + + + + + Sign in with KeyCape + + + + +
+

Sign in with KeyCape

+

+ Platform operators authenticate through KeyCape at kc.coulomb.social. +

+ + +
+ + \ No newline at end of file diff --git a/helm/openbao-ui-overlay/login.js b/helm/openbao-ui-overlay/login.js new file mode 100644 index 0000000..d629ad1 --- /dev/null +++ b/helm/openbao-ui-overlay/login.js @@ -0,0 +1,86 @@ +(function () { + "use strict"; + + const PRESETS_URL = "/ui/platform-overlay/presets.json"; + const DEFAULT_PRESETS = { + mount: "netkingdom", + role: "platform-admin", + title: "Sign in with KeyCape", + signInLabel: "Sign in with KeyCape", + banner: + "Platform operators authenticate through KeyCape at kc.coulomb.social.", + }; + + async function loadPresets() { + try { + const response = await fetch(PRESETS_URL, { cache: "no-store" }); + if (!response.ok) return { ...DEFAULT_PRESETS }; + return { ...DEFAULT_PRESETS, ...(await response.json()) }; + } catch (_error) { + return { ...DEFAULT_PRESETS }; + } + } + + async function redirectToKeyCape(presets) { + const mount = presets.mount || "netkingdom"; + const role = presets.role || "platform-admin"; + const redirectUri = `${window.location.origin}/ui/vault/auth/${mount}/oidc/callback`; + + const response = await fetch(`/v1/auth/${mount}/oidc/auth_url`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + role, + redirect_uri: redirectUri, + }), + }); + + if (!response.ok) { + throw new Error(`OIDC auth_url request failed (${response.status})`); + } + + const payload = await response.json(); + const authUrl = payload?.data?.auth_url; + if (!authUrl) { + throw new Error("OIDC auth_url missing from OpenBao response"); + } + + window.location.assign(authUrl); + } + + function showError(message) { + const error = document.getElementById("login-error"); + if (!error) return; + error.textContent = message; + error.classList.add("is-visible"); + } + + async function init() { + const presets = await loadPresets(); + const title = document.getElementById("login-title"); + const banner = document.getElementById("login-banner"); + const button = document.getElementById("login-submit"); + + if (title) title.textContent = presets.title; + if (banner) banner.textContent = presets.banner; + if (button) button.textContent = presets.signInLabel; + + if (!button) return; + + button.addEventListener("click", async () => { + button.disabled = true; + try { + await redirectToKeyCape(presets); + } catch (error) { + button.disabled = false; + showError( + error instanceof Error + ? error.message + : "Sign-in failed. Contact your administrator." + ); + } + }); + } + + init(); +})(); \ No newline at end of file diff --git a/helm/openbao-ui-overlay/nginx.conf b/helm/openbao-ui-overlay/nginx.conf index 8facdf3..c89a4d8 100644 --- a/helm/openbao-ui-overlay/nginx.conf +++ b/helm/openbao-ui-overlay/nginx.conf @@ -26,6 +26,13 @@ http { add_header Cache-Control "public, max-age=300"; } + # Standalone KeyCape login page — bypasses Ember auth route and ?with= bounce. + location = /ui/vault/auth { + alias /etc/nginx/overlay/login.html; + default_type text/html; + add_header Cache-Control "no-store"; + } + # Static UI bundles and API calls bypass HTML injection and stay compressed. location ~ ^/(v1|ui/assets|ui/engines-dist|ui/favicon\.svg) { proxy_pass http://openbao_upstream; diff --git a/scripts/openbao-ui-overlay-apply.sh b/scripts/openbao-ui-overlay-apply.sh index 93e2c25..d69385a 100755 --- a/scripts/openbao-ui-overlay-apply.sh +++ b/scripts/openbao-ui-overlay-apply.sh @@ -27,7 +27,7 @@ if [ "${1:-}" = "-h" ] || [ "${1:-}" = "--help" ]; then exit 0 fi -for required in overlay.css overlay.js presets.json nginx.conf VERSION; do +for required in overlay.css overlay.js login.css login.html login.js presets.json nginx.conf VERSION; do if [ ! -f "$OVERLAY_DIR/$required" ]; then echo "missing overlay asset: $OVERLAY_DIR/$required" >&2 exit 1 @@ -47,6 +47,9 @@ $KUBECTL create configmap openbao-ui-overlay \ --namespace "$OPENBAO_NAMESPACE" \ --from-file="$OVERLAY_DIR/overlay.css" \ --from-file="$OVERLAY_DIR/overlay.js" \ + --from-file="$OVERLAY_DIR/login.css" \ + --from-file="$OVERLAY_DIR/login.html" \ + --from-file="$OVERLAY_DIR/login.js" \ --from-file="$OVERLAY_DIR/presets.json" \ --from-file="$OVERLAY_DIR/VERSION" \ --dry-run=client -o yaml | $KUBECTL apply -f - diff --git a/scripts/openbao-verify-login-overlay.sh b/scripts/openbao-verify-login-overlay.sh index 0170634..4ce04b7 100755 --- a/scripts/openbao-verify-login-overlay.sh +++ b/scripts/openbao-verify-login-overlay.sh @@ -52,6 +52,19 @@ require_pattern() { ok "$label" } +step "Standalone login page" +auth_html="$(curl -fsS "$BASE_URL/ui/vault/auth")" +require_pattern \ + "auth page serves standalone KeyCape login" \ + "$auth_html" \ + 'id="login-submit"|Sign in with KeyCape' + +if grep -Eq 'vault-|engines-dist' <<<"$auth_html"; then + err "auth page still serves Ember shell (expected standalone login.html)" + exit 1 +fi +ok "auth page is standalone login.html (no Ember shell)" + step "Overlay asset endpoints" index_html="$(curl -fsS "$BASE_URL/ui/")" overlay_js="$(curl -fsS "$BASE_URL/ui/platform-overlay/overlay.js")"