# ===================================================================================== # authentik-tenant-mapping-sync — GATED nightly CronJob (Au-3 / ADR-198 §2.A P1) # # STATUS: GATED. spec.suspend: true (belt-and-suspenders). This manifest lives in a Notes # STAGING path (docs/gated-manifests/) and is NOT under bluejay-infra apps/, so ArgoCD # does not deploy it. It does NOTHING until Au-3 public-go (see README.md in this dir). # # WHAT IT RUNS: scripts/authentik/authentik-tenant-mapping-sync.py (Notes repo) — reads the # 1Password Document `flowercore-tenant-mapping` via Connect REST and reconciles its # mappings[].authentikGroup entries into Authentik groups (idempotent; never deletes # unmanaged groups). Pairs Codex Cx2-7. # # SECRETS (referenced, NOT invented — no secret VALUES in this file): # AUTHENTIK_TOKEN <- Secret authentik/authentik-credentials key BOOTSTRAP_ADMIN_TOKEN (exists) # OP_TOKEN <- Secret authentik/tenant-mapping-sync-op-token key token # (a copy of onepassword-system/onepassword-token — see README "OP_TOKEN # cross-namespace" for the one-liner that mints it; OR mint via the # OnePasswordItem CRD per feedback_1password_operator_pattern). # # The script is delivered via the ConfigMap below (same pattern as guacamole guac-k8s-sync). # When this lane is libraryized/imaged later (ADR-198 §2.B P3) this ConfigMap can be replaced # by a baked image; for now ConfigMap-delivery keeps the script the single source of truth. # ===================================================================================== apiVersion: batch/v1 kind: CronJob metadata: name: authentik-tenant-mapping-sync namespace: authentik labels: app.kubernetes.io/name: authentik-tenant-mapping-sync app.kubernetes.io/component: sync app.kubernetes.io/part-of: flowercore-identity flowercore.io/adr: "198" flowercore.io/gated: "true" annotations: flowercore.io/gate: "Au-3 public-go — suspended until tenant self-registration goes live" flowercore.io/pairs-with: "Codex Cx2-7" spec: # GATE: suspended so it never fires until an operator un-suspends at Au-3 public-go. suspend: true # Nightly at 03:17 (off-peak; jittered minute to avoid colliding with other 03:00 jobs). schedule: "17 3 * * *" concurrencyPolicy: Forbid startingDeadlineSeconds: 600 successfulJobsHistoryLimit: 3 failedJobsHistoryLimit: 3 jobTemplate: spec: backoffLimit: 2 activeDeadlineSeconds: 600 template: metadata: labels: app.kubernetes.io/name: authentik-tenant-mapping-sync app.kubernetes.io/component: sync spec: restartPolicy: OnFailure securityContext: runAsNonRoot: true runAsUser: 65532 runAsGroup: 65532 fsGroup: 65532 seccompProfile: type: RuntimeDefault containers: - name: sync # python:3.12-slim is sufficient: the script uses only the stdlib (urllib/json/ssl). # No pip install needed. Pin a digest at activation time for air-gap reproducibility. image: python:3.12-slim imagePullPolicy: IfNotPresent command: - python3 - /scripts/authentik-tenant-mapping-sync.py # NOTE: no --dry-run here -> this is the real reconcile. Operators wanting a # dry-run first should `kubectl create job --from=cronjob/... ` with the arg # appended, or run the script from a workstation. See README. env: - name: AUTHENTIK_URL value: "https://id.iamworkin.lan" - name: OP_CONNECT_URL value: "http://10.0.56.10:8180/v1" # port 8180, NOT 8443 - name: OP_VAULT_ID value: "qaphopopkryhbg353ukzhhuqoq" # IAmWorkin - name: TENANT_MAPPING_ITEM value: "flowercore-tenant-mapping" - name: TENANT_MAPPING_FIELD value: "mapping" - name: AUTHENTIK_TOKEN valueFrom: secretKeyRef: name: authentik-credentials key: BOOTSTRAP_ADMIN_TOKEN - name: OP_TOKEN valueFrom: secretKeyRef: # A same-namespace copy of onepassword-system/onepassword-token. # See README "OP_TOKEN cross-namespace". Until Au-3 this Secret need # not exist (the job is suspended). name: tenant-mapping-sync-op-token key: token resources: requests: cpu: 25m memory: 64Mi limits: cpu: 250m memory: 128Mi securityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true capabilities: drop: ["ALL"] volumeMounts: - name: script mountPath: /scripts readOnly: true volumes: - name: script configMap: name: authentik-tenant-mapping-sync-script defaultMode: 0555 --- # The reconcile script, delivered as a ConfigMap (single source of truth = the Notes repo # scripts/authentik/authentik-tenant-mapping-sync.py). At activation, regenerate this # ConfigMap from the live script so the two never drift, e.g.: # kubectl 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 > docs/gated-manifests/authentik-tenant-sync/configmap.script.yaml # (kept as a placeholder body here so the manifest set is self-describing; the real body is # the script file — DO NOT hand-edit a divergent copy into this ConfigMap.) apiVersion: v1 kind: ConfigMap metadata: name: authentik-tenant-mapping-sync-script namespace: authentik labels: app.kubernetes.io/name: authentik-tenant-mapping-sync app.kubernetes.io/component: sync flowercore.io/gated: "true" annotations: flowercore.io/source: "scripts/authentik/authentik-tenant-mapping-sync.py (Notes repo) — regenerate at activation, do not hand-edit" data: authentik-tenant-mapping-sync.py: | # PLACEHOLDER — regenerate from the canonical script at activation (see annotation above). # The Notes repo file scripts/authentik/authentik-tenant-mapping-sync.py is the source of # truth; embedding a hand-copy here would drift. The orchestrator (or the activation # runbook) materializes this ConfigMap from the live script via `kubectl create configmap # ... --from-file=...` before un-suspending the CronJob. import sys sys.exit("authentik-tenant-mapping-sync ConfigMap not materialized from the canonical " "script — regenerate with kubectl create configmap --from-file before activation.")