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
@@ -7,6 +7,7 @@ type: Opaque
|
||||
stringData:
|
||||
grafana-client-secret: ENC[AES256_GCM,data:J6grYrkJVubGNS+z1WrcUuvjKu2Z/fETWNXsKkjQnk/RlitaMjDqkazglKgy8PkEymNLNuVao48gksnuNLQ6aA==,iv:9sw5IdW+7GuwsRjn4NSi2JVZh9mTE2hiWJQYQS7w2bA=,tag:2hOUFeFqcQJsPeC8e6U4RA==,type:str]
|
||||
oauth2-proxy-client-secret: ENC[AES256_GCM,data:hE9mN6A6uTL3GpqOCGb3T0qVR9bcY4wcqNlgcrs9+uezSeci/Vs9uiXeFzfD57klHe8nEqxF1bCftD0GVFadhQ==,iv:rP2CbbGG4LWmGyV0adrlsmn2GtQLH8hSlqbioOAD824=,tag:p28vfuSZxYMs7XnAjslBWg==,type:str]
|
||||
gitea-client-secret: ENC[AES256_GCM,data:ObU5sWSq2xEZKgqwccFo/kzs/nfhOVJ0NceBlgxgy6a6iOWeUN079iQpJ0ADoJyjsoQk1IQediFD06rwVSvNIQ==,iv:Bc4L8MngA6YKNRQRwEQojnUo5ixdS2t1AocvGMwE0oQ=,tag:YsozaKtL6CR68sPmGiL5wA==,type:str]
|
||||
sops:
|
||||
kms: []
|
||||
gcp_kms: []
|
||||
@@ -22,8 +23,8 @@ sops:
|
||||
YXJGUUJiTnFndFhDZDU1bmxXTENCbmsKuhb3RQ3LZmuTPAXEck8b6iuZ5vxLb9fV
|
||||
+fQELmU4kOTbZrPSxy+kpttLG5Cy5d6X2ieZXBf6opzpLTUhlBOkHA==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
lastmodified: "2026-08-25T03:06:30Z"
|
||||
mac: ENC[AES256_GCM,data:wJ6Mf7yFXnnnuo/IhfGdDYcgNUzzNv2bFGN6wPwmVRr1To4rt3DGb1JZO19OxvloWYrGAyJaeXzC5NCLGDZOzS99TBmMy1TIeEYUqd6zEmFofEla0mZyLFGLsU/JUIVL3d1l4ECPmcHpUnEjJf4o4kE8BxkWAyHSMWktB69a1cc=,iv:7E7T7cFpAcT42ryTfqbDUoWr0d23a9rCI3n//LgBFS8=,tag:zVXOh/hjQNousg85TZw/Zg==,type:str]
|
||||
lastmodified: "2026-08-27T21:58:13Z"
|
||||
mac: ENC[AES256_GCM,data:+QNCSmJMoAAUibsQZ7by/Neno6VfxLoxCNUoRwHAW+T3SDrgQI7hkrVcWr39bpCne7AAGnlyVS7Zd4m3lk/ZGs90LbSx5xdFyGPQ7vpI774aaIMQJe+84pBPLGw1NH8bVmDOS99P0rlIG0lGBixl4UqrQ3BWhgnd63tAvnKnCBU=,iv:K4Wi+LvKhszj3bnRBuheHdbS2UwhlN6hBJP/H3nEo9M=,tag:P4+14EaB88QQNzDvCUlnJA==,type:str]
|
||||
pgp: []
|
||||
encrypted_regex: ^(data|stringData)$
|
||||
version: 3.9.4
|
||||
@@ -23,6 +23,10 @@ spec:
|
||||
secret:
|
||||
name: keycloak-realm-clients
|
||||
key: oauth2-proxy-client-secret
|
||||
GITEA_CLIENT_SECRET:
|
||||
secret:
|
||||
name: keycloak-realm-clients
|
||||
key: gitea-client-secret
|
||||
realm:
|
||||
realm: homelab
|
||||
enabled: true
|
||||
@@ -145,6 +149,39 @@ spec:
|
||||
"access.token.claim": "true"
|
||||
"userinfo.token.claim": "true"
|
||||
"claim.name": groups
|
||||
# gitea — native OIDC SSO for the bootstrapping-critical git host (catalog/gitea.yaml auth.mode
|
||||
# oidc; docs/13 SSO row, docs/15 "Native OIDC"; R-IDP-02). Confidential auth-code client modeled on
|
||||
# grafana. Redirect URI is Gitea's OAuth2 callback for the auth source named `keycloak`
|
||||
# (/user/oauth2/<source>/callback). The `groups` claim drives authorization: Gitea maps the
|
||||
# platform-admins group -> Gitea admin (auth source --admin-group; R-IDP-06). The LOCAL Gitea admin
|
||||
# remains the break-glass path (docs/15 availability caveat). Secret injected from the SOPS
|
||||
# keycloak-realm-clients Secret via the GITEA_CLIENT_SECRET placeholder (R-IDP-04).
|
||||
- clientId: gitea
|
||||
name: Gitea
|
||||
protocol: openid-connect
|
||||
enabled: true
|
||||
publicClient: false
|
||||
standardFlowEnabled: true
|
||||
directAccessGrantsEnabled: false
|
||||
secret: "$(env:GITEA_CLIENT_SECRET)"
|
||||
redirectUris:
|
||||
- https://gitea.home.keithjackson.dev/user/oauth2/keycloak/callback
|
||||
webOrigins:
|
||||
- https://gitea.home.keithjackson.dev
|
||||
defaultClientScopes:
|
||||
- email
|
||||
- profile
|
||||
- roles
|
||||
protocolMappers:
|
||||
- name: groups
|
||||
protocol: openid-connect
|
||||
protocolMapper: oidc-group-membership-mapper
|
||||
config:
|
||||
"full.path": "false"
|
||||
"id.token.claim": "true"
|
||||
"access.token.claim": "true"
|
||||
"userinfo.token.claim": "true"
|
||||
"claim.name": groups
|
||||
# --- MFA for platform-admins (R-IDP-04): a browser flow whose OTP step is CONDITIONAL on the
|
||||
# platform-admin role. Non-admins log in without OTP; platform-admins must enroll/present TOTP.
|
||||
# Set as the realm browserFlow below. VERIFY at bring-up (see runbook): a malformed flow fails
|
||||
|
||||
Reference in new issue
Block a user