feat(gitea): wire native Keycloak OIDC SSO (break-glass admin retained)
Wires the bootstrapping-critical Gitea git host to the Keycloak `homelab` realm as a native confidential OIDC client, moving it off the local-admin-only login while keeping the local admin as the break-glass path (docs/15 availability caveat; R-IDP-02/06). Realm (source of truth): add the `gitea` confidential client to platform/keycloak/keycloak-realm-import.yaml (modeled on grafana) + its GITEA_CLIENT_SECRET placeholder sourced from the keycloak-realm-clients Secret. NOTE: a KeycloakRealmImport no-ops on an already-existing realm, so the live client was ALSO created via kcadm (id 70a6ca60-317a-42ce-8331-a60e380838ee); the YAML keeps git authoritative for a rebuild. Secrets (SOPS/age, never plaintext): add gitea-client-secret to platform/keycloak/keycloak-realm-clients.sops.yaml (keycloak ns) and mirror the same value as oidc-client-secret into apps/wave-0/gitea-bootstrap-secret.sops.yaml (gitea ns) so the app can read it in-namespace. Both == the live client secret. App config (catalog source + re-render): catalog/gitea.yaml gains the GITEA_OIDC_CLIENT_SECRET env (from the mirrored Secret), [oauth2_client] auto-registration/account-linking app.ini env, and an idempotent postStart step that registers the "keycloak" OAuth2 login source (auto-discovery; groups claim; platform-admins -> Gitea admin). apps/wave-0/gitea.yaml re-rendered via `render.py --no-image-automation`. Verified: realm import Done=True/HasErrors=False and platform-config Ready=True/Healthy=True (unchanged); edited realm YAML passes server dry-run; static gate (lint/l0/l1) green. Browser SSO login still needs a human check. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
df947b33d0
commit
87388712f0
5 files changed
+118
-21
No files matched your search
+63
-16
@@ -41,6 +41,23 @@ env:
|
||||
secretKeyRef:
|
||||
name: gitea-bootstrap
|
||||
key: admin-password
|
||||
# --- Keycloak native OIDC SSO (auth.mode oidc; docs/15 "Native OIDC"; R-IDP-02/06). The confidential
|
||||
# `gitea` realm client's secret is mirrored into this namespace's gitea-bootstrap Secret (the same
|
||||
# value SOPS-held in keycloak-realm-clients); the postStart hook registers it as the "keycloak"
|
||||
# OAuth2 login source. The LOCAL admin above stays as the break-glass path (docs/15). ---
|
||||
- name: GITEA_OIDC_CLIENT_SECRET
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: gitea-bootstrap
|
||||
key: oidc-client-secret
|
||||
# app.ini [oauth2_client]: auto-provision a Gitea account on first Keycloak login and link it to an
|
||||
# existing local account by verified email, so SSO users don't need a pre-created local user.
|
||||
- name: GITEA__oauth2_client__ENABLE_AUTO_REGISTRATION
|
||||
value: "true"
|
||||
- name: GITEA__oauth2_client__ACCOUNT_LINKING
|
||||
value: auto
|
||||
- name: GITEA__oauth2_client__USERNAME
|
||||
value: preferred_username
|
||||
lifecycle:
|
||||
postStart:
|
||||
exec:
|
||||
@@ -61,19 +78,47 @@ lifecycle:
|
||||
done
|
||||
if su-exec git gitea admin user list --admin 2>/dev/null | awk 'NR>1{print $2}' | grep -qx "$GITEA_ADMIN_USERNAME"; then
|
||||
echo "[gitea-bootstrap] admin '$GITEA_ADMIN_USERNAME' already present"
|
||||
exit 0
|
||||
else
|
||||
n=0
|
||||
until su-exec git gitea admin user create \
|
||||
--username "$GITEA_ADMIN_USERNAME" \
|
||||
--password "$GITEA_ADMIN_PASSWORD" \
|
||||
--email "[email protected]" \
|
||||
--admin --must-change-password=false; do
|
||||
n=$((n + 1))
|
||||
[ "$n" -ge 10 ] && { echo "[gitea-bootstrap] admin create failed after retries" >&2; break; }
|
||||
sleep 3
|
||||
done
|
||||
echo "[gitea-bootstrap] created break-glass admin '$GITEA_ADMIN_USERNAME'"
|
||||
fi
|
||||
# --- Keycloak OIDC login source (idempotent add-or-update; R-IDP-02/06) ---
|
||||
# Registers the confidential `gitea` client as the OAuth2 login source "keycloak" against the
|
||||
# homelab realm's discovery doc. The `groups` claim maps the platform-admins group to Gitea
|
||||
# admin (--admin-group). Auto-discovery pulls auth/token/userinfo/jwks endpoints. Runs on every
|
||||
# start: updates the existing source if present, else creates it. Missing secret => skip (the
|
||||
# local break-glass admin above still works), so an unfilled secret never crashes the pod.
|
||||
if [ -n "${GITEA_OIDC_CLIENT_SECRET:-}" ]; then
|
||||
oidc_id=$(su-exec git gitea admin auth list 2>/dev/null | awk '$2=="keycloak"{print $1; exit}')
|
||||
if [ -n "$oidc_id" ]; then
|
||||
su-exec git gitea admin auth update-oauth --id "$oidc_id" \
|
||||
--name keycloak --provider openidConnect --key gitea \
|
||||
--secret "$GITEA_OIDC_CLIENT_SECRET" \
|
||||
--auto-discover-url https://keycloak.home.keithjackson.dev/realms/homelab/.well-known/openid-configuration \
|
||||
--scopes "openid email profile" --group-claim-name groups --admin-group platform-admins \
|
||||
&& echo "[gitea-oidc] updated login source 'keycloak' (id $oidc_id)" \
|
||||
|| echo "[gitea-oidc] update-oauth failed" >&2
|
||||
else
|
||||
su-exec git gitea admin auth add-oauth \
|
||||
--name keycloak --provider openidConnect --key gitea \
|
||||
--secret "$GITEA_OIDC_CLIENT_SECRET" \
|
||||
--auto-discover-url https://keycloak.home.keithjackson.dev/realms/homelab/.well-known/openid-configuration \
|
||||
--scopes "openid email profile" --group-claim-name groups --admin-group platform-admins \
|
||||
&& echo "[gitea-oidc] added login source 'keycloak'" \
|
||||
|| echo "[gitea-oidc] add-oauth failed" >&2
|
||||
fi
|
||||
else
|
||||
echo "[gitea-oidc] GITEA_OIDC_CLIENT_SECRET unset; skipping OIDC source (break-glass admin only)"
|
||||
fi
|
||||
n=0
|
||||
until su-exec git gitea admin user create \
|
||||
--username "$GITEA_ADMIN_USERNAME" \
|
||||
--password "$GITEA_ADMIN_PASSWORD" \
|
||||
--email "[email protected]" \
|
||||
--admin --must-change-password=false; do
|
||||
n=$((n + 1))
|
||||
[ "$n" -ge 10 ] && { echo "[gitea-bootstrap] admin create failed after retries" >&2; exit 0; }
|
||||
sleep 3
|
||||
done
|
||||
echo "[gitea-bootstrap] created break-glass admin '$GITEA_ADMIN_USERNAME'"
|
||||
exposure: public # edge-forwarded git host at gitea.home.keithjackson.dev (docs/09; R-DNS/R-NET)
|
||||
egress: internet # normal WAN egress (docs/03; R-NET-05)
|
||||
storage:
|
||||
@@ -90,10 +135,12 @@ auth:
|
||||
mode: oidc # native Keycloak OIDC client (docs/13 SSO row, docs/15 "Native OIDC" set; R-IDP-02)
|
||||
groups:
|
||||
- platform-admins # access scoped to an authorized group (R-IDP-06)
|
||||
# NOTE (OIDC follow-up + break-glass): Gitea keeps a LOCAL admin as the break-glass path so an IdP
|
||||
# outage never locks the operator out of recovery tools (docs/15 "Availability caveat"). At initial
|
||||
# deploy Gitea comes up on that local admin; the Keycloak OIDC client (+ SOPS client secret) is wired
|
||||
# once Keycloak is live. This declaration records the target integration; it does not embed a secret.
|
||||
# WIRED (native OIDC): the Keycloak `gitea` confidential client (platform/keycloak/keycloak-realm-
|
||||
# import.yaml + live kcadm) is registered as Gitea's "keycloak" OAuth2 login source by the postStart
|
||||
# hook, using the client secret mirrored into the gitea-bootstrap Secret (oidc-client-secret; same
|
||||
# value SOPS-held in keycloak-realm-clients). platform-admins (groups claim) -> Gitea admin. Gitea
|
||||
# KEEPS a LOCAL admin as the break-glass path so an IdP outage never locks the operator out of
|
||||
# recovery tools (docs/15 "Availability caveat"). No secret is embedded here.
|
||||
update:
|
||||
channel: stable # R-UPD-01
|
||||
range: ">=1.22.0 <2.0.0" # stay on the 1.x stable line
|
||||
|
||||
Reference in new issue
Block a user