Skip to content

feat(authentik): MendysRobotics identity silo — separate Authentik instance + platform OIDC client - #428

Merged
izzywdev merged 1 commit into
masterfrom
feat/mendys-authentik-tenant
Jul 28, 2026
Merged

feat(authentik): MendysRobotics identity silo — separate Authentik instance + platform OIDC client#428
izzywdev merged 1 commit into
masterfrom
feat/mendys-authentik-tenant

Conversation

@izzywdev

Copy link
Copy Markdown
Owner

Registers the requested per-product Authentik OIDC client for MendysRobotics platform SSO — but as its own identity silo, not as another provider on the shared FuzeFront directory.

Companion PR (backing database): izzywdev/FuzeInfra#421

Why a separate instance and not a brand

The requirement is that MendysRobotics has its own set of accounts: a FuzeFront account must not be recognised by the Mendys apps, a Mendys account must not be recognised by FuzeFront, and the same email may exist independently on both sides.

That cannot be done by adding a blueprint to the existing instance:

  • Authentik has no realm. One instance is one user directory.
  • Brands are soft only — branding + default flows per domain. Per authentik's docs, "all objects like applications and providers are still global". A Mendys brand would still share every account.
  • Hard tenancy (a Postgres schema per tenant, 2024.2+) does isolate users, but it is Enterprise-only, still alpha, needs a license per tenant, and is managed only via the API — it cannot be expressed as a blueprint in git, which is how every other authentik object here is managed.

A second instance on its own database gives the same isolation property for free and stays fully declarative.

What's here

  • templates/authentik-mendys.yaml — the instance (server + worker + Service), gated on authentikMendys.enabled, default off.
  • templates/authentik-mendys-blueprints.yaml — ConfigMap sourced only from authentik/blueprints-mendys/.
  • blueprints-mendys/provider-oidc-mendys-platform.yaml — the requested client: client_type: confidential, client_id: mendys-platform-oidc-client, client_secret via !Env, sub_mode: user_email, include_claims_in_id_token: true, openid/email/profile scope mappings, an invalidation_flow, and both strict redirect URIs (live. and marketplace.mendysrobotics.com /api/auth/oidc/callback). Application slug mendys-platform, launch URL https://live.mendysrobotics.com/.
  • blueprints-mendys/{brand-mendys,flow-enrollment}.yaml — the silo's brand and a self-service enrollment flow mirroring FuzeFront's design.
  • blueprints-mendys/provider-oidc-mendys-datasets.yaml — the datasets provider's silo-side copy.
  • MENDYS_PLATFORM_CLIENT_SECRET plus the instance's own secret key, bootstrap credentials and DB password, wired through the chart Secret and env.
  • blueprints-mendys/README.md — the boundary, the secret contract, and the exposure decision.

Isolation invariants (each enforced and documented in-file)

# Invariant Why it is load-bearing
1 Separate database Separate user table. This is the boundary.
2 Separate AUTHENTIK_SECRET_KEY Otherwise either instance could validate the other's sessions/tokens.
3 Separate Redis logical DB Authentik stores cache + sessions under non-namespaced keys.
4 Separate cookie domain A shared parent domain would send one instance's session cookie to the other.
5 Separate blueprint mount Authentik applies every *.yaml under /blueprints; a shared mount would recreate FuzeFront's flows/providers/apps inside the Mendys directory.

Because the directory itself is the tenant boundary, the applications use policy_engine_mode: all with no group bindings — there is no need to police who may reach them, since users from the other side have no account here at all.

Datasets migration is staged, not switched

The FuzeFront-side datasets provider is left untouched and serving traffic. Retiring it before MendysRobotics repoints its issuer would black out datasets sign-in, and deleting the blueprint file alone would not remove the objects anyway — that needs an explicit state: absent pass. Both copies coexist safely: different instances, different databases, so the shared client_id does not collide.

Accounts do not migrate. Existing datasets users live in the FuzeFront directory and have no account in the silo until they enroll there.

Verification

  • helm lint clean.
  • kubeconform -strict -ignore-missing-schemas: 16/16 valid, 0 invalid (1 skip = Traefik CRD).
  • Every blueprint parses under authentik's !Env/!Find tags; provider attributes asserted against the spec above.
  • The FuzeFront render is byte-identical with the silo off — enabling this cannot disturb the live IdP. (An earlier revision of this branch added a comment to a FuzeFront blueprint; that changed checksum/blueprints and would have rolled the live single-replica IdP, so it was reverted.)

One bug caught during review: the new provider initially omitted grant_types. On authentik 2026.x a freshly-created provider without it rejects every authorize request (Invalid grant_type) — the client would have been dead on arrival. Fixed; both providers now assert [authorization_code, refresh_token].

Follow-ups (deliberately not in this PR)

  • The issuer URL in izzywdev/MendysRobotics#242 is now wrong. https://auth.fuzefront.com/application/o/mendys-platform/ points at the FuzeFront instance, a different directory. The Mendys side must repoint.
  • Browser-facing exposure is undecided. authentikMendys.ingress is off by default because FuzeFront deliberately has no public IdP host. Preferred option is an /api/auth/idp/* proxy path on the MendysRobotics ingress; the alternative is a public auth.mendysrobotics.com behind Cloudflare Access. See the README.
  • The two SealedSecrets, and flipping both enabled gates (one PR, once the secrets exist).
  • CF tunnel / CF Access for whichever host is chosen.
  • Retiring the FuzeFront-side datasets provider, after the repoint.

The client secret is not generated here — it must exist only in the destination secret stores. Generate with openssl rand -hex 32 and place the same value in MENDYS_PLATFORM_CLIENT_SECRET (this chart) and MENDYS_AUTHENTIK_CLIENT_SECRET in mendys-secrets (namespace mendys-prod). Note blueprints stamp client_secret only at provider creation — rotation means deleting and re-applying the provider entry.

🤖 Generated with Claude Code

…stance + platform OIDC client

MendysRobotics gets its own Authentik (server + worker) with its own Postgres
database, so its accounts are genuinely separate from FuzeFront's: a FuzeFront
account cannot sign in to the Mendys apps, a Mendys account cannot sign in to
FuzeFront, and the same email may exist independently on both sides.

Why an instance and not a brand: authentik has no realm. One instance is one
user directory, and brands are branding + default flows only ("all objects
like applications and providers are still global"). Hard tenancy (a Postgres
schema per tenant, 2024.2+) does isolate users but is Enterprise-only, alpha,
licensed per tenant and API-managed — it cannot be expressed as a blueprint in
git, which is how every other authentik object here is managed. A second
instance on its own database gives the same isolation for free and stays
declarative.

Adds:
  - templates/authentik-mendys.yaml — the instance, gated on
    authentikMendys.enabled (default false). Isolation invariants are enforced
    and documented in-file: distinct database, distinct AUTHENTIK_SECRET_KEY,
    distinct Redis logical DB (sessions/cache are stored under non-namespaced
    keys), distinct cookie domain, and a separate blueprint mount.
  - templates/authentik-mendys-blueprints.yaml — ConfigMap sourced from
    authentik/blueprints-mendys/ ONLY. Mounting the FuzeFront set here would
    recreate FuzeFront's flows/providers/applications inside this directory.
  - blueprints-mendys/provider-oidc-mendys-platform.yaml — the requested
    client: confidential, client_id mendys-platform-oidc-client, sub_mode
    user_email, include_claims_in_id_token, openid/email/profile scope
    mappings, an invalidation_flow, and strict redirect URIs for both
    live.mendysrobotics.com and marketplace.mendysrobotics.com
    /api/auth/oidc/callback. Application slug mendys-platform.
  - blueprints-mendys/{brand-mendys,flow-enrollment}.yaml — the silo's brand
    and a self-service enrollment flow mirroring FuzeFront's design.
  - blueprints-mendys/provider-oidc-mendys-datasets.yaml — the datasets
    provider's silo-side copy.
  - MENDYS_PLATFORM_CLIENT_SECRET plus the instance's own secret key,
    bootstrap credentials and DB password in the chart Secret and env wiring.

Datasets migration is deliberately staged, not switched: the FuzeFront-side
provider is left untouched and serving traffic. Retiring it before
MendysRobotics repoints its issuer would black out datasets sign-in, and
deleting the blueprint file alone would not remove the objects anyway — that
needs an explicit `state: absent` pass. Accounts do not migrate either.

Backing store is provisioned by FuzeInfra (serviceDatabases entry
`authentik-mendys`), not by hand.

Verified: helm lint clean; kubeconform strict 16/16 valid (1 skip = Traefik
CRD); every mendys blueprint parses with authentik's !Env/!Find tags; the
FuzeFront render is byte-identical with the silo off, so enabling this cannot
disturb the live IdP.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session-Id: 55a394fa-d5ba-4da4-b41f-b0ef56fa6ddf
@github-actions

Copy link
Copy Markdown
Contributor

Automated code review (gate-code-review)

Looking at this PR — it's almost entirely declarative Helm/YAML and Authentik blueprints. I checked the template conditionals, secret gating, !Find references, env wiring, and resource limits for anything that would actually break at runtime.

  • deploy/helm/fuzefront/templates/authentik-mendys.yaml:311 — The worker's memory limits is 768Mi, but the sibling comment on the server (:305) states authentik 2026.5 on Python 3.14 OOMKilled at 1Gi and mandates ≥2Gi. This is a fresh instance: on first boot the worker runs the full blueprint application (and Celery migration tasks) against an empty DB, so a 768Mi ceiling is a plausible OOMKill that would leave the silo's providers/flows never created — the pod restarts before finishing the apply.

  • deploy/helm/fuzefront/authentik/blueprints-mendys/provider-oidc-mendys-platform.yaml:130 (and provider-oidc-mendys-datasets.yaml:146) — The provider entry does invalidation_flow: !Find [authentik_flows.flow, [slug, default-provider-invalidation-flow]], a reference to a system-blueprint object. This directly contradicts the file's own DESIGN header ("no !Find references to system-blueprint objects … would silently abort the entry if that object isn't yet in the DB"). If the system default flows haven't been committed when this blueprint runs on a brand-new database, the !Find resolves to nothing and the provider entry is silently skipped — the same swallowed-failure mode the comment says already caused "the first apply of this blueprint errored and created nothing." (Plausible rather than confirmed: system blueprints are normally applied early on startup, and the identical FuzeFront provider uses this pattern successfully.)

I did not find any confirmed logic bug (wrong condition, null deref, off-by-one) in the template guards — the enabled/ingress.enabled/smtp.host conditionals, secret gating, and optional: true secretKeyRefs are all consistent, and the SMTP useTls/useSSL/timeout value keys match values.yaml.

Report-only — this check never blocks merge.

@izzywdev
izzywdev merged commit 705f1bf into master Jul 28, 2026
52 checks passed
@izzywdev
izzywdev deleted the feat/mendys-authentik-tenant branch July 28, 2026 10:24
izzywdev added a commit that referenced this pull request Jul 28, 2026
…ret.yaml (#439)

templates/authentik-mendys.yaml mounts AUTHENTIK_MENDYS_SECRET_KEY,
AUTHENTIK_MENDYS_DB_PASSWORD, AUTHENTIK_MENDYS_BOOTSTRAP_PASSWORD and
AUTHENTIK_MENDYS_BOOTSTRAP_TOKEN as NON-OPTIONAL secretKeyRefs, but the block
in secret.yaml that creates them is no longer on master. Rendering the chart
with authentikMendys.enabled=true therefore produces 8 unresolved non-optional
secret references (4 keys x server + worker), and both pods would fail with
CreateContainerConfigError.

The keys were present in the change that added the instance (#428). values.yaml
still carries both the authentikMendys block and all four secret.authentikMendys*
values, and the instance template is intact — only this stanza went missing, and
the surrounding client-secret comments were rewritten, which points at a
conflict resolution over that region of the file rather than an intentional
removal.

Latent rather than live: the gate is off, so nothing is deployed and nothing is
currently broken. It would have failed at the moment someone first enabled the
silo, which is the worst time to discover it.

Adds a note tying the two files together so they are added and removed as a
pair.

Verified by cross-checking every secretKeyRef in the rendered chart against the
keys the chart actually creates: 8 unresolved non-optional refs before, 0 after.


Claude-Session-Id: 55a394fa-d5ba-4da4-b41f-b0ef56fa6ddf

Co-authored-by: fuzeone-bot[bot] <fuzeone-bot[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
@izzywdev
izzywdev restored the feat/mendys-authentik-tenant branch July 29, 2026 05:10
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.

1 participant