Adds missing CLIENT_SECRET env injection + nodeSelector constraint:
- k8s/argocd/bootstrap/forgejo.yaml: inject GITEA__oauth2__CLIENT_SECRET
from forgejo-oidc Secret (created by authentik-provision Job), and pin
pods to talos-cp-1 via nodeSelector (only node with Longhorn storage —
gitea-shared-storage PVC can't attach on cp-2/cp-3)
Root cause chain for 'Forgejo SSO not working':
1. Authentik 2026.5.5 requires explicit grant_types on OAuth2 providers
2. Old provision script never set it → all providers had grant_types=[]
3. /authorize returned 'Invalid grant_type for provider' → all SSO broken
4. Fixed in k8s/security/iam/scripts/authentik-provision.py (commit be2a56c)
+ successfully re-ran via iam-jobs Application sync
5. But Forgejo deployment still missing CLIENT_SECRET env var → no creds
6. Forgejo bootstrap App used inline valuesObject (chicken-egg with git
repo self-hosting), but missing the extraEnv block that was only in
k8s/security/ci-cd/forgejo-values.yaml → CLIENT_SECRET never injected
All 4 OAuth2 providers now have correct grant_types=['authorization_code',
'refresh_token'], Forgejo pods now have CLIENT_SECRET env, and pods are
constrained to the storage node. SSO login flow should now work end-to-end.
10 KiB
Plan: Fix homelab SSO end-to-end, add Homarr landing page, add OAuth flow-replay test
Context
Reported symptom: clicking any app in the Authentik launcher, no application lets you sign in. Read-only diagnosis (replaying the OAuth2 authorize flow against Authentik with the bootstrap token) found the true root cause — not missing objects:
- Every OAuth2 provider has
grant_types = []. Authentik 2026.5.5 added an explicitgrant_typeslist field; the provision script (authentik-provision-job.yaml) never sets it, so it defaults empty./authorizethen logs "Invalid grant_type for provider" (grant_type=authorization_code) → invalid_request "The request is otherwise malformed" and bounces an error back to the app. Breaks all apps (grafana/minio/forgejo/argocd) identically. Providers, apps, client secrets, flows, redirect_uris, signing key all exist and are correct — which is why "check the objects exist" checks passed while SSO was 100% broken. Verification must replay the real flow.
Secondary issues found:
- MinIO app side unwired: deployed
minio-tenant.yamlsets onlyMINIO_IDENTITY_OPENID_SCOPES; lacksCONFIG_URL/CLIENT_ID/envFrom minio-oidc. Full config sits in an orphanedminio-values.yamlthe kustomization doesn't include. - Custom
homelab: groups claimproperty mapping uses deprecatedUser.ak_groups(deprecation warning; should beUser.groups).
User-requested additions: Homarr landing page (Authentik SSO, official Helm chart, declarative infra), Portainer OIDC wired via the Portainer API, and an automated SSO test that replays the OAuth2 flow across portainer/grafana/minio/argocd (+forgejo/homarr).
Verified repo facts
- App registration: individual
ApplicationCRs; user apps ink8s/argocd/apps/60-applications.yaml(roothomelab-root→path: k8s/argocd/apps).layer-Nfiles are stale. - nginx default cert
ingress-nginx/riotpiao-com-tls(*.riotpiao.com) → ingresses need notls:. - Default SC
longhorn-wffc(WFFC, single-node → needsnodeSelector zone=az-a+ CP toleration). - Provision job pattern (
k8s/security/iam/authentik-provision-job.yaml): ConfigMap python +batch/v1Job, PostSync hook,python:3.12-alpine+ stdlib urllib, bootstrap tokeniam/authentik-secrets:AUTHENTIK_BOOTSTRAP_TOKEN, SAauthentik-provisionerwith per-namespace RoleBindings (iam/cicd/argocd/logging/storage — no dashboard).SERVICESdict + idempotentget_or_create(..., patch_existing=...). - In-cluster reach: CoreDNS rewrites
*.riotpiao.com→ nginx, so pods can curl realhttps://authentik.riotpiao.com/...(valid LE cert). Reference verify script:k8s/security/iam/verify_existing_oauth_integrations.sh. - Blackbox exporter already probes app URLs (availability only) via
serviceMonitor.targetsink8s/platform/monitoring/blackbox-exporter-values.yaml.
Part A — FIX THE SSO BUG (highest priority)
A1. Set grant_types on every provider — k8s/security/iam/authentik-provision-job.yaml
In the provider get_or_create (create payload and patch_existing, ~lines 281-308) add:
"grant_types": ["authorization_code", "refresh_token"],
Single fix that restores SSO for all four existing apps. patch_existing updates the
already-created providers on the next hook run.
A2. Fix deprecated groups claim — same file
Update the homelab: groups claim property-mapping expression from request.user.ak_groups
to request.user.groups.
A3. Re-run + verify
Trigger the PostSync hook (kubectl -n argocd patch application iam-jobs ... syncStrategy.hook),
then re-run the flow-replay (Part D) — /authorize must now 302 to the authentication flow, not
error=invalid_request.
Part B — MinIO app-side OIDC (make its login actually work)
Extend the env: block in the deployed Tenant k8s/infrastructure/minio/minio-tenant.yaml
(keep _SCOPES):
env:
- name: MINIO_IDENTITY_OPENID_CONFIG_URL
value: "https://authentik.riotpiao.com/application/o/minio/.well-known/openid-configuration"
- name: MINIO_IDENTITY_OPENID_CLIENT_ID
value: "minio"
- name: MINIO_IDENTITY_OPENID_CLAIM_NAME
value: "policy"
- name: MINIO_IDENTITY_OPENID_REDIRECT_URI
value: "https://minio.riotpiao.com/oauth_callback"
- name: MINIO_IDENTITY_OPENID_DISPLAY_NAME
value: "Authentik"
# + MINIO_IDENTITY_OPENID_CLIENT_SECRET from secret storage/minio-oidc
- name: MINIO_IDENTITY_OPENID_SCOPES
value: "openid,profile,email,minio"
Client secret from storage/minio-oidc key MINIO_IDENTITY_OPENID_CLIENT_SECRET. Retire the
orphaned minio-values.yaml (or mark the tenant as source of truth).
Part C — New features
C1. Homarr landing page (SSO, official chart, declarative infra)
k8s/applications/homarr/:kustomization.yaml(stub, nsdashboard) +homarr-values.yaml(officialhomarr-labs/homarrchart, imageghcr.io/homarr-labs/homarr, pinned version).- Persistence PVC
longhorn-wffc(2–5Gi RWO) +nodeSelector zone=az-a+ CP toleration. - OIDC env:
AUTH_PROVIDERS=oidc,credentials,AUTH_OIDC_ISSUER=https://authentik.riotpiao.com/application/o/homarr/,AUTH_OIDC_CALLBACK_URL=https://homarr.riotpiao.com/api/auth/callback/oidc,AUTH_OIDC_CLIENT_NAME=Authentik,AUTH_OIDC_GROUPS_ATTRIBUTE=groups,OAUTH_ALLOW_DANGEROUS_EMAIL_ACCOUNT_LINKING=true,BASE_URL/NEXTAUTH_URL=https://homarr.riotpiao.com;AUTH_OIDC_CLIENT_ID/SECRETvia secretKeyRef →dashboard/homarr-oidc;SECRET_ENCRYPTION_KEYvia the SOPS secret below.
- Persistence PVC
k8s/applications/homarr/homarr-secrets.enc.yaml: SOPS-encryptedSECRET_ENCRYPTION_KEY(openssl rand -hex 32), age recipientage1smu533f803gmd0jq60s2zaj9zlznajy0ca6rtewd4r37mr2hs3uqsrldfh(stable — a new key invalidates saved integrations). Followsk8s/applications/temporal/temporal-secrets.enc.yaml.60-applications.yaml: multi-sourceApplication(charthomarrfromhttps://homarr-labs.github.io/charts+ in-repo$values), nsdashboard, wave 8,CreateNamespace=true, automated prune/selfHeal.- Ingress
homarr.riotpiao.com→k8s/bootstrap/ingress/ingress.yaml(notls:). - CoreDNS rewrite for
homarr.riotpiao.com→k8s/bootstrap/coredns/coredns-configmap.yaml. - Add
homarrtoSERVICES(secretdashboard/homarr-oidc,generate_if_missing,extra_secret_literals {client-id: homarr}, redirect.../api/auth/callback/oidc). - Add a dashboard RoleBinding for SA
authentik-provisioner(mirror storage/logging). - Tile content is UI-managed on the PVC (Homarr v1 has no config-as-code — accepted caveat).
C2. Portainer OIDC via Portainer API (user-chosen)
- Add
portainertoSERVICES(authentik provider+app+secret, redirecthttps://portainer.riotpiao.com/). - New
k8s/applications/portainer/portainer-oauth-job.yaml(PostSync hook, python:3.12-alpine + urllib): authenticate to Portainer API (admin creds from SOPS secret),PUT /api/settingswith the OAuth block (AuthorizationURL/AccessTokenURI/ResourceURI/RedirectURI/ClientID/ClientSecret,AuthenticationMethod: 3). Handle first-run admin init. CE caveat: login works, team auto-map is BE-only → teams assigned manually. Shares thedashboardRoleBinding.
Part D — Automated SSO flow-replay test (the "proper verification")
New k8s/security/iam/sso-verify-job.yaml — ConfigMap python + batch/v1 Job, PostSync hook,
python:3.12-alpine + stdlib urllib, bootstrap-token access. Logic ported from
verify_existing_oauth_integrations.sh but replays real OAuth2 (not just object existence).
For each app in {grafana, minio, forgejo, argocd, portainer, homarr}:
- Provider assert (catches THIS bug): assert
authorization_code in grant_typesandredirect_urisnon-empty; application +homelab-adminsbinding exist. - Discovery assert: GET
.../application/o/<slug>/.well-known/openid-configuration(through ingress); assertissuerishttps://and endpoints present. - Authorize-replay assert (key check): GET
/application/o/authorize/?client_id=<slug> &redirect_uri=<registered>&response_type=code&scope=openid...(no redirect follow); assert 302 → Authentik authentication flow (/flows/), noterror=invalid_request. - (Stretch) full code exchange: authenticate a dedicated test user via the flow executor API,
complete
/authorize→code, POST/application/o/token/with client secret, assert validid_token(issmatch,groupsclaim present). Optional to keep the hook fast/non-flaky.
Job fails non-zero on any assertion failure → ArgoCD marks the hook Degraded (visible + alertable).
Optionally add per-app .well-known targets to blackbox-exporter-values.yaml for a continuous
availability signal.
Files touched
k8s/security/iam/authentik-provision-job.yaml— grant_types fix (A1), groups-claim deprecation (A2), homarr + portainer SERVICES entries,dashboardRoleBinding.k8s/infrastructure/minio/minio-tenant.yaml— MinIO OIDC env (B); retireminio-values.yaml.- new
k8s/security/iam/sso-verify-job.yaml— flow-replay SSO test (D). - new
k8s/applications/homarr/{kustomization.yaml,homarr-values.yaml,homarr-secrets.enc.yaml}(C1). - new
k8s/applications/portainer/portainer-oauth-job.yaml(C2). - edit
k8s/argocd/apps/60-applications.yaml(Homarr Application). - edit
k8s/bootstrap/ingress/ingress.yaml(homarr host). - edit
k8s/bootstrap/coredns/coredns-configmap.yaml(homarr rewrite).
Verification (end-to-end)
- Commit/push each logical change; ArgoCD auto-syncs. Order: A1/A2 (grant_types) first.
- Re-trigger
iam-jobs; confirm providergrant_typesnow includesauthorization_code. - Flow-replay:
/authorizeper app returns 302 → /flows/, notinvalid_request. - Browser: log into Authentik as
rock, click each tile → lands logged-in in grafana/argocd/forgejo/minio/homarr with no OAuth error. sso-verify-jobcompletes green; reverting grant_types in a scratch test turns it red (proves it detects the real failure).- Homarr reachable at
https://homarr.riotpiao.com, SSO works; add tiles in UI.
Notes / caveats
- Homarr v1 tile content is DB-backed (PVC), not git — accepted.
- Portainer CE: login works but no group→team auto-map (BE-only); teams assigned manually.
- Authentik liveness kill-loop already fixed earlier this session (probe 3s→15s), which is why authentik is now reachable for provisioning/tests.