From fa278674c106193acada6c6f9765810449e921d9 Mon Sep 17 00:00:00 2001 From: tegwick Date: Wed, 19 Aug 2026 12:31:08 +0200 Subject: [PATCH] Pin caller-auth digest in warn on independently rollable overlay pins MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The sanctioned Helm chart could not promote ADR 0004 at all, and the emergency manifests selected enforce. That made a FLEX-WP-0011 apply either a no-op or a global 401. First production pin is now warn, per consumer, on CI digest sha256:138aa347… . Enforce stays a later per-consumer flip so USER-WP-0023-T03 can close without waiting on tenant-engine. --- charts/flex-auth/Chart.yaml | 2 +- charts/flex-auth/templates/_helpers.tpl | 15 ++++ charts/flex-auth/templates/deployment.yaml | 34 +++++++++ charts/flex-auth/templates/networkpolicy.yaml | 9 +++ charts/flex-auth/templates/rbac.yaml | 30 ++++++++ .../flex-auth/templates/serviceaccount.yaml | 9 +++ charts/flex-auth/values.yaml | 12 +++ deploy/README.md | 12 ++- deploy/flex-auth-tenant-engine.yaml | 4 +- deploy/flex-auth-user-engine.yaml | 4 +- .../adr/0004-inbound-caller-authentication.md | 8 +- docs/tenancy-posture-review.md | 2 +- railiance/README.md | 32 +++++++- tenancy.yaml | 12 +-- tests/stage1.sh | 17 +++++ tools/verify-posture.sh | 24 +++++- values/stage1.yaml | 2 +- values/stage2-canary.yaml | 2 +- values/stage3-production.yaml | 2 +- values/tenant-engine.yaml | 8 +- values/user-engine.yaml | 8 +- ...LEX-WP-0015-tenancy-posture-conformance.md | 75 +++++++++++-------- 22 files changed, 268 insertions(+), 55 deletions(-) create mode 100644 charts/flex-auth/templates/rbac.yaml create mode 100644 charts/flex-auth/templates/serviceaccount.yaml diff --git a/charts/flex-auth/Chart.yaml b/charts/flex-auth/Chart.yaml index f59be21..1c8cd82 100644 --- a/charts/flex-auth/Chart.yaml +++ b/charts/flex-auth/Chart.yaml @@ -2,5 +2,5 @@ apiVersion: v2 name: flex-auth description: Independently rollable flex-auth policy-decision Deployment (one consumer per release). type: application -version: 0.1.0 +version: 0.2.0 appVersion: "0.1.0" diff --git a/charts/flex-auth/templates/_helpers.tpl b/charts/flex-auth/templates/_helpers.tpl index 4335226..6146953 100644 --- a/charts/flex-auth/templates/_helpers.tpl +++ b/charts/flex-auth/templates/_helpers.tpl @@ -18,3 +18,18 @@ app.kubernetes.io/name: {{ include "flex-auth.name" . }} app.kubernetes.io/instance: {{ .Release.Name }} app.kubernetes.io/part-of: flex-auth {{- end -}} + +{{- define "flex-auth.callerAuth.mode" -}} +{{- $mode := "disabled" -}} +{{- if and (hasKey .Values "callerAuth") .Values.callerAuth (hasKey .Values.callerAuth "mode") .Values.callerAuth.mode -}} +{{- $mode = .Values.callerAuth.mode -}} +{{- end -}} +{{- if not (has $mode (list "disabled" "warn" "enforce")) -}} +{{- fail (printf "callerAuth.mode must be disabled, warn, or enforce; got %q" $mode) -}} +{{- end -}} +{{- $mode -}} +{{- end -}} + +{{- define "flex-auth.callerAuth.enabled" -}} +{{- if has (include "flex-auth.callerAuth.mode" .) (list "warn" "enforce") -}}true{{- else -}}false{{- end -}} +{{- end -}} diff --git a/charts/flex-auth/templates/deployment.yaml b/charts/flex-auth/templates/deployment.yaml index b127df6..0b414c9 100644 --- a/charts/flex-auth/templates/deployment.yaml +++ b/charts/flex-auth/templates/deployment.yaml @@ -15,6 +15,9 @@ spec: {{- include "flex-auth.labels" . | nindent 8 }} spec: automountServiceAccountToken: false + {{- if eq (include "flex-auth.callerAuth.enabled" .) "true" }} + serviceAccountName: {{ include "flex-auth.name" . }} + {{- end }} securityContext: runAsNonRoot: true seccompProfile: @@ -25,6 +28,15 @@ spec: imagePullPolicy: {{ .Values.image.pullPolicy }} args: {{- toYaml .Values.args | nindent 12 }} + {{- if eq (include "flex-auth.callerAuth.enabled" .) "true" }} + {{- $binding := required "callerAuth.binding is required when callerAuth.mode is warn or enforce" .Values.callerAuth.binding }} + - --caller-auth-mode + - {{ include "flex-auth.callerAuth.mode" . | quote }} + - --caller-kubernetes-url + - {{ required "callerAuth.kubernetesURL is required when callerAuth.mode is warn or enforce" .Values.callerAuth.kubernetesURL | quote }} + - --caller-binding + - {{ $binding | quote }} + {{- end }} ports: - name: http containerPort: {{ .Values.service.port }} @@ -46,3 +58,25 @@ spec: drop: - ALL readOnlyRootFilesystem: true + {{- if eq (include "flex-auth.callerAuth.enabled" .) "true" }} + volumeMounts: + - mountPath: {{ .Values.callerAuth.reviewer.mountPath }} + name: flex-auth-reviewer + readOnly: true + {{- end }} + {{- if eq (include "flex-auth.callerAuth.enabled" .) "true" }} + volumes: + - name: flex-auth-reviewer + projected: + defaultMode: 0440 + sources: + - serviceAccountToken: + audience: {{ .Values.callerAuth.reviewer.audience | quote }} + expirationSeconds: {{ .Values.callerAuth.reviewer.expirationSeconds }} + path: token + - configMap: + name: kube-root-ca.crt + items: + - key: ca.crt + path: ca.crt + {{- end }} diff --git a/charts/flex-auth/templates/networkpolicy.yaml b/charts/flex-auth/templates/networkpolicy.yaml index 6960030..88b63f1 100644 --- a/charts/flex-auth/templates/networkpolicy.yaml +++ b/charts/flex-auth/templates/networkpolicy.yaml @@ -11,7 +11,16 @@ spec: policyTypes: - Ingress - Egress + {{- if eq (include "flex-auth.callerAuth.enabled" .) "true" }} + egress: + - ports: + - port: 443 + protocol: TCP + - port: 6443 + protocol: TCP + {{- else }} egress: [] + {{- end }} {{- if .Values.consumer.isolated }} ingress: [] {{- else }} diff --git a/charts/flex-auth/templates/rbac.yaml b/charts/flex-auth/templates/rbac.yaml new file mode 100644 index 0000000..ac2b8b1 --- /dev/null +++ b/charts/flex-auth/templates/rbac.yaml @@ -0,0 +1,30 @@ +{{- if eq (include "flex-auth.callerAuth.enabled" .) "true" }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ include "flex-auth.name" . }}-tokenreviewer + labels: + {{- include "flex-auth.labels" . | nindent 4 }} +rules: + - apiGroups: + - authentication.k8s.io + resources: + - tokenreviews + verbs: + - create +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ include "flex-auth.name" . }}-tokenreviewer + labels: + {{- include "flex-auth.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ include "flex-auth.name" . }}-tokenreviewer +subjects: + - kind: ServiceAccount + name: {{ include "flex-auth.name" . }} + namespace: {{ .Release.Namespace }} +{{- end }} diff --git a/charts/flex-auth/templates/serviceaccount.yaml b/charts/flex-auth/templates/serviceaccount.yaml new file mode 100644 index 0000000..2cab59f --- /dev/null +++ b/charts/flex-auth/templates/serviceaccount.yaml @@ -0,0 +1,9 @@ +{{- if eq (include "flex-auth.callerAuth.enabled" .) "true" }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "flex-auth.name" . }} + labels: + {{- include "flex-auth.labels" . | nindent 4 }} +automountServiceAccountToken: false +{{- end }} diff --git a/charts/flex-auth/values.yaml b/charts/flex-auth/values.yaml index 7c3dd53..d1b3c66 100644 --- a/charts/flex-auth/values.yaml +++ b/charts/flex-auth/values.yaml @@ -29,6 +29,18 @@ consumer: namespace: "" podName: "" +# disabled keeps the image bootable without TokenReview (isolated canary). +# Production pins set warn, then enforce, independently per consumer. +callerAuth: + mode: disabled + audience: flex-auth + kubernetesURL: https://kubernetes.default.svc + binding: "" + reviewer: + mountPath: /var/run/secrets/flex-auth-reviewer + audience: https://kubernetes.default.svc + expirationSeconds: 3600 + resources: requests: cpu: 25m diff --git a/deploy/README.md b/deploy/README.md index 63039d1..b70b84b 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -7,6 +7,12 @@ emergency kubectl path recovered on 2026-08-11 from live `last-applied-configuration`, kept so a rollback does not depend on a cluster annotation or on Helm history. +Caller authentication (FLEX-WP-0015-T02) is pinned here in **`warn`**, +matching `values/user-engine.yaml` and `values/tenant-engine.yaml`. Do not +edit these files back to `enforce` and apply them as a shortcut: that is +the hazard. Flip `callerAuth.mode` per consumer in the overlay after that +consumer's warn logs are clean, then re-render or edit only that pin. + Manifests for the two cluster-local flex-auth policy-decision services. | File | Deployment | Consumer | Service DNS | @@ -86,10 +92,12 @@ last-known-good digest below. | Deployment | Last-known-good digest | Policy state | | --- | --- | --- | -| `flex-auth-tenant-engine` | `sha256:1bf060e61122693ce98359c167cc5fe8bdafc84e097e090eaa71af94d0f27cbc` | **live** — nine-action policy (FLEX-WP-0014), CI-built from `f304688` | +| `flex-auth-tenant-engine` | `sha256:138aa3471c46bca6e814691fa1e6520aedda3dffd743e6b09141ab433afdb64b` | **pin, not live** — caller-auth **warn** (FLEX-WP-0015-T02), CI `main-3de72fe` | +| `flex-auth-tenant-engine` *(live until warn promote)* | `sha256:1bf060e61122693ce98359c167cc5fe8bdafc84e097e090eaa71af94d0f27cbc` | nine-action policy (FLEX-WP-0014), CI-built from `f304688` | | `flex-auth-tenant-engine` *(previous)* | `sha256:9320df394a642eff24da8af4a0ee8886a7bb78b0f14d8ee1deeb30ea8eeeaba7` | seven-action policy, FLEX-WP-0013 restore; guardrail actions deny `unknown_action` | | `flex-auth-tenant-engine` *(rollback)* | `sha256:c25fc34a6cd7e64d955f8723ec70e176a583d5ae71d76280c4e2d89fba0fe0aa` | four-action policy; lifecycle actions deny `unknown_action` | -| `flex-auth-user-engine` | `sha256:1f5290376dc5fcf456dc7a785e394d8b90949dabecd1d3e856f38557149bb5f4` | **current** — FLEX-WP-0009-T04, nine fixtures (incl. registration-applicant) verified live 2026-08-16 | +| `flex-auth-user-engine` | `sha256:138aa3471c46bca6e814691fa1e6520aedda3dffd743e6b09141ab433afdb64b` | **pin, not live** — caller-auth **warn** (FLEX-WP-0015-T02), CI `main-3de72fe` | +| `flex-auth-user-engine` *(live until warn promote)* | `sha256:1f5290376dc5fcf456dc7a785e394d8b90949dabecd1d3e856f38557149bb5f4` | FLEX-WP-0009-T04, nine fixtures (incl. registration-applicant) verified live 2026-08-16 | | `flex-auth-user-engine` *(previous)* | `sha256:a31961c45215aa6baf3bc748c6741ab703c2c8325e61aa7983a355026195e51b` | FLEX-WP-0009-T03, six fixtures verified live 2026-08-10 | Rolling back the tenant-engine service to `c25fc34a…` restores fail-closed diff --git a/deploy/flex-auth-tenant-engine.yaml b/deploy/flex-auth-tenant-engine.yaml index b1e0f64..974abe5 100644 --- a/deploy/flex-auth-tenant-engine.yaml +++ b/deploy/flex-auth-tenant-engine.yaml @@ -25,12 +25,12 @@ spec: - --policy - /opt/flex-auth/examples/tenant-engine/policy_package.md - --caller-auth-mode - - enforce + - warn - --caller-kubernetes-url - https://10.43.0.1 - --caller-binding - tenant-engine=system:serviceaccount:tenant-engine:tenant-engine - image: forgejo.coulomb.social/coulomb/flex-auth@sha256:1bf060e61122693ce98359c167cc5fe8bdafc84e097e090eaa71af94d0f27cbc + image: forgejo.coulomb.social/coulomb/flex-auth@sha256:138aa3471c46bca6e814691fa1e6520aedda3dffd743e6b09141ab433afdb64b livenessProbe: httpGet: path: /healthz diff --git a/deploy/flex-auth-user-engine.yaml b/deploy/flex-auth-user-engine.yaml index da6595d..9ab46fc 100644 --- a/deploy/flex-auth-user-engine.yaml +++ b/deploy/flex-auth-user-engine.yaml @@ -25,12 +25,12 @@ spec: - --policy - /opt/flex-auth/examples/user-engine/policy_package.md - --caller-auth-mode - - enforce + - warn - --caller-kubernetes-url - https://10.43.0.1 - --caller-binding - user-engine=system:serviceaccount:user-engine:user-engine - image: forgejo.coulomb.social/coulomb/flex-auth@sha256:1f5290376dc5fcf456dc7a785e394d8b90949dabecd1d3e856f38557149bb5f4 + image: forgejo.coulomb.social/coulomb/flex-auth@sha256:138aa3471c46bca6e814691fa1e6520aedda3dffd743e6b09141ab433afdb64b livenessProbe: httpGet: path: /healthz diff --git a/docs/adr/0004-inbound-caller-authentication.md b/docs/adr/0004-inbound-caller-authentication.md index 92b87ae..18f36aa 100644 --- a/docs/adr/0004-inbound-caller-authentication.md +++ b/docs/adr/0004-inbound-caller-authentication.md @@ -38,9 +38,11 @@ cached across rotation. Three modes support promotion: `disabled`, `warn`, and `enforce`. Warn mode records the same authentication failures without logging credentials. It is a -bounded migration aid, not a conformant steady state. The reviewed desired -manifests select `enforce`; promotion still follows FLEX-WP-0011 and requires a -new immutable image digest plus caller rollout evidence. +bounded migration aid, not a conformant steady state. The first production pin +is `warn` on each independently rollable Deployment; `enforce` is the end +state and is flipped per consumer after that consumer's warn logs are clean. +Promotion follows FLEX-WP-0011. The two production pins must not be flipped +together: user-engine can enforce while tenant-engine stays in warn. ## Rejected alternatives diff --git a/docs/tenancy-posture-review.md b/docs/tenancy-posture-review.md index 8919d28..b11957d 100644 --- a/docs/tenancy-posture-review.md +++ b/docs/tenancy-posture-review.md @@ -271,7 +271,7 @@ volunteer. | Task | | |---|---| | T01 | Publish the posture vector and this review; reply to `rapp-postgres` | -| T02 | **Close the A0**: TokenReview source/desired state done; immutable promotion pending | +| T02 | **Close the A0**: TokenReview source + warn-first overlay pin done; operator promote-then-per-consumer-enforce pending | | T03 | Deleted the unused tenant-engine live-roles adapter | | T04 | AuthZEN endpoint — `wait`, with a written trigger | | T05 | `make verify-posture` guards declaration/source/deployment drift | diff --git a/railiance/README.md b/railiance/README.md index 5663694..465f9a3 100644 --- a/railiance/README.md +++ b/railiance/README.md @@ -45,6 +45,32 @@ $RAILIANCE rollback . --apply --approval-id --revision The canary Service is `flex-auth-canary.flex-auth.svc.cluster.local:8080`. Production consumers keep calling their own Service names. +## Caller-auth promotion (FLEX-WP-0015-T02) + +The two production pins are independently rollable. Do not flip both to +`enforce` in one apply. The first pin of digest +`sha256:138aa3471c46bca6e814691fa1e6520aedda3dffd743e6b09141ab433afdb64b` +is **warn** on each consumer. + +```bash +# Warn pin (safe if a caller still sends no token) +helm upgrade --install flex-auth-user-engine charts/flex-auth \ + --namespace flex-auth -f values/user-engine.yaml --wait --timeout 2m +helm upgrade --install flex-auth-tenant-engine charts/flex-auth \ + --namespace flex-auth -f values/tenant-engine.yaml --wait --timeout 2m + +# Confirm warn logs: "caller authentication warning:" must be absent for a +# migrated caller. user-engine is migrated; tenant-engine live status is +# unconfirmed. Isolated canary keeps callerAuth.mode=disabled. + +# Then set values/user-engine.yaml callerAuth.mode: enforce and upgrade only +# that release. USER-WP-0023-T03 runs only after that flip. tenant-engine +# stays warn until its logs are clean. +``` + +Emergency kubectl path is also warn. Applying it in enforce is the hazard +the warn pin exists to prevent. + ## Rolling a production pin Policy is baked into the image. Do not build images on a workstation. @@ -76,8 +102,10 @@ or re-apply the last-known-good digest in `deploy/README.md`. | Deployment | Last-known-good digest | Policy state | | --- | --- | --- | -| `flex-auth-tenant-engine` | `sha256:1bf060e61122693ce98359c167cc5fe8bdafc84e097e090eaa71af94d0f27cbc` | **live** — nine-action policy (FLEX-WP-0014), CI-built from `f304688` | +| `flex-auth-tenant-engine` | `sha256:138aa3471c46bca6e814691fa1e6520aedda3dffd743e6b09141ab433afdb64b` | **pin, not live** — caller-auth warn (FLEX-WP-0015-T02), CI `main-3de72fe` | +| `flex-auth-tenant-engine` *(live until warn promote)* | `sha256:1bf060e61122693ce98359c167cc5fe8bdafc84e097e090eaa71af94d0f27cbc` | nine-action policy (FLEX-WP-0014), CI-built from `f304688` | | `flex-auth-tenant-engine` *(previous)* | `sha256:9320df394a642eff24da8af4a0ee8886a7bb78b0f14d8ee1deeb30ea8eeeaba7` | seven-action policy, FLEX-WP-0013 restore | | `flex-auth-tenant-engine` *(rollback)* | `sha256:c25fc34a6cd7e64d955f8723ec70e176a583d5ae71d76280c4e2d89fba0fe0aa` | four-action policy; lifecycle actions deny `unknown_action` | -| `flex-auth-user-engine` | `sha256:1f5290376dc5fcf456dc7a785e394d8b90949dabecd1d3e856f38557149bb5f4` | FLEX-WP-0009-T04, nine fixtures, live 2026-08-16 | +| `flex-auth-user-engine` | `sha256:138aa3471c46bca6e814691fa1e6520aedda3dffd743e6b09141ab433afdb64b` | **pin, not live** — caller-auth warn (FLEX-WP-0015-T02), CI `main-3de72fe` | +| `flex-auth-user-engine` *(live until warn promote)* | `sha256:1f5290376dc5fcf456dc7a785e394d8b90949dabecd1d3e856f38557149bb5f4` | FLEX-WP-0009-T04, nine fixtures, live 2026-08-16 | | `flex-auth-user-engine` *(previous)* | `sha256:a31961c45215aa6baf3bc748c6741ab703c2c8325e61aa7983a355026195e51b` | FLEX-WP-0009-T03, six fixtures | diff --git a/tenancy.yaml b/tenancy.yaml index 8b337cc..c6705dd 100644 --- a/tenancy.yaml +++ b/tenancy.yaml @@ -40,10 +40,11 @@ tenancy: own inputs. Not a defect and not a target for movement. A: >- The running immutable digest still authenticates no caller, so current - remains A0. Source and reviewed desired manifests implement A2 with an - audience-scoped Kubernetes TokenReview choke point and exact - protected-system-to-ServiceAccount bindings. Promotion and a live - unbound-request probe remain under FLEX-WP-0011/FLEX-WP-0015-T02. + remains A0. Source, overlay, and reviewed desired manifests implement + A2 with an audience-scoped Kubernetes TokenReview choke point and exact + protected-system-to-ServiceAccount bindings. The first production pin + is warn, independently per consumer, under FLEX-WP-0011/FLEX-WP-0015-T02. + Current moves to A2 only after enforce and a live unbound-request probe. E: >- No tenant data at rest. Tenant scoping in decisions runs through one choke point (internal/decision/engine.go normalizeRequest and the @@ -84,6 +85,7 @@ evidence: - "internal/callerauth/auth.go" - "internal/callerauth/auth_test.go" - "cmd/flex-auth/main_test.go" + - "charts/flex-auth/templates/rbac.yaml" - "deploy/caller-auth-rbac.yaml" - "docs/adr/0004-inbound-caller-authentication.md" - deployment: "deploy/flex-auth-user-engine.yaml, deploy/flex-auth-tenant-engine.yaml" + deployment: "values/user-engine.yaml, values/tenant-engine.yaml, deploy/flex-auth-user-engine.yaml, deploy/flex-auth-tenant-engine.yaml" diff --git a/tests/stage1.sh b/tests/stage1.sh index 33234eb..c9bc7e5 100755 --- a/tests/stage1.sh +++ b/tests/stage1.sh @@ -12,4 +12,21 @@ for values in values/stage1.yaml values/stage2-canary.yaml values/stage3-product helm template flex-auth charts/flex-auth -f "${values}" --namespace flex-auth >/dev/null done +user_render="$(helm template flex-auth-user-engine charts/flex-auth -f values/user-engine.yaml --namespace flex-auth)" +echo "$user_render" | grep -q -- '--caller-auth-mode' || { echo "user-engine render omits caller-auth-mode" >&2; exit 1; } +echo "$user_render" | grep -A1 -- '--caller-auth-mode' | grep -q warn || { echo "user-engine first pin must be warn" >&2; exit 1; } +echo "$user_render" | grep -q tokenreviews || { echo "user-engine render omits TokenReview RBAC" >&2; exit 1; } +echo "$user_render" | grep -q flex-auth-reviewer || { echo "user-engine render omits reviewer projection" >&2; exit 1; } + +tenant_render="$(helm template flex-auth-tenant-engine charts/flex-auth -f values/tenant-engine.yaml --namespace flex-auth)" +echo "$tenant_render" | grep -A1 -- '--caller-auth-mode' | grep -q warn || { echo "tenant-engine first pin must be warn" >&2; exit 1; } +echo "$tenant_render" | grep -q 'tenant-engine=system:serviceaccount:tenant-engine:tenant-engine' \ + || { echo "tenant-engine render omits its exact binding" >&2; exit 1; } + +canary_render="$(helm template flex-auth-canary charts/flex-auth -f values/stage2-canary.yaml --namespace flex-auth)" +if echo "$canary_render" | grep -q tokenreviews; then + echo "isolated canary must not create TokenReview RBAC" >&2 + exit 1 +fi + echo "stage1 overlay render ok" diff --git a/tools/verify-posture.sh b/tools/verify-posture.sh index 1333d2e..cd81b6a 100644 --- a/tools/verify-posture.sh +++ b/tools/verify-posture.sh @@ -26,14 +26,36 @@ fail() { exit 1 } +caller_mode_from_manifest() { + awk ' + $0 ~ /--caller-auth-mode/ { getline; gsub(/^[[:space:]-]+/, ""); print; exit } + ' "$1" +} + +caller_mode_from_values() { + awk ' + $1 == "callerAuth:" { in_block=1; next } + in_block && /^[^[:space:]#]/ { exit } + in_block && $1 == "mode:" { print $2; exit } + ' "$1" +} + if grep -q 'authenticator.Authorize' "$main"; then [[ "${declared_a:-0}" -ge 2 ]] || fail "caller authentication exists but current/implemented A is below 2" for manifest in deploy/flex-auth-user-engine.yaml deploy/flex-auth-tenant-engine.yaml; do grep -q -- '--caller-auth-mode' "$root/$manifest" || fail "$manifest omits caller auth mode" - grep -q 'enforce' "$root/$manifest" || fail "$manifest does not select enforce mode" + mode="$(caller_mode_from_manifest "$root/$manifest")" + [[ "$mode" == "warn" || "$mode" == "enforce" ]] || fail "$manifest caller-auth-mode is ${mode:-empty}, not warn or enforce" grep -q -- '--caller-binding' "$root/$manifest" || fail "$manifest omits the exact system binding" + grep -q 'flex-auth-reviewer' "$root/$manifest" || fail "$manifest omits the reviewer token projection" done [[ -f "$root/deploy/caller-auth-rbac.yaml" ]] || fail "TokenReview RBAC manifest is absent" + grep -q 'tokenreviews' "$root/charts/flex-auth/templates/rbac.yaml" || fail "overlay TokenReview RBAC template is absent" + for values in values/user-engine.yaml values/tenant-engine.yaml; do + mode="$(caller_mode_from_values "$root/$values")" + [[ "$mode" == "warn" || "$mode" == "enforce" ]] || fail "$values callerAuth.mode is ${mode:-empty}, not warn or enforce" + grep -q 'binding:' "$root/$values" || fail "$values omits callerAuth.binding" + done else [[ "${declared_a:-0}" -lt 2 ]] || fail "A2 is declared without a caller-authentication choke point" fi diff --git a/values/stage1.yaml b/values/stage1.yaml index 006215e..6037332 100644 --- a/values/stage1.yaml +++ b/values/stage1.yaml @@ -2,7 +2,7 @@ name: flex-auth-local image: repository: forgejo.coulomb.social/coulomb/flex-auth - digest: sha256:1f5290376dc5fcf456dc7a785e394d8b90949dabecd1d3e856f38557149bb5f4 + digest: sha256:138aa3471c46bca6e814691fa1e6520aedda3dffd743e6b09141ab433afdb64b args: - serve - --addr diff --git a/values/stage2-canary.yaml b/values/stage2-canary.yaml index 9e75462..d19eea9 100644 --- a/values/stage2-canary.yaml +++ b/values/stage2-canary.yaml @@ -3,7 +3,7 @@ name: flex-auth-canary image: repository: forgejo.coulomb.social/coulomb/flex-auth - digest: sha256:1f5290376dc5fcf456dc7a785e394d8b90949dabecd1d3e856f38557149bb5f4 + digest: sha256:138aa3471c46bca6e814691fa1e6520aedda3dffd743e6b09141ab433afdb64b args: - serve - --addr diff --git a/values/stage3-production.yaml b/values/stage3-production.yaml index 6665da2..edbfd4e 100644 --- a/values/stage3-production.yaml +++ b/values/stage3-production.yaml @@ -5,7 +5,7 @@ name: flex-auth-canary image: repository: forgejo.coulomb.social/coulomb/flex-auth - digest: sha256:1f5290376dc5fcf456dc7a785e394d8b90949dabecd1d3e856f38557149bb5f4 + digest: sha256:138aa3471c46bca6e814691fa1e6520aedda3dffd743e6b09141ab433afdb64b args: - serve - --addr diff --git a/values/tenant-engine.yaml b/values/tenant-engine.yaml index 5ecac8d..a8ecb45 100644 --- a/values/tenant-engine.yaml +++ b/values/tenant-engine.yaml @@ -1,8 +1,10 @@ # Production pin for the tenant-engine policy service. Independently rollable. +# Warn is safe whether or not the live tenant-engine client already sends a +# token. Do not flip this pin to enforce until its warn logs are clean. name: flex-auth-tenant-engine image: repository: forgejo.coulomb.social/coulomb/flex-auth - digest: sha256:1bf060e61122693ce98359c167cc5fe8bdafc84e097e090eaa71af94d0f27cbc + digest: sha256:138aa3471c46bca6e814691fa1e6520aedda3dffd743e6b09141ab433afdb64b args: - serve - --addr @@ -11,6 +13,10 @@ args: - /opt/flex-auth/examples/tenant-engine/registry_snapshot.json - --policy - /opt/flex-auth/examples/tenant-engine/policy_package.md +callerAuth: + mode: warn + kubernetesURL: https://10.43.0.1 + binding: tenant-engine=system:serviceaccount:tenant-engine:tenant-engine consumer: isolated: false namespace: tenant-engine diff --git a/values/user-engine.yaml b/values/user-engine.yaml index 51b9c52..21fdd96 100644 --- a/values/user-engine.yaml +++ b/values/user-engine.yaml @@ -1,8 +1,10 @@ # Production pin for the user-engine policy service. Independently rollable. +# First caller-auth pin is warn (FLEX-WP-0015-T02). Flip mode to enforce only +# after warn logs are clean of unauthenticated callers for this consumer. name: flex-auth-user-engine image: repository: forgejo.coulomb.social/coulomb/flex-auth - digest: sha256:1f5290376dc5fcf456dc7a785e394d8b90949dabecd1d3e856f38557149bb5f4 + digest: sha256:138aa3471c46bca6e814691fa1e6520aedda3dffd743e6b09141ab433afdb64b args: - serve - --addr @@ -11,6 +13,10 @@ args: - /opt/flex-auth/examples/user-engine/registry_snapshot.json - --policy - /opt/flex-auth/examples/user-engine/policy_package.md +callerAuth: + mode: warn + kubernetesURL: https://10.43.0.1 + binding: user-engine=system:serviceaccount:user-engine:user-engine consumer: isolated: false namespace: user-engine diff --git a/workplans/FLEX-WP-0015-tenancy-posture-conformance.md b/workplans/FLEX-WP-0015-tenancy-posture-conformance.md index 57fb81f..967eef0 100644 --- a/workplans/FLEX-WP-0015-tenancy-posture-conformance.md +++ b/workplans/FLEX-WP-0015-tenancy-posture-conformance.md @@ -14,7 +14,7 @@ related_workplans: - FLEX-WP-0004 - FLEX-WP-0011 created: "2026-08-17" -updated: "2026-08-18" +updated: "2026-08-19" state_hub_workstream_id: "31846b19-c2a3-428e-950b-5985bc9146eb" --- @@ -110,38 +110,50 @@ Ship through the FLEX-WP-0011 staged-promotion path, not by direct apply. ServiceAccount TokenReview with a separately projected reviewer identity and exact `resource.system` → ServiceAccount bindings. Both check endpoints share the choke point; missing/mismatched identity fails 401/403 and reviewer outage -fails 503. Unit and handler tests include the required unbound request. Desired -manifests select enforce mode and carry narrow TokenReview RBAC. The running -digest is unchanged, so `tenancy.current.A` honestly remains 0 while -`tenancy.implemented.A` is 2. Source committed as `1e1e077`. +fails 503. Unit and handler tests include the required unbound request. Source +committed as `1e1e077`. -**Remaining, in this order — the sequence is a constraint, not a preference.** -`ops-warden` ruled on rollout (2026-08-17) and `user-engine` asked for the same: +2026-08-19 overlay outcome: the sanctioned Helm chart did not render caller +auth at all, so a FLEX-WP-0011 pin of the new digest would have stayed +`disabled`. That is now wired. Desired emergency manifests and production +values select **`warn`**, not `enforce` — applying the previous enforce +manifests was the hazard. CI image `sha256:138aa3471c46bca6e814691fa1e6520aedda3dffd743e6b09141ab433afdb64b` +(`main-3de72fe`, built 2026-08-18T13:25Z) contains the TokenReview flags. +The running cluster digest is still unchanged, so `tenancy.current.A` +remains 0 while `tenancy.implemented.A` is 2. -1. Build and pin a new immutable digest carrying the caller-auth code. -2. Promote it in **`warn` mode**, not `enforce`. The reviewed desired manifests - in `deploy/` select `enforce` because that is the end state — applying them - directly is the hazard. Warn authenticates and logs failures without - rejecting, so it is safe for an unmigrated caller. -3. Confirm the warn logs are clean of unauthenticated callers. `user-engine` is - migrated and deployed as of 2026-08-18 (image `sha256:c501aeb2…`, token at - `/var/run/secrets/flex-auth-caller/token`). `tenant-engine`'s caller status - is **unconfirmed — asked 2026-08-18**; promoting `enforce` while their - client sends no token 401s every check and blocks their write paths. - `ops-warden` adopts the calling side on its own schedule. -4. Flip to `enforce`, then capture the live negative probe. `user-engine` has - the three assertions written and ready in their - `docs/flex-auth-caller-identity.md`; they deliberately have not run it, - because against an unenforced digest "no token returns 401" comes back as a - normal decision and would record as false evidence. -5. Only then may `policy.enabled` flip anywhere — `ops-warden` names that the - real deadline, and it is the same gate as FLEX-WP-0007. +**Better path than a global enforce flip.** The two production Deployments +are independently rollable (FLEX-WP-0011). USER-WP-0023-T03 only probes +`flex-auth-user-engine`. Serializing that probe on tenant-engine's caller +status would 401 tenant-engine writes if we enforced them together, and +would delay user-engine A2 evidence for no safety gain. Sequence: -**Operator gate.** Steps 1–4 need cluster credentials this session does not +1. Pin the caller-auth digest in overlay + emergency manifests — **done + 2026-08-19**, first mode `warn` on both consumers. +2. Operator: promote each pin in warn via `helm upgrade --install + flex-auth- charts/flex-auth --namespace flex-auth -f + values/.yaml`. Isolated canary may boot the same digest with + caller-auth disabled. Do not `kubectl apply` an enforce manifest. +3. Confirm warn logs per consumer. `user-engine` is migrated and deployed + as of 2026-08-18 (image `sha256:c501aeb2…`, token at + `/var/run/secrets/flex-auth-caller/token`). `tenant-engine` source and + desired manifests already project that token and send it; live digest + is still unconfirmed. Warn is safe either way. `ops-warden` adopts the + calling side on its own schedule. +4. Flip **user-engine only** to `enforce` (`callerAuth.mode: enforce` in + `values/user-engine.yaml`, same digest). Then capture the live negative + probe from a user-engine pod. The three assertions are already written + in user-engine `docs/flex-auth-caller-identity.md`; they must not run + against warn, because "no token returns 401" would still be a decision. +5. Flip tenant-engine to enforce only after its warn logs are clean. +6. Only then may `policy.enabled` flip anywhere — `ops-warden` names that + the real deadline, and it is the same gate as FLEX-WP-0007. + +**Operator gate.** Steps 2–5 need cluster credentials this session does not have: `kubectl` returns `Unauthorized` and the context is `default`, which `tenant-engine` documented on 2026-08-16 as indistinguishable from a -wrong-cluster KUBECONFIG. Promotion follows FLEX-WP-0011 staged promotion and -the CI image build, not a hand-built image. +wrong-cluster KUBECONFIG. Promotion follows FLEX-WP-0011 and the CI image, +not a hand-built image. `RISK-F-0001` (risk-nexus) tracks this A0 externally. Their NetworkPolicy question was answered 2026-08-18: both Deployments carry an ingress policy @@ -218,6 +230,7 @@ correct-looking responses, exactly like the two silent pin rollbacks the estate has already been bitten by. Completed 2026-08-18. `make verify-posture` checks the current/implemented -distinction, both authenticated handlers, enforce-mode desired manifests, -TokenReview RBAC, absence of a latent tenant-engine caller, and the stateless -deployment assumptions behind `R: n/a`. +distinction, both authenticated handlers, warn-or-enforce desired manifests +and overlay pins, TokenReview RBAC, absence of a latent tenant-engine caller, +and the stateless deployment assumptions behind `R: n/a`. First production +pin is warn; enforce is the end state, flipped per consumer.