Skip to content

feat(authentik): Gmail login + OIDC providers for the FuzeInfra admin plane - #454

Merged
izzywdev merged 1 commit into
masterfrom
feat/authentik-infra-sso
Jul 29, 2026
Merged

feat(authentik): Gmail login + OIDC providers for the FuzeInfra admin plane#454
izzywdev merged 1 commit into
masterfrom
feat/authentik-infra-sso

Conversation

@izzywdev

Copy link
Copy Markdown
Owner

Makes Authentik usable as the single sign-on for FuzeInfra's admin UIs, and fixes the two reasons Google sign-in has never actually worked.

This is the IdP half and must merge first. Consumer half: izzywdev/FuzeInfra#470.

Google sign-in was wired but unreachable

source-google.yaml creates the Google OAuth source, sets enabled: true, and configures its flows. But the login button is rendered from the sources M2M on the identification stage, and creating a source does not add it there. So the source existed, was enabled, and no user could ever reach it — the comment in that file (# Show a "Sign in with Google" button on the login page) describes an outcome the config does not produce.

source-google-binding.yaml adds it to default-authentication-identification. authentik-built-in is kept in the list — the field is a replace, not an append, and dropping it would remove username/password login entirely and strand the akadmin bootstrap account.

Google users had nowhere to land

With user_matching_mode: link_by_email, an email matching an existing user links into that account; an unmatched email enrolls a fresh, unprivileged one. The infra admin's Google address is not the akadmin bootstrap address (admin@fuzefront.com), so signing in with Google would have produced a powerless second account.

groups-fuzeinfra-admins.yaml pre-creates the user in a superuser group, so the first Google sign-in links straight into it. No post-login clickops, and a rebuild from scratch reproduces it. The user has no password, so the account is Google-only.

Four OIDC providers for the FuzeInfra admin plane

provider-oidc-fuzeinfra-admin.yaml adds Cloudflare Access (which fronts every *.prod admin tile, including services with no SSO of their own), Grafana, ArgoCD and Kafka UI.

All entries live in one blueprint so nothing depends on a cross-blueprint !Find — per the concurrent-apply race documented in provider-oidc-mendys-platform.yaml. Entries within a blueprint apply in order, so the shared flow and scope mappings are safe to reference.

A dedicated groups scope is defined, not just groups-inside-profile: Grafana's role_attribute_path and ArgoCD's policy.csv both request groups by name, and without a scope of that name the claim never arrives and every user silently lands on the lowest role.

Blueprint changes never rolled the pods

Neither Deployment had a checksum/blueprints annotation, even though authentik/ops.py:8 states the deployments "roll on blueprint changes via a checksum annotation". Blueprints are discovered at startup, so editing the ConfigMap changed nothing and edits sat unapplied — which is why prod-authentik-ops.yml describes the file-based runner as failing "silently" and exists as a manual workflow_dispatch escape hatch.

Both Deployments now carry the annotation. This is a precondition for everything else in this PR actually applying.

Client secrets

Four optional secretKeyRefs (CF_ACCESS_CLIENT_SECRET, GRAFANA_CLIENT_SECRET, ARGOCD_CLIENT_SECRET, KAFKA_UI_CLIENT_SECRET) on fuzefront-secrets. Not minted in this PR — see deploy/sealed-secrets/authentik-oidc-secrets.yaml.template in FuzeInfra for the sealing runbook.

⚠️ Seal the values before these providers are first created. The 2024.x blueprint runner honours client_secret only at creation time; if a provider is created while the key is absent it is stamped with an empty secret, and sealing the value later does not update it. Recovery is deleting the provider entry and re-applying.

Verified

helm template with values-prod.yaml: all three blueprints land in the ConfigMap, all four env vars appear on both server and worker, and the checksum/blueprints annotation matches across both pod templates.

Not verified: nothing applied to the cluster; no live Authentik API call made.

Noticed in passing, not fixed here

  • MENDYS_DATASETS_CLIENT_SECRET is absent from the prod SealedSecret, so that provider was created with an empty client secret (silent — the secretKeyRef is optional).
  • Prod Authentik has no SMTP (authentik.smtp.host unset in values-prod.yaml), so the blueprinted enrollment-verification and recovery email stages cannot deliver.
  • docs/AUTHENTICATION_SETUP.md and README.md:95 still describe Authentik as docker-compose + clickops, which has been wrong since the blueprints landed.

🤖 Generated with Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

Automated code review (gate-code-review)

Credit balance is too low

Report-only — this check never blocks merge.

@izzywdev
izzywdev marked this pull request as draft July 29, 2026 16:33
auto-merge was automatically disabled July 29, 2026 16:33

Pull request was converted to draft

@izzywdev
izzywdev marked this pull request as ready for review July 29, 2026 16:56
@github-actions
github-actions Bot enabled auto-merge (squash) July 29, 2026 16:56
… plane

Makes Authentik usable as the single sign-on for FuzeInfra's admin UIs, so
each service stops carrying its own local password.

Google sign-in was wired but unreachable. source-google.yaml creates the
source and sets its flows, but the login button is rendered from the `sources`
M2M on the identification stage — creating a source does not add it there. The
new source-google-binding.yaml adds it, keeping authentik-built-in in the list
so username/password (the akadmin break-glass) survives.

Google-federated users also had nowhere to land: with link_by_email, an email
that matches no existing user enrolls as an ordinary account. Since the infra
admin's Google address is not the akadmin bootstrap address, signing in with
Google produced a powerless second account. groups-fuzeinfra-admins.yaml
pre-creates the user in a superuser group so the first Google sign-in links
straight into it.

provider-oidc-fuzeinfra-admin.yaml adds four OIDC providers — Cloudflare
Access (which fronts every *.prod admin tile, including services with no SSO
of their own), Grafana, ArgoCD and Kafka UI. All entries live in ONE blueprint
so nothing depends on a cross-blueprint !Find, per the concurrent-apply race
documented in provider-oidc-mendys-platform.yaml. A dedicated `groups` scope
is defined because Grafana and ArgoCD request it by name for role mapping.

Also adds the checksum/blueprints annotation the chart never had. Blueprints
are discovered at startup, so editing the ConfigMap alone changed nothing and
edits sat unapplied — the reason blueprint changes appeared to "fail silently"
in prod and had to be pushed through prod-authentik-ops by hand.

Client secrets are optional secretKeyRefs and are not minted here. Note the
2024.x runner honours client_secret only at creation time, so the values must
be sealed BEFORE the providers are first created.

Consumer side: izzywdev/FuzeInfra

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session-Id: 0e31d346-0be5-451a-9c99-c579ce8d4aa6
@izzywdev
izzywdev force-pushed the feat/authentik-infra-sso branch from 8c3b874 to edfc0fd Compare July 29, 2026 16:57
@github-actions

Copy link
Copy Markdown
Contributor

Automated code review (gate-code-review)

Credit balance is too low

Report-only — this check never blocks merge.

@izzywdev
izzywdev merged commit 331c143 into master Jul 29, 2026
54 checks passed
@izzywdev
izzywdev deleted the feat/authentik-infra-sso branch July 29, 2026 17:11
izzywdev added a commit that referenced this pull request Jul 29, 2026
… invalidation_flow (#456)

* fix(authentik): FuzeInfra OIDC providers were never created — missing invalidation_flow

All four providers added in #454 (cloudflare-access, grafana, argocd, kafka-ui)
silently failed to apply. invalidation_flow is a REQUIRED field on
oauth2provider in Authentik 2024.12, and omitting it fails validation.

The failure mode is quiet and misleading. Every provider entry is skipped, the
Applications are created with no provider bound, and the only symptom is a 404
from /application/o/<slug>/.well-known/openid-configuration. Meanwhile the other
blueprints from the same PR applied fine — the Google sign-in button is live on
the login page — so it looked like the blueprints had run when they only half
had.

Confirmed against prod: /application/o/fuzefront/ and /application/o/mendys-platform/
both return 200 (their blueprints set invalidation_flow); grafana, argocd,
kafka-ui and cloudflare-access all returned 404.

Referencing default-provider-invalidation-flow is safe despite this file's
inline-everything rule: it ships in Authentik's own system blueprints, applied
at startup before any custom blueprint, so it is not exposed to the concurrent
apply race that rule exists to avoid. Documented in the header so it does not
get tidied away again.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session-Id: 0e31d346-0be5-451a-9c99-c579ce8d4aa6

* secrets(prod): seal the four FuzeInfra admin-plane OIDC client secrets

Adds CF_ACCESS_CLIENT_SECRET, GRAFANA_CLIENT_SECRET, ARGOCD_CLIENT_SECRET and
KAFKA_UI_CLIENT_SECRET to fuzefront-secrets, merged in alongside the existing 15
keys.

These MUST land in the same change as the provider fix. The Authentik 2024.x
blueprint runner honours client_secret only at creation time, so if the
providers are created while these keys are absent they are stamped with an empty
secret and sealing afterwards does not update them — recovery would mean
deleting the provider entries and re-applying.

Values are 96-char hex, generated with no trailing newline: seal-secret.sh
base64s a KEY=@file value verbatim, so a trailing newline would be sealed INTO
the secret while `gh secret set` strips it, silently desynchronising the two
sides of the handshake.

The consumer halves of the same four values are sealed in izzywdev/FuzeInfra#470.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session-Id: 0e31d346-0be5-451a-9c99-c579ce8d4aa6

* chore(semgrep): exclude SealedSecret manifests from generic-secret detection

detected-generic-secret fires on the base64 blobs in spec.encryptedData, which
are ciphertext by construction — encrypted against the cluster's published
public cert, decryptable only by the in-cluster controller's private key. Being
safe to commit is the whole point of Sealed Secrets.

It also re-fires on the entire file whenever a diff touches any key, so every
routine credential rotation would turn the check red for a false positive. That
is a check people learn to ignore, which is worse than no check.

Scoped to deploy/contabo/sealed/ only, and the rationale notes the trade-off: a
plaintext v1/Secret accidentally committed to THAT directory would no longer be
caught by this rule, so it must stay sealed-manifests-only. Detection is
unchanged everywhere else.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session-Id: 0e31d346-0be5-451a-9c99-c579ce8d4aa6

---------

Co-authored-by: fuzeone-bot[bot] <fuzeone-bot[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
izzywdev pushed a commit that referenced this pull request Jul 29, 2026
Resolves the sealed-secret conflict. master (#454/#456, the Authentik OIDC work)
rewrote deploy/contabo/sealed/fuzefront-secrets.yaml from JSON to YAML and added
four keys — ARGOCD_CLIENT_SECRET, CF_ACCESS_CLIENT_SECRET, GRAFANA_CLIENT_SECRET,
KAFKA_UI_CLIENT_SECRET — while this branch had added LITELLM_MASTER_KEY to the
old JSON form. Git saw the whole file as rewritten and could not merge it.

Resolution: took master's YAML file wholesale (it is the newer canonical format
and carries the four new keys), then re-inserted LITELLM_MASTER_KEY in its
alphabetical position with the byte-identical ciphertext FuzeInfra sealed for
fuzefront/fuzefront-secrets. Verified afterwards: 20 keys, none of the 15
pre-existing ones lost, LITELLM_MASTER_KEY value intact, target still
fuzefront/fuzefront-secrets.

values-prod.yaml auto-merged cleanly — my edits (chatService.enabled: true, the
secret.litellmMasterKey gate, deletion of the orphaned litellm: block) sit
alongside master's image-tag bumps to fcf2390 with no overlap.

Worth noting: billing-service's tag is now fcf2390 rather than the stale
6135e6b it had been frozen at since 2026-06-30, which confirms the missing
bump anchor added in #442 is doing its job.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants