Gated substrate (Cl2-4 / Cl-infra-3) — outside apps/ so the ApplicationSet will not deploy it, and spec.suspend: true. Reconciles the 1Password tenant-mapping doc into Authentik groups via Connect REST. Activate at Au-3 public-go (un-suspend + materialize the script ConfigMap). Pairs Codex Cx2-7. Canonical script: FlowerCore.Notes/scripts/authentik/authentik-tenant-mapping-sync.py. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
authentik-tenant-mapping-sync — GATED manifest staging
Status: GATED (suspended). ADR: ADR-198 §2.A P1 (Au-1 / Au-3 substrate). Pairs: Codex Cx2-7.
This directory is a Notes staging area, NOT a deploy target. The orchestrator relocates
cronjob.yaml into a gated/ path outside bluejay-infra/apps/ so ArgoCD's apps/*
directory generator never picks it up. Nothing here runs until the activation steps below.
What this is
A nightly Kubernetes CronJob that runs
scripts/authentik/authentik-tenant-mapping-sync.py
(Notes repo). The script:
- reads the 1Password Document
flowercore-tenant-mapping(vaultIAmWorkin, fieldmapping) via 1Password Connect REST — never the 1Password CLI/desktop (operator hard rule); - parses + light-validates the mapping JSON (schema:
authentik-oidc-tenant-mapping-schema.md—version==1,mappings[]withauthentikGroup/fcTenantId/fcRole); - reconciles each distinct
authentikGroupinto Authentik/api/v3/core/groups/: create-if-missing, PATCH-managed-markers-on-drift, never delete or disable unmanaged groups; - emits structured (Serilog-shaped JSON) logs and exits 0 on success.
It is the slow nightly fix-up path. The <1s hot path stays the MCP tool
authentik_sync_tenant_mapping (schema doc §6.2 force-broadcast). This CronJob does NOT
broadcast SignalR — group reconcile is its only side effect; services pick up mapping changes
on their own 5-minute 1P refresh.
Why it is GATED (two locks)
spec.suspend: trueincronjob.yaml— belt-and-suspenders so even if applied it never fires.- Lives outside
apps/— staged here in Notes; ArgoCD does not manage it.
Both must be cleared to go live. This pairs Codex Cx2-7: do not activate ahead of the Au-3 public-go for tenant self-registration.
Files
| File | Purpose |
|---|---|
cronjob.yaml |
The suspended CronJob + the script-delivery ConfigMap (placeholder body). |
README.md |
This file. |
scripts/authentik/authentik-tenant-mapping-sync.py |
The reconcile script (canonical source; NOT in this dir). |
Secrets (referenced, not invented)
No secret values appear in cronjob.yaml — only secretKeyRefs:
AUTHENTIK_TOKEN←Secret authentik/authentik-credentialskeyBOOTSTRAP_ADMIN_TOKEN(already exists; the same tokenprovision-oidc-client.pyreads). Au-9 caveat: this is the never-rotated bootstrap token — when/rotate-password rotate authentik(Au-9) lands, this CronJob is one of its fan-out consumers.OP_TOKEN←Secret authentik/tenant-mapping-sync-op-tokenkeytoken.
OP_TOKEN cross-namespace
The canonical 1P Connect token Secret is onepassword-system/onepassword-token, but this
CronJob runs in the authentik namespace and K8s Secrets are namespace-scoped. Pick one at
activation:
- Option A (copy, simplest). Mint a same-namespace copy right before un-suspending:
(Re-run whenever the Connect token rotates — add this CronJob to the Au-10 Connect-token fan-out checklist so the copy can't go stale.)
kubectl get secret onepassword-token -n onepassword-system -o jsonpath='{.data.token}' \ | base64 -d \ | kubectl create secret generic tenant-mapping-sync-op-token -n authentik \ --from-file=token=/dev/stdin --dry-run=client -o yaml | kubectl apply -f - - Option B (CRD, preferred long-term). Use an
OnePasswordItemCRD (feedback_1password_operator_pattern) so the 1P operator mints/refreshesauthentik/tenant-mapping-sync-op-tokenautomatically — no manual copy, rotation-safe.
If neither secret exists yet, that's fine while suspended — the job never schedules.
How to ACTIVATE (at Au-3 public-go)
- Pre-flight (workstation dry-run, writes nothing):
Confirm the planned create/update set matches the 1P mapping document.
export AUTHENTIK_TOKEN=... # or let it read authentik/authentik-credentials via kubectl export OP_TOKEN=... # or rely on credential-helper.sh get_op_token (fcadmin@noc1) python scripts/authentik/authentik-tenant-mapping-sync.py --dry-run --verbose - Provide
OP_TOKENin-cluster — Option A or B above. - Materialize the script ConfigMap from the canonical file (do NOT hand-edit a copy into
cronjob.yaml— the embedded body is a deliberate placeholder):(Or, in the imaged future per ADR-198 §2.B P3, bake the script intokubectl create configmap authentik-tenant-mapping-sync-script -n authentik \ --from-file=authentik-tenant-mapping-sync.py=scripts/authentik/authentik-tenant-mapping-sync.py \ --dry-run=client -o yaml | kubectl apply -f -fc-runtime-baseand drop the ConfigMap volume.) - Relocate into bluejay-infra — move
cronjob.yamlinto agated/(orapps/) path inbluejay-infraper the orchestrator's placement decision. If underapps/, ArgoCD will sync it. - Un-suspend — set
spec.suspend: false(commit inbluejay-infraso ArgoCD selfHeal doesn't revert), or one-off:kubectl patch cronjob authentik-tenant-mapping-sync -n authentik \ -p '{"spec":{"suspend":false}}' - Smoke (VG-A1): trigger an immediate run and check the structured logs:
Then edit a mapping entry in 1P and confirm the next run reconciles the group; the <1s propagation still comes from the MCP
kubectl create job --from=cronjob/authentik-tenant-mapping-sync tms-smoke -n authentik kubectl logs -n authentik job/tms-smokeauthentik_sync_tenant_mappingforce-broadcast.
Rollback
Re-suspend (spec.suspend: true) or delete the CronJob. The script never deletes Authentik
groups, so a bad run can only over-create groups present in the mapping — remove any unwanted
group by hand in the Authentik admin UI. No data loss path.
Idempotency / safety summary
- Re-running is a no-op when groups already match (mirrors
provision-oidc-client.py). - Only the managed attribute block (
fc:managed-by/fc:tenant/fc:role/ optionalfc:label/fc:regulated/fc:strict-mode) is asserted; group parent/users/roles are never touched. - Wildcard SuperAdmin entries (
fcTenantId: "*") do not create a per-tenant group. --dry-runprints the plan and writes nothing — always run it first.
Cross-links
docs/standards/auth-acl-unattended-lifecycle-plan.md— ADR-198; Au-1/Au-3 lanes, VG-A1/A2.docs/standards/authentik-oidc-tenant-mapping-schema.md— the mapping JSON shape + 1P item layout (§2/§3).scripts/authentik/provision-oidc-client.py— sibling idempotent provisioner (same API + posture).scripts/credential-helper.sh—get_op_token1P Connect bootstrap (fcadmin@noc1).