feat(nextcloud,semaphore): wire native Keycloak OIDC SSO (break-glass admins retained)
Extends the SSO work to the two remaining `auth: oidc` apps still on local-admin
logins, each as a native confidential client of the Keycloak `homelab` realm with
its local admin kept as break-glass (docs/15 availability caveat; R-IDP-02/06).
Realm (source of truth): add the `nextcloud` and `semaphore` confidential clients
to platform/keycloak/keycloak-realm-import.yaml (modeled on grafana/gitea) + their
NEXTCLOUD_CLIENT_SECRET / SEMAPHORE_CLIENT_SECRET placeholders. As before, a
KeycloakRealmImport no-ops on an already-existing realm, so both live clients were
ALSO created via kcadm (nextcloud e95049ed-ca10-4085-97d9-4bbef495fe61, semaphore
1835bfe9-b125-4026-8f69-33557519cf4a); the YAML keeps git authoritative.
Secrets (SOPS/age): add nextcloud-client-secret + semaphore-client-secret to
keycloak-realm-clients.sops.yaml (keycloak ns). Mirror the nextcloud secret as
oidc-client-secret into nextcloud-bootstrap (nextcloud ns). For semaphore, the
whole SEMAPHORE_OIDC_PROVIDERS JSON (which embeds the client secret) is held
encrypted as oidc-providers-json in semaphore-bootstrap (semaphore ns). All values
verified == the live clients; existing keys untouched; no plaintext in git.
App config (catalog source + re-render):
- nextcloud: add NEXTCLOUD_OIDC_CLIENT_SECRET env + an idempotent postStart hook
that installs/enables user_oidc and runs `occ user_oidc:provider keycloak`
(create-or-update; secret via --clientsecret-env so it never hits argv; occ as
www-data; group provisioning on the `groups` claim). Syntax verified against the
running user_oidc 8.11.0 on Nextcloud 31.0.14; su-to-www-data env passthrough
verified. Missing-secret/not-installed guards prevent crashloops.
- semaphore: add SEMAPHORE_OIDC_PROVIDERS env sourced from the encrypted
oidc-providers-json (provider_url=realm issuer/auto-discovery, redirect
/api/auth/oidc/keycloak/redirect). Mechanism verified against semaphore v2.19.10
source (util ConfigType oidc_providers env tag + OidcProvider struct + router
redirect path); no kubelet service-link env collision.
apps/wave-1/{nextcloud,semaphore}.yaml re-rendered via
`render.py --no-image-automation` (diffs are env/lifecycle-only).
Verified: realm import Done=True/HasErrors=False and platform-config
Ready=True/Healthy=True (unchanged); edited realm YAML passes server dry-run; live
homelab clients now include gitea/nextcloud/semaphore; static gate (lint/l0/l1)
green. Browser SSO login for each still needs a human check.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
87388712f0
commit
f163026954
8 files changed
+179
-22
No files matched your search
@@ -14,6 +14,7 @@ stringData:
|
||||
admin-username: ENC[AES256_GCM,data:GVvF2Q6ymg==,iv:z0aNezBuAit4nZU0o8C5vd0V0eqFV27Ly6BX6EHA/QQ=,tag:mX17Bc5uC/HMH1d4uA3WGw==,type:str]
|
||||
admin-password: ENC[AES256_GCM,data:kdP3lOEMKTOrefDNYY0m9b2JsVEWF0cwYHhpizFRvN0tA8nYv0blgvYmS16KvuUI,iv:9bbntbUewcayR3RgdeieThaFxt0UwFYHIqQoD1VSHHo=,tag:E1DHYb5TuNLl/WVFRuDCtw==,type:str]
|
||||
db-password: ENC[AES256_GCM,data:Xh62RMSTEv9uS4UYhL63qclbRWFhHtUZn9/YCEbBHwkYtImYNSgKltt9d5ntUeKk,iv:ITCJE3J1eSdIzBEnWh3q2BGW3O82fqqRZ+GnWodFh78=,tag:f9MsOPPTec3dGCXweFUetQ==,type:str]
|
||||
oidc-client-secret: ENC[AES256_GCM,data:5eFxTbW6MsrynLBXb8jNljcwDvG5XYFp9z6TtfVXvIaJPGBLlBc21f+0PSZ7mCaGUq9d6pyhqco3LFdJFSg77w==,iv:no50G222ahfHr1J+OYORpgj0olin26FjAE58n2Z1e9o=,tag:4qh1cqnziUIGJtEcJLVX3w==,type:str]
|
||||
sops:
|
||||
kms: []
|
||||
gcp_kms: []
|
||||
@@ -29,8 +30,8 @@ sops:
|
||||
SDdoWXhVRi9DR0RzL0dlM0dtcTlFQlEKNVBukqVCZ7ZjREYAl2xK/YxuJVKEgESh
|
||||
ToKuzCgydEaxW3NoKWMvZxJUM5vnGI1rPxSZdGgXDjcpI4Q4iTYmZA==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
lastmodified: "2026-08-26T06:30:08Z"
|
||||
mac: ENC[AES256_GCM,data:INbmOvfiZFll97zz3Dfq4xFpbBWpNUoW4LOERUSQ2jBy2UBGO8wSNf0r8F328mlO1GqFXFs0a0LD11pDgC2wAhF/bQWYsPFAYNEQFgU3FpxCNnLNPUN9nS58CE9C1sMIs5WaAaOzFBudRXEYulQNRKuLPGOF7UKytxkzmgIggT4=,iv:JuzQ7scb+CQg0kOtwzTqNe0D8V90sQSpiNA/2lMsJr8=,tag:1ICb7zPIqvonmEgEfYdGqA==,type:str]
|
||||
lastmodified: "2026-08-27T22:12:32Z"
|
||||
mac: ENC[AES256_GCM,data:vnwIPefz9V/s6lbkoCvnylEzaPO788eSS5grFOaBJbjT5p0yYCVVpEoh19Z1k9BnQiMKwRL2btMYGwxgN2/BdsmWE8QdpQd25ma5Fso2/EnNxTZ87aM8loA0f8JFaSvU9iyFioqGWqLxXhm7pFzJYUn/vn6cZNB/mcuPcvMI3VY=,iv:HjPfmg77anshGGv+WIOS/vsI5ctsS4JLzh4WJe/xmTk=,tag:QcMgV68/iclfAfLCWK+siQ==,type:str]
|
||||
pgp: []
|
||||
encrypted_regex: ^(data|stringData)$
|
||||
version: 3.9.4
|
||||
@@ -111,9 +111,21 @@ spec:
|
||||
value: https
|
||||
- name: OVERWRITECLIURL
|
||||
value: https://nextcloud.home.keithjackson.dev
|
||||
- name: NEXTCLOUD_OIDC_CLIENT_SECRET
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: nextcloud-bootstrap
|
||||
key: oidc-client-secret
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /var/www/html
|
||||
lifecycle:
|
||||
postStart:
|
||||
exec:
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- "# Idempotent Keycloak OIDC wiring (R-IDP-02/06). Runs alongside the nextcloud entrypoint\n# (which installs/upgrades on first boot). Installs+enables the user_oidc app and creates OR\n# updates the \"keycloak\" login provider (occ user_oidc:provider is create-or-update by\n# identifier). Auto-discovery pulls the realm's endpoints; the `groups` claim drives Nextcloud\n# group provisioning. occ runs as www-data (owns config.php); the client secret is passed via\n# --clientsecret-env so it never lands on a command line. Safe to re-run every start; a missing\n# secret or not-yet-installed instance is skipped so the pod never crashloops (the local\n# break-glass admin still works).\nset -u\nOCC() { su -s /bin/sh www-data -c \"php /var/www/html/occ $*\"; }\ni=0\nuntil OCC status 2>/dev/null | grep -q \"installed: true\"; do\n i=$((i + 1))\n [ \"$i\" -ge 300 ] && { echo \"[nextcloud-oidc] occ not ready after wait; skipping\" >&2; exit 0; }\n sleep 2\ndone\nif [ -z \"${NEXTCLOUD_OIDC_CLIENT_SECRET:-}\" ]; then\n echo \"[nextcloud-oidc] client secret unset; skipping OIDC (break-glass admin only)\"\n exit 0\nfi\nif ! OCC app:list 2>/dev/null | grep -q \"user_oidc\"; then\n OCC app:install user_oidc || echo \"[nextcloud-oidc] app:install user_oidc failed\" >&2\nfi\nOCC app:enable user_oidc >/dev/null 2>&1 || true\nif OCC user_oidc:provider keycloak \\\n --clientid=nextcloud \\\n --clientsecret-env=NEXTCLOUD_OIDC_CLIENT_SECRET \\\n --discoveryuri=https://keycloak.home.keithjackson.dev/realms/homelab/.well-known/openid-configuration \\\n --scope=\"openid email profile\" \\\n --unique-uid=0 \\\n --mapping-uid=preferred_username \\\n --mapping-email=email \\\n --mapping-display-name=name \\\n --group-provisioning=1 \\\n --mapping-groups=groups; then\n echo \"[nextcloud-oidc] provider 'keycloak' configured\"\nelse\n echo \"[nextcloud-oidc] provider config failed\" >&2\nfi\n"
|
||||
volumes:
|
||||
- name: data
|
||||
persistentVolumeClaim:
|
||||
|
||||
@@ -19,6 +19,7 @@ stringData:
|
||||
admin-password: ENC[AES256_GCM,data:544nWY1UmBnlsdPJAQZJMXRumu4W9X7UXZvZ6vHN/ArjRgLXKTArOe5JkT/nNrNw,iv:AC1DDbZj2HfpU19EjzA7T+WpIaR4nZc3PNN61y8bAzU=,tag:r28LFi5w6ZeCCaRaX2jRdg==,type:str]
|
||||
db-password: ENC[AES256_GCM,data:ySkdvaGRCW9fMQqChs5l8dY4zCG19i4HOEinRC48mgLuh9WOrjd16T6jwFgiwB55,iv:+1gztJ22HbwgQJiolZMMWOcKIsxrBJpnruJdtxdooOU=,tag:8CfJGwyPTjDGyr0CvalXMQ==,type:str]
|
||||
access-key-encryption: ENC[AES256_GCM,data:1YAMLo1nVkpJWZc+KBZ7B4ia+Y8P+xMyOhHMcEGGJOXd3o+WC5FqirLBVh4=,iv:j28ozeaDhGWDjoaxFOhn2t9YbYE7L3ah/Mfi+i2DWwY=,tag:czG9Wxc/7Nsuh1W5iATOww==,type:str]
|
||||
oidc-providers-json: ENC[AES256_GCM,data:90yt5mIVWLVI2KrlJWpxhL/BMDpEgRhv7+BL+mBRt79diRvU7pN9vtzhZprAKzcU1sEywlT4EwFlsvJpYyo5Qbkp+ZVknbyhAlGvHhJOBHukOPWgf4LignLerO2GzhXjOOzFqWfZ4EyUxNMWTbSe/xc0zIVHDDNQiYplGVRK+E/ukiI7sD/Tn2RrbrdvpC5GyK0NXWRsXn9ffhE3fCoiRHXagj9i/q+uUrQI5EKpPobrDUAGqvKoBXTl/wJ4phnWg9IqtCTE/c3yA7fLLz4j5dCtPZNSVPT4TKDx8Mdv8CfCgziVicjG2s+yHuWpsQn8lf6nvdJmpYPvIqTtLWdxFWuD8Vfo6Qbq2mqPbKR+KmDOGUnjl4gbZdL1SkE9JiqBUobz7UXeLXwYzxMIsinOfYZLcCk7alrD5Yndj9JDxDJZQ54sMMBfVbxDl94W1VHy03RxAR46KKAbPaxOP5wH4Q4z3SDceNwmuGCDRGChRXiHhN5X2X0GqYCbc8E6GdkIMcpmD80e26ydEmAjw7F89mCF4F/yMzJNiG82qaZIn8HeqgGA8urous9r9bYJNlitYTYQ,iv:jbfVdrrZ9fGiOxhQeoTJfFg1QwiOMkoQyaadiVfxX/w=,tag:JkepcRs34EuEgNb4buTTDQ==,type:str]
|
||||
sops:
|
||||
kms: []
|
||||
gcp_kms: []
|
||||
@@ -34,8 +35,8 @@ sops:
|
||||
elp0SWxHcjg0RWF3aW9mWTJHZ0lhcUEKc0rU5VilAVDleCwqq4SOF+Y9sniu1Pka
|
||||
w/MGDA9THSB0b+YgWRWiX/KiimFqp0ZFODOJ9cHVwFGY71+VadPwOg==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
lastmodified: "2026-08-26T06:45:19Z"
|
||||
mac: ENC[AES256_GCM,data:5XhhTZ9BGQTIdHPNrFUxwgPRm0hOqyzdsjc2ACVSJofBho+3j7XiExGTXTyblSmT5Kv+BEym0rgOjxgKKF59CAnjEMhIoZ/3mYWegQGF8HZhVqDdPgjHmxFF93D6tLK5mQGimXILmDAYcAMRAvQoJGF16BDo4v5YeVZ1bAJZ4Ug=,iv:33cmv3YaskhbUQ+K+dcJQI9LYHKZDCtDjd2NgR5yvts=,tag:CRh/ZsPggCmqyePQe5Q0gw==,type:str]
|
||||
lastmodified: "2026-08-27T22:12:32Z"
|
||||
mac: ENC[AES256_GCM,data:nHftJTGnTr0O5h8yGXwgz3eflggXJfIXM3m8n3bzDJE/P4nvSpZzFeMb+sc2on+AL+t+SeKrIl8blzm33XsQ1lkaY85Zm+bc5cnED1g04BMDVmmK3brh/8w50Bp2wPEVzb4I01MDzhhyIbOYg7pFvZAH3/VuqvM1t6XmfF1NrOA=,iv:4KBa9PH/dfdGwLC16RenZIVB6BtSoGo6x3qt3h1R43I=,tag:KULiA1klP/RZMNigkloGDA==,type:str]
|
||||
pgp: []
|
||||
encrypted_regex: ^(data|stringData)$
|
||||
version: 3.9.4
|
||||
@@ -118,6 +118,11 @@ spec:
|
||||
key: access-key-encryption
|
||||
- name: SEMAPHORE_PORT
|
||||
value: :3000
|
||||
- name: SEMAPHORE_OIDC_PROVIDERS
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: semaphore-bootstrap
|
||||
key: oidc-providers-json
|
||||
volumeMounts:
|
||||
- name: config
|
||||
mountPath: /etc/semaphore
|
||||
|
||||
+62
-8
@@ -57,6 +57,62 @@ env:
|
||||
value: https
|
||||
- name: OVERWRITECLIURL
|
||||
value: https://nextcloud.home.keithjackson.dev
|
||||
# --- Keycloak native OIDC SSO (auth.mode oidc; docs/15 "Native OIDC"; R-IDP-02/06). The confidential
|
||||
# `nextcloud` realm client's secret is mirrored into this namespace's nextcloud-bootstrap Secret
|
||||
# (oidc-client-secret; same value SOPS-held in keycloak-realm-clients). The postStart hook reads it
|
||||
# via user_oidc's --clientsecret-env (never on argv) to register the "keycloak" provider. The LOCAL
|
||||
# admin above stays as the break-glass path (docs/15). ---
|
||||
- name: NEXTCLOUD_OIDC_CLIENT_SECRET
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: nextcloud-bootstrap
|
||||
key: oidc-client-secret
|
||||
lifecycle:
|
||||
postStart:
|
||||
exec:
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- |
|
||||
# Idempotent Keycloak OIDC wiring (R-IDP-02/06). Runs alongside the nextcloud entrypoint
|
||||
# (which installs/upgrades on first boot). Installs+enables the user_oidc app and creates OR
|
||||
# updates the "keycloak" login provider (occ user_oidc:provider is create-or-update by
|
||||
# identifier). Auto-discovery pulls the realm's endpoints; the `groups` claim drives Nextcloud
|
||||
# group provisioning. occ runs as www-data (owns config.php); the client secret is passed via
|
||||
# --clientsecret-env so it never lands on a command line. Safe to re-run every start; a missing
|
||||
# secret or not-yet-installed instance is skipped so the pod never crashloops (the local
|
||||
# break-glass admin still works).
|
||||
set -u
|
||||
OCC() { su -s /bin/sh www-data -c "php /var/www/html/occ $*"; }
|
||||
i=0
|
||||
until OCC status 2>/dev/null | grep -q "installed: true"; do
|
||||
i=$((i + 1))
|
||||
[ "$i" -ge 300 ] && { echo "[nextcloud-oidc] occ not ready after wait; skipping" >&2; exit 0; }
|
||||
sleep 2
|
||||
done
|
||||
if [ -z "${NEXTCLOUD_OIDC_CLIENT_SECRET:-}" ]; then
|
||||
echo "[nextcloud-oidc] client secret unset; skipping OIDC (break-glass admin only)"
|
||||
exit 0
|
||||
fi
|
||||
if ! OCC app:list 2>/dev/null | grep -q "user_oidc"; then
|
||||
OCC app:install user_oidc || echo "[nextcloud-oidc] app:install user_oidc failed" >&2
|
||||
fi
|
||||
OCC app:enable user_oidc >/dev/null 2>&1 || true
|
||||
if OCC user_oidc:provider keycloak \
|
||||
--clientid=nextcloud \
|
||||
--clientsecret-env=NEXTCLOUD_OIDC_CLIENT_SECRET \
|
||||
--discoveryuri=https://keycloak.home.keithjackson.dev/realms/homelab/.well-known/openid-configuration \
|
||||
--scope="openid email profile" \
|
||||
--unique-uid=0 \
|
||||
--mapping-uid=preferred_username \
|
||||
--mapping-email=email \
|
||||
--mapping-display-name=name \
|
||||
--group-provisioning=1 \
|
||||
--mapping-groups=groups; then
|
||||
echo "[nextcloud-oidc] provider 'keycloak' configured"
|
||||
else
|
||||
echo "[nextcloud-oidc] provider config failed" >&2
|
||||
fi
|
||||
exposure: public # edge port-forward + public DNS (docs/09; R-DNS/R-NET)
|
||||
egress: internet # normal WAN egress (docs/03; R-NET-05) — app store, integrity checks
|
||||
storage:
|
||||
@@ -77,14 +133,12 @@ auth:
|
||||
mode: oidc # native Keycloak OIDC client (docs/13 SSO row, docs/15 "Native OIDC"; R-IDP-02)
|
||||
groups:
|
||||
- platform-admins # access scoped to an authorized group (R-IDP-06)
|
||||
# NOTE (OIDC follow-up + break-glass): like Gitea (docs/15 "Availability caveat"), Nextcloud keeps a
|
||||
# LOCAL admin as the break-glass path so an IdP outage never locks the operator out. At initial deploy
|
||||
# it comes up INSTALLED on that local admin (env above). The Keycloak `nextcloud` realm client + its
|
||||
# user_oidc app config is a DEFERRED follow-up: wiring it means editing the reconciled realm import in
|
||||
# platform-config (a malformed edit fails the whole platform-config apply / can leave the realm import
|
||||
# PENDING) AND post-install `occ` steps the image has no first-class env for — deferred rather than
|
||||
# risking the running Keycloak realm / Flux source (WS-APPS step 3 escape hatch). This records the
|
||||
# target integration; it embeds no secret.
|
||||
# WIRED (native OIDC): the Keycloak `nextcloud` confidential client (platform/keycloak/keycloak-realm-
|
||||
# import.yaml + live kcadm) is registered as a user_oidc login provider ("keycloak") by the postStart
|
||||
# hook (occ user_oidc:provider), using the client secret mirrored into nextcloud-bootstrap
|
||||
# (oidc-client-secret; same value SOPS-held in keycloak-realm-clients). The `groups` claim drives
|
||||
# user_oidc group provisioning (R-IDP-06). Nextcloud KEEPS its LOCAL admin as the break-glass path so
|
||||
# an IdP outage never locks the operator out (docs/15 "Availability caveat"). No secret is embedded here.
|
||||
update:
|
||||
channel: stable # R-UPD-01
|
||||
range: ">=31.0.0 <32.0.0" # stay on the 31.x stable line
|
||||
|
||||
+20
-8
@@ -73,6 +73,21 @@ env:
|
||||
# service links), pinning the listen port back to :3000 (matches containerPort/Service).
|
||||
- name: SEMAPHORE_PORT
|
||||
value: ":3000"
|
||||
# --- Keycloak native OIDC SSO (auth.mode oidc; docs/15 "Native OIDC"; R-IDP-02/06). Semaphore
|
||||
# v2.19.10 reads its OIDC providers from the SEMAPHORE_OIDC_PROVIDERS env as a JSON map
|
||||
# (util ConfigType `oidc_providers`, env-tagged SEMAPHORE_OIDC_PROVIDERS). The value is a single
|
||||
# JSON blob for the `keycloak` provider — provider_url is the homelab realm issuer (auto-discovery),
|
||||
# client_id=semaphore, redirect_url=/api/auth/oidc/keycloak/redirect, claims map to the realm's
|
||||
# token. It CONTAINS the confidential client secret, so the whole blob is held SOPS-encrypted in the
|
||||
# semaphore-bootstrap Secret (key oidc-providers-json; same secret value SOPS-held in
|
||||
# keycloak-realm-clients) and injected here by reference — never in cleartext. The name is NOT a
|
||||
# kubelet service-link var (those are SEMAPHORE_SERVICE_HOST / SEMAPHORE_PORT_*), so no collision.
|
||||
# The LOCAL SEMAPHORE_ADMIN above stays as the break-glass path (docs/15). ---
|
||||
- name: SEMAPHORE_OIDC_PROVIDERS
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: semaphore-bootstrap
|
||||
key: oidc-providers-json
|
||||
# The semaphoreui/semaphore image runs as the non-root user UID 1001 / GID 0 (tini -> server-wrapper),
|
||||
# and on boot the wrapper writes its generated config.json into the config dir. A mounted Longhorn PVC
|
||||
# is root:root, so UID 1001 cannot write it -> the pod panics ("permission denied"). fsGroup=1001 makes
|
||||
@@ -101,14 +116,11 @@ auth:
|
||||
mode: oidc # native Keycloak OIDC client (docs/13 SSO row, docs/15 "Native OIDC"; R-IDP-02)
|
||||
groups:
|
||||
- platform-admins # access scoped to an authorized group (R-IDP-06)
|
||||
# NOTE (OIDC follow-up + break-glass): like Gitea/Nextcloud (docs/15 "Availability caveat"),
|
||||
# Semaphore keeps a LOCAL admin as the break-glass path so an IdP outage never locks the operator
|
||||
# out. At initial deploy it comes up on that local admin (SEMAPHORE_ADMIN* env above). The Keycloak
|
||||
# `semaphore` realm client + Semaphore's OIDC provider config (config.json `oidc_providers`) is a
|
||||
# DEFERRED follow-up: wiring it means editing the reconciled realm import in platform-config (a
|
||||
# malformed edit fails the whole platform-config apply / can leave the realm import PENDING) — so it
|
||||
# is deferred rather than risking the running Keycloak realm / Flux source. This records the target
|
||||
# integration; it embeds no secret.
|
||||
# WIRED (native OIDC): the Keycloak `semaphore` confidential client (platform/keycloak/keycloak-realm-
|
||||
# import.yaml + live kcadm) is consumed via the SEMAPHORE_OIDC_PROVIDERS env (JSON blob held encrypted
|
||||
# in the semaphore-bootstrap Secret, oidc-providers-json). The `groups` claim is exposed for
|
||||
# authorization (R-IDP-06). Semaphore KEEPS its LOCAL admin as the break-glass path so an IdP outage
|
||||
# never locks the operator out (docs/15 "Availability caveat"). No secret is embedded here.
|
||||
update:
|
||||
channel: stable # R-UPD-01
|
||||
range: ">=2.19.0 <3.0.0" # stay on the 2.x stable line
|
||||
|
||||
@@ -8,6 +8,8 @@ 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]
|
||||
nextcloud-client-secret: ENC[AES256_GCM,data:Uh3+EqIr88CNNt3KfDYP8lbNIhcB9iQrxJvbaxek40hD+JYAJmUP31WQypyDY3BiSvkjE6urr9PJmYoDgz7tkw==,iv:Tg4WAgyydIUBB1mqdOZPt0HsCkWSGNPvbI4WtFKj9V8=,tag:1LgZYdDPVEM+9UROanBllw==,type:str]
|
||||
semaphore-client-secret: ENC[AES256_GCM,data:BX5lXhFNX6vQh67G2+XM2vt9wymhL8Psx+NqQJneFup7gdZRjhk8paEU5dOfugZNwooieara7UFyAFm5+1BJFg==,iv:kmmz0ZiCZgsSowns3hjDTQzoD2lPCr9Oz1iS5WgbhEE=,tag:BbesQX6lxAhaGiqPqMQ9vA==,type:str]
|
||||
sops:
|
||||
kms: []
|
||||
gcp_kms: []
|
||||
@@ -23,8 +25,8 @@ sops:
|
||||
YXJGUUJiTnFndFhDZDU1bmxXTENCbmsKuhb3RQ3LZmuTPAXEck8b6iuZ5vxLb9fV
|
||||
+fQELmU4kOTbZrPSxy+kpttLG5Cy5d6X2ieZXBf6opzpLTUhlBOkHA==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
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]
|
||||
lastmodified: "2026-08-27T22:12:32Z"
|
||||
mac: ENC[AES256_GCM,data:nruWApjP/BK694O9/BtSsQExmjtWTBNCfq+FyXDtockjRdDHRmyxuePVdfvm2F/OO7AuxI2EPYhr+a1SiB+RYGWfz7GCtbKW1iDPPmgiAnGxFH/HiuEGpa7S4+CVkNaD1j/z6jBkYe6oPko2IO/ezVtcTG/x7Wzst8vRDeid5Tw=,iv:wzuEl5cnycPMpoBGWrx1F2YoIgFufZ9se0yzVcQznEQ=,tag:B5pSL2IEC9T5maa7UZhYJw==,type:str]
|
||||
pgp: []
|
||||
encrypted_regex: ^(data|stringData)$
|
||||
version: 3.9.4
|
||||
@@ -27,6 +27,14 @@ spec:
|
||||
secret:
|
||||
name: keycloak-realm-clients
|
||||
key: gitea-client-secret
|
||||
NEXTCLOUD_CLIENT_SECRET:
|
||||
secret:
|
||||
name: keycloak-realm-clients
|
||||
key: nextcloud-client-secret
|
||||
SEMAPHORE_CLIENT_SECRET:
|
||||
secret:
|
||||
name: keycloak-realm-clients
|
||||
key: semaphore-client-secret
|
||||
realm:
|
||||
realm: homelab
|
||||
enabled: true
|
||||
@@ -182,6 +190,68 @@ spec:
|
||||
"access.token.claim": "true"
|
||||
"userinfo.token.claim": "true"
|
||||
"claim.name": groups
|
||||
# nextcloud — native OIDC SSO for the file-sync host (catalog/nextcloud.yaml auth.mode oidc; docs/13
|
||||
# SSO row, docs/15; R-IDP-02). Confidential auth-code client modeled on grafana. Redirect URI is the
|
||||
# user_oidc app's callback (/apps/user_oidc/code). The `groups` claim drives group provisioning in
|
||||
# Nextcloud (user_oidc --group-provisioning; R-IDP-06). The LOCAL Nextcloud admin stays break-glass
|
||||
# (docs/15). Secret injected from the SOPS keycloak-realm-clients Secret via NEXTCLOUD_CLIENT_SECRET.
|
||||
- clientId: nextcloud
|
||||
name: Nextcloud
|
||||
protocol: openid-connect
|
||||
enabled: true
|
||||
publicClient: false
|
||||
standardFlowEnabled: true
|
||||
directAccessGrantsEnabled: false
|
||||
secret: "$(env:NEXTCLOUD_CLIENT_SECRET)"
|
||||
redirectUris:
|
||||
- https://nextcloud.home.keithjackson.dev/apps/user_oidc/code
|
||||
webOrigins:
|
||||
- https://nextcloud.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
|
||||
# semaphore — native OIDC SSO for the Ansible UI (catalog/semaphore.yaml auth.mode oidc; docs/13 SSO
|
||||
# row, docs/15; R-IDP-02). Confidential auth-code client modeled on grafana. Redirect URI is
|
||||
# Semaphore's OIDC callback (/api/auth/oidc/<provider>/redirect for provider `keycloak`). The
|
||||
# `groups` claim is exposed for authorization (R-IDP-06). The LOCAL Semaphore admin stays break-glass
|
||||
# (docs/15). Secret injected from the SOPS keycloak-realm-clients Secret via SEMAPHORE_CLIENT_SECRET.
|
||||
- clientId: semaphore
|
||||
name: Semaphore
|
||||
protocol: openid-connect
|
||||
enabled: true
|
||||
publicClient: false
|
||||
standardFlowEnabled: true
|
||||
directAccessGrantsEnabled: false
|
||||
secret: "$(env:SEMAPHORE_CLIENT_SECRET)"
|
||||
redirectUris:
|
||||
- https://semaphore.home.keithjackson.dev/api/auth/oidc/keycloak/redirect
|
||||
webOrigins:
|
||||
- https://semaphore.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