diff --git a/charts/flex-auth/templates/deployment.yaml b/charts/flex-auth/templates/deployment.yaml index 8bccd6c..a6a73c0 100644 --- a/charts/flex-auth/templates/deployment.yaml +++ b/charts/flex-auth/templates/deployment.yaml @@ -45,11 +45,19 @@ spec: - {{ required "outbox.source is required when outbox.enabled=true" .Values.outbox.source | quote }} {{- end }} {{- if eq (include "flex-auth.callerAuth.enabled" .) "true" }} - {{- $binding := required "callerAuth.binding is required when callerAuth.mode is warn or enforce" .Values.callerAuth.binding }} + {{- $binding := "" }} + {{- if not (and .Values.readback .Values.readback.enabled) }} + {{- $binding = required "callerAuth.binding is required when callerAuth.mode is warn or enforce" .Values.callerAuth.binding }} + {{- end }} - --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 }} + {{- if and .Values.readback .Values.readback.enabled }} + {{- /* serve-readback admits one readback caller and no /v1/check binding (FLEX-DEC-2026-019). */}} + - --readback-caller + - {{ required "readback.caller is required when readback.enabled=true" .Values.readback.caller | quote }} + {{- else }} - --caller-binding - {{ $binding | quote }} {{- range .Values.callerAuth.extraBindings }} @@ -57,6 +65,7 @@ spec: - {{ . | quote }} {{- end }} {{- end }} + {{- end }} ports: - name: http containerPort: {{ .Values.service.port }} diff --git a/charts/flex-auth/values.yaml b/charts/flex-auth/values.yaml index 9f3d517..b61b808 100644 --- a/charts/flex-auth/values.yaml +++ b/charts/flex-auth/values.yaml @@ -40,6 +40,12 @@ consumer: # disabled keeps the image bootable without TokenReview (isolated canary). # Production pins set warn, then enforce, independently per consumer. +# serve-readback pins (FLEX-WP-0035) admit one readback caller via +# --readback-caller instead of --caller-binding. +readback: + enabled: false + caller: "" + callerAuth: mode: disabled audience: flex-auth diff --git a/cmd/flex-auth/readback_pin_test.go b/cmd/flex-auth/readback_pin_test.go new file mode 100644 index 0000000..f23c6c2 --- /dev/null +++ b/cmd/flex-auth/readback_pin_test.go @@ -0,0 +1,72 @@ +package main + +import ( + "os" + "path/filepath" + "strings" + "testing" + + "gopkg.in/yaml.v3" +) + +type pinValues struct { + Image struct { + Digest string `yaml:"digest"` + } `yaml:"image"` + Args []string `yaml:"args"` +} + +func loadPinValues(t *testing.T, name string) pinValues { + t.Helper() + raw, err := os.ReadFile(filepath.Join("..", "..", "values", name)) + if err != nil { + t.Fatal(err) + } + var v pinValues + if err := yaml.Unmarshal(raw, &v); err != nil { + t.Fatal(err) + } + return v +} + +func argAfter(args []string, flag string) []string { + var out []string + for i := 0; i+1 < len(args); i++ { + if args[i] == flag { + out = append(out, args[i+1]) + } + } + return out +} + +// FLEX-WP-0035-T04, contract §4: the readback pin runs each participant's +// package at the same digest its own pin runs. The package is baked into the +// image, so same image digest and same path is the same package digest. +func TestReadbackPinMatchesParticipantPins(t *testing.T) { + readback := loadPinValues(t, "application-readback.yaml") + if len(readback.Args) == 0 || readback.Args[0] != "serve-readback" { + t.Fatal("readback pin must run serve-readback, which mounts no /v1/check") + } + participants := map[string]string{"informed-decision": "informed-decision-lifecycle.yaml"} + packages := argAfter(readback.Args, "--package") + if len(packages) != len(participants) { + t.Fatalf("readback packages %v, want one per participant %v", packages, participants) + } + for _, pkg := range packages { + system, path, _ := strings.Cut(pkg, "=") + file, ok := participants[system] + if !ok { + t.Fatalf("readback package for %q has no participant pin", system) + } + own := loadPinValues(t, file) + if own.Image.Digest != readback.Image.Digest { + t.Errorf("%s: readback image %s, own pin %s", system, readback.Image.Digest, own.Image.Digest) + } + if policies := argAfter(own.Args, "--policy"); len(policies) != 1 || policies[0] != path { + t.Errorf("%s: readback package %s, own pin %v", system, path, policies) + } + if !strings.Contains(strings.Join(argAfter(readback.Args, "--registry"), " "), strings.Join(argAfter(own.Args, "--registry"), " ")) { + t.Errorf("%s: readback pin does not load its registry", system) + } + } +} diff --git a/values/application-readback.yaml b/values/application-readback.yaml new file mode 100644 index 0000000..07153a0 --- /dev/null +++ b/values/application-readback.yaml @@ -0,0 +1,35 @@ +# FLEX-WP-0035-T04: advisory application access readback for user-engine. +# Founder-approved 2026-10-10. Runs each participant's package from the same +# image digest and path as that participant's own pin (contract §4); the test +# TestReadbackPinMatchesParticipantPins enforces it. +name: flex-auth-application-readback +image: + repository: forgejo.coulomb.social/coulomb/flex-auth + digest: sha256:e8c57c18a4a9d5583be099146984f7e9cd881446218b65aeefd7619b18aa9229 +args: +- serve-readback +- --addr +- 0.0.0.0:8080 +- --registry +- /opt/flex-auth/examples/application-readback/registry_snapshot.json +- --registry +- /opt/flex-auth/examples/informed-decision-lifecycle/registry.json +- --package +- informed-decision=/opt/flex-auth/examples/informed-decision-lifecycle/policy.md +callerAuth: + mode: enforce + kubernetesURL: https://10.43.0.1 +readback: + enabled: true + caller: system:serviceaccount:user-engine:user-engine +consumer: + isolated: false + namespace: user-engine + podName: user-engine +resources: + requests: + cpu: 5m + memory: 32Mi + limits: + cpu: 300m + memory: 192Mi diff --git a/values/informed-decision-lifecycle.yaml b/values/informed-decision-lifecycle.yaml index 8ef9dd9..aacc786 100644 --- a/values/informed-decision-lifecycle.yaml +++ b/values/informed-decision-lifecycle.yaml @@ -1,8 +1,10 @@ # Founder-approved INFD-WP-0007 / FLEX-WP-0033 cutover, 2026-10-04. +# Lifecycle v2 (application.access), founder-approved FLEX-WP-0035-T04, 2026-10-10. +# Previous stable: sha256:bdac62bc3f089cba0df7c35eeb8d0d9b3af2d4bae5e4ea4767516c1ed20b1e99 name: flex-auth-informed-decision-lifecycle image: repository: forgejo.coulomb.social/coulomb/flex-auth - digest: sha256:bdac62bc3f089cba0df7c35eeb8d0d9b3af2d4bae5e4ea4767516c1ed20b1e99 + digest: sha256:e8c57c18a4a9d5583be099146984f7e9cd881446218b65aeefd7619b18aa9229 args: - serve - --addr