Prepare renamed Access Engine runtime twins with exact reviewer scope
Assistant: codex Assistant-Model: gpt-6.1-sol Assistant-Session: 01a126e1-76f8-7643-8cf3-e93336539ade
This commit is contained in:
parent
0bc15925e9
commit
dd15d8cf3f
26 changed files with 1480 additions and 26 deletions
6
charts/access-engine/Chart.yaml
Normal file
6
charts/access-engine/Chart.yaml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
apiVersion: v2
|
||||
name: access-engine
|
||||
description: Independently rollable access-engine policy-decision Deployment (one consumer per release).
|
||||
type: application
|
||||
version: 0.2.0
|
||||
appVersion: "0.1.0"
|
||||
35
charts/access-engine/templates/_helpers.tpl
Normal file
35
charts/access-engine/templates/_helpers.tpl
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
{{- define "access-engine.image" -}}
|
||||
{{- if not .Values.image.digest }}
|
||||
{{- fail "image.digest is required (digest_policy=required); do not deploy by tag" }}
|
||||
{{- end }}
|
||||
{{- printf "%s@%s" .Values.image.repository .Values.image.digest -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "access-engine.name" -}}
|
||||
{{- required "name is required" .Values.name -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "access-engine.selectorLabels" -}}
|
||||
app.kubernetes.io/name: {{ include "access-engine.name" . }}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "access-engine.labels" -}}
|
||||
{{ include "access-engine.selectorLabels" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
app.kubernetes.io/part-of: access-engine
|
||||
{{- end -}}
|
||||
|
||||
{{- define "access-engine.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 "access-engine.callerAuth.enabled" -}}
|
||||
{{- if has (include "access-engine.callerAuth.mode" .) (list "warn" "enforce") -}}true{{- else -}}false{{- end -}}
|
||||
{{- end -}}
|
||||
167
charts/access-engine/templates/deployment.yaml
Normal file
167
charts/access-engine/templates/deployment.yaml
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
{{- if .Values.auditDelivery.enabled }}
|
||||
{{- if ne (first .Values.args) "serve" }}
|
||||
{{- fail "auditDelivery currently supports serve decision pins only" }}
|
||||
{{- end }}
|
||||
{{- if not .Values.outbox.enabled }}
|
||||
{{- fail "auditDelivery requires enabled durable outbox" }}
|
||||
{{- end }}
|
||||
{{- if not (regexMatch "^https?://[^/?#@]+/v1/events$" .Values.auditDelivery.url) }}
|
||||
{{- fail "auditDelivery.url must be an explicit http(s) /v1/events endpoint" }}
|
||||
{{- end }}
|
||||
{{- if or (not (hasPrefix "/" .Values.auditDelivery.mountPath)) (contains ".." .Values.auditDelivery.mountPath) }}
|
||||
{{- fail "auditDelivery.mountPath must be an absolute normalized directory" }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
namespace: {{ .Release.Namespace }}
|
||||
name: {{ include "access-engine.name" . }}
|
||||
labels:
|
||||
{{- include "access-engine.labels" . | nindent 4 }}
|
||||
spec:
|
||||
{{- if .Values.outbox.enabled }}
|
||||
{{- if ne (int .Values.replicaCount) 1 }}
|
||||
{{- fail "outbox requires replicaCount=1: one writer per source and PVC" }}
|
||||
{{- end }}
|
||||
strategy:
|
||||
type: Recreate
|
||||
{{- end }}
|
||||
replicas: {{ .Values.replicaCount }}
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "access-engine.selectorLabels" . | nindent 6 }}
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
{{- include "access-engine.labels" . | nindent 8 }}
|
||||
spec:
|
||||
automountServiceAccountToken: false
|
||||
{{- if eq (include "access-engine.callerAuth.enabled" .) "true" }}
|
||||
serviceAccountName: {{ include "access-engine.name" . }}
|
||||
{{- end }}
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 65532
|
||||
runAsGroup: 65532
|
||||
fsGroup: 65532
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
containers:
|
||||
- name: access-engine
|
||||
image: {{ include "access-engine.image" . }}
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
args:
|
||||
{{- toYaml .Values.args | nindent 12 }}
|
||||
{{- if .Values.outbox.enabled }}
|
||||
- --outbox-dir
|
||||
- {{ .Values.outbox.mountPath | quote }}
|
||||
- --emission-source
|
||||
- {{ required "outbox.source is required when outbox.enabled=true" .Values.outbox.source | quote }}
|
||||
{{- end }}
|
||||
{{- if .Values.auditDelivery.enabled }}
|
||||
- --audit-url
|
||||
- {{ .Values.auditDelivery.url | quote }}
|
||||
- --audit-token-file
|
||||
- {{ printf "%s/token" .Values.auditDelivery.mountPath | quote }}
|
||||
{{- end }}
|
||||
{{- if eq (include "access-engine.callerAuth.enabled" .) "true" }}
|
||||
{{- $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-audience
|
||||
- {{ required "callerAuth.audience is required" .Values.callerAuth.audience | quote }}
|
||||
- --caller-reviewer-token-file
|
||||
- {{ printf "%s/token" .Values.callerAuth.reviewer.mountPath | quote }}
|
||||
- --caller-ca-file
|
||||
- {{ printf "%s/ca.crt" .Values.callerAuth.reviewer.mountPath | quote }}
|
||||
- --caller-auth-mode
|
||||
- {{ include "access-engine.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 }}
|
||||
- --caller-binding
|
||||
- {{ . | quote }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: {{ .Values.service.port }}
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: {{ .Values.health.path }}
|
||||
port: http
|
||||
periodSeconds: 20
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: {{ .Values.health.path }}
|
||||
port: http
|
||||
periodSeconds: 5
|
||||
resources:
|
||||
{{- toYaml .Values.resources | nindent 12 }}
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
readOnlyRootFilesystem: true
|
||||
{{- if or .Values.outbox.enabled .Values.auditDelivery.enabled (eq (include "access-engine.callerAuth.enabled" .) "true") }}
|
||||
volumeMounts:
|
||||
{{- if .Values.auditDelivery.enabled }}
|
||||
- mountPath: {{ .Values.auditDelivery.mountPath | quote }}
|
||||
name: access-engine-audit
|
||||
readOnly: true
|
||||
{{- end }}
|
||||
{{- if .Values.outbox.enabled }}
|
||||
- mountPath: {{ .Values.outbox.mountPath | quote }}
|
||||
name: access-engine-outbox
|
||||
{{- end }}
|
||||
{{- if eq (include "access-engine.callerAuth.enabled" .) "true" }}
|
||||
- mountPath: {{ .Values.callerAuth.reviewer.mountPath }}
|
||||
name: access-engine-reviewer
|
||||
readOnly: true
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if or .Values.outbox.enabled .Values.auditDelivery.enabled (eq (include "access-engine.callerAuth.enabled" .) "true") }}
|
||||
volumes:
|
||||
{{- if .Values.auditDelivery.enabled }}
|
||||
- name: access-engine-audit
|
||||
secret:
|
||||
secretName: {{ required "auditDelivery.secretName must name an admitted existing Secret" .Values.auditDelivery.secretName | quote }}
|
||||
defaultMode: 0440
|
||||
items:
|
||||
- key: {{ required "auditDelivery.secretKey is required" .Values.auditDelivery.secretKey | quote }}
|
||||
path: token
|
||||
{{- end }}
|
||||
{{- if .Values.outbox.enabled }}
|
||||
- name: access-engine-outbox
|
||||
persistentVolumeClaim:
|
||||
claimName: {{ default (printf "%s-outbox" (include "access-engine.name" .)) .Values.outbox.existingClaim | quote }}
|
||||
{{- end }}
|
||||
{{- if eq (include "access-engine.callerAuth.enabled" .) "true" }}
|
||||
- name: access-engine-reviewer
|
||||
projected:
|
||||
defaultMode: 0440
|
||||
sources:
|
||||
- serviceAccountToken:
|
||||
{{- if .Values.callerAuth.reviewer.audience }}
|
||||
audience: {{ .Values.callerAuth.reviewer.audience | quote }}
|
||||
{{- end }}
|
||||
expirationSeconds: {{ .Values.callerAuth.reviewer.expirationSeconds }}
|
||||
path: token
|
||||
- configMap:
|
||||
name: kube-root-ca.crt
|
||||
items:
|
||||
- key: ca.crt
|
||||
path: ca.crt
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
68
charts/access-engine/templates/networkpolicy.yaml
Normal file
68
charts/access-engine/templates/networkpolicy.yaml
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
namespace: {{ .Release.Namespace }}
|
||||
name: {{ include "access-engine.name" . }}
|
||||
labels:
|
||||
{{- include "access-engine.labels" . | nindent 4 }}
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
{{- include "access-engine.selectorLabels" . | nindent 6 }}
|
||||
policyTypes:
|
||||
- Ingress
|
||||
- Egress
|
||||
{{- if or .Values.auditDelivery.enabled (eq (include "access-engine.callerAuth.enabled" .) "true") }}
|
||||
egress:
|
||||
{{- if eq (include "access-engine.callerAuth.enabled" .) "true" }}
|
||||
- ports:
|
||||
- port: 443
|
||||
protocol: TCP
|
||||
- port: 6443
|
||||
protocol: TCP
|
||||
{{- end }}
|
||||
{{- if .Values.auditDelivery.enabled }}
|
||||
{{- if or (not (regexMatch "^[1-9][0-9]*$" (toString .Values.auditDelivery.receiverPort))) (lt (int .Values.auditDelivery.receiverPort) 1) (gt (int .Values.auditDelivery.receiverPort) 65535) }}
|
||||
{{- fail "auditDelivery.receiverPort must be explicitly set to a valid receiver port" }}
|
||||
{{- end }}
|
||||
- to:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: {{ required "auditDelivery.receiverNamespace is required" .Values.auditDelivery.receiverNamespace | quote }}
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: {{ required "auditDelivery.receiverPodName is required" .Values.auditDelivery.receiverPodName | quote }}
|
||||
ports:
|
||||
- port: {{ .Values.auditDelivery.receiverPort }}
|
||||
protocol: TCP
|
||||
- to:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: {{ required "auditDelivery.dnsNamespace is required" .Values.auditDelivery.dnsNamespace | quote }}
|
||||
podSelector:
|
||||
matchLabels:
|
||||
k8s-app: {{ required "auditDelivery.dnsPodLabel is required" .Values.auditDelivery.dnsPodLabel | quote }}
|
||||
ports:
|
||||
- port: 53
|
||||
protocol: UDP
|
||||
- port: 53
|
||||
protocol: TCP
|
||||
{{- end }}
|
||||
{{- else }}
|
||||
egress: []
|
||||
{{- end }}
|
||||
{{- if .Values.consumer.isolated }}
|
||||
ingress: []
|
||||
{{- else }}
|
||||
ingress:
|
||||
- from:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: {{ required "consumer.namespace is required when not isolated" .Values.consumer.namespace }}
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: {{ required "consumer.podName is required when not isolated" .Values.consumer.podName }}
|
||||
ports:
|
||||
- port: {{ .Values.service.port }}
|
||||
protocol: TCP
|
||||
{{- end }}
|
||||
20
charts/access-engine/templates/outbox-pvc.yaml
Normal file
20
charts/access-engine/templates/outbox-pvc.yaml
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
{{- if and .Values.outbox.enabled (not .Values.outbox.existingClaim) }}
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
namespace: {{ .Release.Namespace }}
|
||||
name: {{ include "access-engine.name" . }}-outbox
|
||||
annotations:
|
||||
helm.sh/resource-policy: keep
|
||||
labels:
|
||||
{{- include "access-engine.labels" . | nindent 4 }}
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
{{- if .Values.outbox.storageClassName }}
|
||||
storageClassName: {{ .Values.outbox.storageClassName | quote }}
|
||||
{{- end }}
|
||||
resources:
|
||||
requests:
|
||||
storage: {{ .Values.outbox.size | quote }}
|
||||
{{- end }}
|
||||
30
charts/access-engine/templates/rbac.yaml
Normal file
30
charts/access-engine/templates/rbac.yaml
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
{{- if eq (include "access-engine.callerAuth.enabled" .) "true" }}
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: {{ include "access-engine.name" . }}-tokenreviewer
|
||||
labels:
|
||||
{{- include "access-engine.labels" . | nindent 4 }}
|
||||
rules:
|
||||
- apiGroups:
|
||||
- authentication.k8s.io
|
||||
resources:
|
||||
- tokenreviews
|
||||
verbs:
|
||||
- create
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: {{ include "access-engine.name" . }}-tokenreviewer
|
||||
labels:
|
||||
{{- include "access-engine.labels" . | nindent 4 }}
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: {{ include "access-engine.name" . }}-tokenreviewer
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: {{ include "access-engine.name" . }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
{{- end }}
|
||||
14
charts/access-engine/templates/service.yaml
Normal file
14
charts/access-engine/templates/service.yaml
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
namespace: {{ .Release.Namespace }}
|
||||
name: {{ include "access-engine.name" . }}
|
||||
labels:
|
||||
{{- include "access-engine.labels" . | nindent 4 }}
|
||||
spec:
|
||||
selector:
|
||||
{{- include "access-engine.selectorLabels" . | nindent 4 }}
|
||||
ports:
|
||||
- name: http
|
||||
port: {{ .Values.service.port }}
|
||||
targetPort: http
|
||||
10
charts/access-engine/templates/serviceaccount.yaml
Normal file
10
charts/access-engine/templates/serviceaccount.yaml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{{- if eq (include "access-engine.callerAuth.enabled" .) "true" }}
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
namespace: {{ .Release.Namespace }}
|
||||
name: {{ include "access-engine.name" . }}
|
||||
labels:
|
||||
{{- include "access-engine.labels" . | nindent 4 }}
|
||||
automountServiceAccountToken: false
|
||||
{{- end }}
|
||||
87
charts/access-engine/values.yaml
Normal file
87
charts/access-engine/values.yaml
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
# Default chart values. Real pins live in values/*.yaml — never promote a tag.
|
||||
name: access-engine
|
||||
namespace: access-engine
|
||||
|
||||
image:
|
||||
repository: forgejo.coulomb.social/coulomb/access-engine
|
||||
digest: ""
|
||||
pullPolicy: IfNotPresent
|
||||
|
||||
replicaCount: 1
|
||||
|
||||
# Inert until enabled for an approved pin. The outbox is single-writer.
|
||||
outbox:
|
||||
enabled: false
|
||||
source: ""
|
||||
mountPath: /var/lib/access-engine/outbox
|
||||
size: 1Gi
|
||||
storageClassName: ""
|
||||
existingClaim: ""
|
||||
|
||||
# Inert preparation: caller provides reviewed existing Secret and exact receiver.
|
||||
# The chart never creates/mints sender credentials or registers a sender.
|
||||
auditDelivery:
|
||||
enabled: false
|
||||
url: ""
|
||||
secretName: ""
|
||||
secretKey: ""
|
||||
mountPath: /var/run/secrets/access-engine-audit
|
||||
receiverNamespace: ""
|
||||
receiverPodName: ""
|
||||
receiverPort: 0
|
||||
dnsNamespace: ""
|
||||
dnsPodLabel: ""
|
||||
|
||||
args:
|
||||
- serve
|
||||
- --addr
|
||||
- 0.0.0.0:8080
|
||||
- --registry
|
||||
- /opt/access-engine/examples/tenant-engine/registry_snapshot.json
|
||||
- --policy
|
||||
- /opt/access-engine/examples/tenant-engine/policy_package.md
|
||||
|
||||
service:
|
||||
port: 8080
|
||||
|
||||
health:
|
||||
path: /healthz
|
||||
|
||||
consumer:
|
||||
isolated: false
|
||||
namespace: ""
|
||||
podName: ""
|
||||
|
||||
# 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: access-engine
|
||||
kubernetesURL: https://kubernetes.default.svc
|
||||
binding: ""
|
||||
# Further resource.system=principal pairs, each rendered as its own
|
||||
# --caller-binding (e.g. ops-warden-fetch binds every catalog owner system
|
||||
# to the ops-warden ServiceAccount).
|
||||
extraBindings: []
|
||||
reviewer:
|
||||
mountPath: /var/run/secrets/access-engine-reviewer
|
||||
# Empty: kubelet mints the API server's default audiences so TokenReview
|
||||
# authenticates. A custom audience (e.g. https://kubernetes.default.svc)
|
||||
# 401s on this k3s cluster, whose API audiences are
|
||||
# https://kubernetes.default.svc.cluster.local and k3s.
|
||||
audience: ""
|
||||
expirationSeconds: 3600
|
||||
|
||||
resources:
|
||||
requests:
|
||||
cpu: 25m
|
||||
memory: 32Mi
|
||||
limits:
|
||||
cpu: 300m
|
||||
memory: 192Mi
|
||||
|
|
@ -43,30 +43,35 @@ func argAfter(args []string, flag string) []string {
|
|||
// 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)
|
||||
}
|
||||
for _, prefix := range []string{"", "access-engine/"} {
|
||||
t.Run(prefix, func(t *testing.T) {
|
||||
readback := loadPinValues(t, prefix+"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, prefix+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)
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
8
deploy/access-engine-namespace.yaml
Normal file
8
deploy/access-engine-namespace.yaml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
# FLEX-WP-0036-T05; creation remains gated on ordered predecessor live proof.
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: access-engine
|
||||
labels:
|
||||
kubernetes.io/metadata.name: access-engine
|
||||
app.kubernetes.io/part-of: access-engine
|
||||
|
|
@ -0,0 +1,439 @@
|
|||
{
|
||||
"schema": "build-mode-prepared-change-v2",
|
||||
"workplan": "FLEX-WP-0036",
|
||||
"task": "FLEX-WP-0036-T05",
|
||||
"status": "PREPARED_SOURCE_ONLY_NATIVE_RED_BATCH_WAIT",
|
||||
"target": "access-engine namespace and nine side-by-side releases",
|
||||
"prerequisites": [
|
||||
"Single daily red batch disposition authorizes exact nine new TokenReview ServiceAccount bindings",
|
||||
"Ordered preceding critical-path live evidence by coordinator",
|
||||
"T03 image compatibility rollout verified on existing pins",
|
||||
"T04 exact artifact availability and same-digest twin provenance verified",
|
||||
"Old T05 declared external dependency qualified by coordinator; worker did not inspect wait task",
|
||||
"Actual live pin inventory/digest and CPU headroom reconciled before server dry-run"
|
||||
],
|
||||
"expected_result": "Nine renamed releases Ready; /healthz200; anonymous protected routes401; exact twin image/policy and caller/network scope unchanged",
|
||||
"abort_owner": "Custodian coordinator acting as Access runtime change owner",
|
||||
"abort_conditions": [
|
||||
"Any source/live twin digest or package argv mismatch",
|
||||
"Any unproven preceding critical-path gate",
|
||||
"Insufficient headroom or rollout stall",
|
||||
"New pin cannot TokenReview or cannot match old decision behavior",
|
||||
"Any changed caller principal, ingress/egress grant or shared durable writer state",
|
||||
"Any missing exact red-batch authorization for new API principals or recovery revocation scope"
|
||||
],
|
||||
"verification": [
|
||||
"Fresh helm template + server dry-run for exact source revision, namespace and pin manifests",
|
||||
"Reconcile live nine-pin inventory and image digests against source",
|
||||
"Ready, healthz200 and anonymous401 each new pin",
|
||||
"Live authenticated owner allow/deny correspondence with old twin; no consumer switch in T05",
|
||||
"Read back exact nine bound ServiceAccount subjects and TokenReview-only ClusterRole rules; prove no additional subjects/resources/verbs were admitted"
|
||||
],
|
||||
"rollback": "Before execution: no native grants exist to revoke. If the exact red batch is authorized and executed, suspend newly introduced GitOps reconciliation and scale only new access-engine Deployments to zero to stop workloads; this does NOT revoke their TokenReview authority. Rollback must additionally revoke exactly the nine new ClusterRoleBindings through the authorized red-batch recovery scope, retaining prior flex-auth bindings and all namespaces/data. Do not claim scale-zero alone revokes credentials or API authority. Destructive namespace/data deletion remains outside this item.",
|
||||
"scope_limits": [
|
||||
"No consumer endpoint/egress flip",
|
||||
"No live mutation executed",
|
||||
"No dual repository publication or unproven new image paths claimed",
|
||||
"No durable writer twins generated",
|
||||
"Nine new Kubernetes API TokenReview caller grants are explicitly proposed in red batch; no new protected-consumer caller mappings, secret grants or native writes executed"
|
||||
],
|
||||
"pins": [
|
||||
{
|
||||
"name": "access-engine-application-readback",
|
||||
"image": "forgejo.coulomb.social/coulomb/flex-auth@sha256:e8c57c18a4a9d5583be099146984f7e9cd881446218b65aeefd7619b18aa9229",
|
||||
"caller_auth": {
|
||||
"mode": "enforce",
|
||||
"kubernetesURL": "https://10.43.0.1",
|
||||
"audience": "access-engine"
|
||||
},
|
||||
"consumer": {
|
||||
"isolated": false,
|
||||
"namespace": "user-engine",
|
||||
"podName": "user-engine"
|
||||
},
|
||||
"request_cpu": "5m",
|
||||
"request_memory": "32Mi"
|
||||
},
|
||||
{
|
||||
"name": "access-engine-informed-decision-lifecycle",
|
||||
"image": "forgejo.coulomb.social/coulomb/flex-auth@sha256:e8c57c18a4a9d5583be099146984f7e9cd881446218b65aeefd7619b18aa9229",
|
||||
"caller_auth": {
|
||||
"mode": "enforce",
|
||||
"kubernetesURL": "https://10.43.0.1",
|
||||
"binding": "informed-decision=system:serviceaccount:informed-decision:review",
|
||||
"audience": "access-engine"
|
||||
},
|
||||
"consumer": {
|
||||
"isolated": false,
|
||||
"namespace": "informed-decision",
|
||||
"podName": "informed-decision"
|
||||
},
|
||||
"request_cpu": "5m",
|
||||
"request_memory": "32Mi"
|
||||
},
|
||||
{
|
||||
"name": "access-engine-informed-decision-sitting",
|
||||
"image": "forgejo.coulomb.social/coulomb/flex-auth@sha256:bdac62bc3f089cba0df7c35eeb8d0d9b3af2d4bae5e4ea4767516c1ed20b1e99",
|
||||
"caller_auth": {
|
||||
"mode": "enforce",
|
||||
"kubernetesURL": "https://10.43.0.1",
|
||||
"binding": "informed-decision=system:serviceaccount:informed-decision:review",
|
||||
"audience": "access-engine"
|
||||
},
|
||||
"consumer": {
|
||||
"isolated": false,
|
||||
"namespace": "informed-decision",
|
||||
"podName": "informed-decision"
|
||||
},
|
||||
"request_cpu": "5m",
|
||||
"request_memory": "32Mi"
|
||||
},
|
||||
{
|
||||
"name": "access-engine-informed-decision-t03",
|
||||
"image": "forgejo.coulomb.social/coulomb/flex-auth@sha256:6600c7a62c36dfb4fbb42590722613827d07ff49875b1f0f4b5de63d8351658d",
|
||||
"caller_auth": {
|
||||
"mode": "enforce",
|
||||
"kubernetesURL": "https://10.43.0.1",
|
||||
"binding": "informed-decision=system:serviceaccount:informed-decision:review",
|
||||
"audience": "access-engine"
|
||||
},
|
||||
"consumer": {
|
||||
"isolated": false,
|
||||
"namespace": "informed-decision",
|
||||
"podName": "informed-decision"
|
||||
},
|
||||
"request_cpu": "5m",
|
||||
"request_memory": "32Mi"
|
||||
},
|
||||
{
|
||||
"name": "access-engine-ops-warden-fetch",
|
||||
"image": "forgejo.coulomb.social/coulomb/flex-auth@sha256:419bc37420d0d0a5304a8587448f1fdbf87117fb041fb43081b0449b4a7bb13b",
|
||||
"caller_auth": {
|
||||
"mode": "enforce",
|
||||
"kubernetesURL": "https://10.43.0.1",
|
||||
"binding": "key-cape=system:serviceaccount:ops-warden:ops-warden",
|
||||
"extraBindings": [
|
||||
"railiance-platform=system:serviceaccount:ops-warden:ops-warden"
|
||||
],
|
||||
"audience": "access-engine"
|
||||
},
|
||||
"consumer": {
|
||||
"isolated": true
|
||||
},
|
||||
"request_cpu": "25m",
|
||||
"request_memory": "32Mi"
|
||||
},
|
||||
{
|
||||
"name": "access-engine-ops-warden",
|
||||
"image": "forgejo.coulomb.social/coulomb/flex-auth@sha256:138aa3471c46bca6e814691fa1e6520aedda3dffd743e6b09141ab433afdb64b",
|
||||
"caller_auth": {
|
||||
"mode": "enforce",
|
||||
"kubernetesURL": "https://10.43.0.1",
|
||||
"binding": "ops-warden=system:serviceaccount:ops-warden:ops-warden",
|
||||
"audience": "access-engine"
|
||||
},
|
||||
"consumer": {
|
||||
"isolated": true
|
||||
},
|
||||
"request_cpu": "25m",
|
||||
"request_memory": "32Mi"
|
||||
},
|
||||
{
|
||||
"name": "access-engine-secrets-engine",
|
||||
"image": "forgejo.coulomb.social/coulomb/flex-auth@sha256:05a03a8790c2210c48ea92391441c77ddf640d0cd32f5ec09838f5393171fcbd",
|
||||
"caller_auth": {
|
||||
"mode": "enforce",
|
||||
"kubernetesURL": "https://10.43.0.1",
|
||||
"binding": "secrets-engine=system:serviceaccount:secrets-engine:secrets-engine",
|
||||
"audience": "access-engine"
|
||||
},
|
||||
"consumer": {
|
||||
"isolated": false,
|
||||
"namespace": "secrets-engine",
|
||||
"podName": "secrets-engine"
|
||||
},
|
||||
"request_cpu": "25m",
|
||||
"request_memory": "32Mi"
|
||||
},
|
||||
{
|
||||
"name": "access-engine-tenant-engine",
|
||||
"image": "forgejo.coulomb.social/coulomb/flex-auth@sha256:05a03a8790c2210c48ea92391441c77ddf640d0cd32f5ec09838f5393171fcbd",
|
||||
"caller_auth": {
|
||||
"mode": "enforce",
|
||||
"kubernetesURL": "https://10.43.0.1",
|
||||
"binding": "tenant-engine=system:serviceaccount:tenant-engine:tenant-engine",
|
||||
"audience": "access-engine"
|
||||
},
|
||||
"consumer": {
|
||||
"isolated": false,
|
||||
"namespace": "tenant-engine",
|
||||
"podName": "tenant-engine"
|
||||
},
|
||||
"request_cpu": "25m",
|
||||
"request_memory": "32Mi"
|
||||
},
|
||||
{
|
||||
"name": "access-engine-user-engine",
|
||||
"image": "forgejo.coulomb.social/coulomb/flex-auth@sha256:138aa3471c46bca6e814691fa1e6520aedda3dffd743e6b09141ab433afdb64b",
|
||||
"caller_auth": {
|
||||
"mode": "enforce",
|
||||
"kubernetesURL": "https://10.43.0.1",
|
||||
"binding": "user-engine=system:serviceaccount:user-engine:user-engine",
|
||||
"audience": "access-engine"
|
||||
},
|
||||
"consumer": {
|
||||
"isolated": false,
|
||||
"namespace": "user-engine",
|
||||
"podName": "user-engine"
|
||||
},
|
||||
"request_cpu": "25m",
|
||||
"request_memory": "32Mi"
|
||||
}
|
||||
],
|
||||
"classification": {
|
||||
"source_preparation": "blue reversible local source work",
|
||||
"native_namespace_and_pins": "red access-scope expansion: new ServiceAccounts receive Kubernetes TokenReview API create authority",
|
||||
"reason": "Equality of verb sets does not establish equality of principals; nine new bound subjects can now create tokenreviews.",
|
||||
"approval_route": "one item in the single daily red batch; no per-item founder request"
|
||||
},
|
||||
"supersedes": {
|
||||
"path": "/tmp/access-runtime-twins-prepared-receipt.json",
|
||||
"sha256": "ddf0d366afa3330ab753079dc6e4c122559b3bb2b9c6e38faf72d23f8a09127d",
|
||||
"reason": "First prepared receipt incorrectly classified new TokenReview principals as blue based on role-verb parity."
|
||||
},
|
||||
"new_access_scope": [
|
||||
{
|
||||
"subject": {
|
||||
"kind": "ServiceAccount",
|
||||
"namespace": "access-engine",
|
||||
"name": "access-engine-application-readback",
|
||||
"authenticated_username": "system:serviceaccount:access-engine:access-engine-application-readback"
|
||||
},
|
||||
"binding": "access-engine-application-readback-tokenreviewer",
|
||||
"role": {
|
||||
"kind": "ClusterRole",
|
||||
"name": "access-engine-application-readback-tokenreviewer"
|
||||
},
|
||||
"capability": {
|
||||
"apiGroups": [
|
||||
"authentication.k8s.io"
|
||||
],
|
||||
"resources": [
|
||||
"tokenreviews"
|
||||
],
|
||||
"verbs": [
|
||||
"create"
|
||||
]
|
||||
},
|
||||
"scope": "cluster-scoped TokenReview API create; not a secrets-read grant or protected-consumer impersonation grant"
|
||||
},
|
||||
{
|
||||
"subject": {
|
||||
"kind": "ServiceAccount",
|
||||
"namespace": "access-engine",
|
||||
"name": "access-engine-informed-decision-lifecycle",
|
||||
"authenticated_username": "system:serviceaccount:access-engine:access-engine-informed-decision-lifecycle"
|
||||
},
|
||||
"binding": "access-engine-informed-decision-lifecycle-tokenreviewer",
|
||||
"role": {
|
||||
"kind": "ClusterRole",
|
||||
"name": "access-engine-informed-decision-lifecycle-tokenreviewer"
|
||||
},
|
||||
"capability": {
|
||||
"apiGroups": [
|
||||
"authentication.k8s.io"
|
||||
],
|
||||
"resources": [
|
||||
"tokenreviews"
|
||||
],
|
||||
"verbs": [
|
||||
"create"
|
||||
]
|
||||
},
|
||||
"scope": "cluster-scoped TokenReview API create; not a secrets-read grant or protected-consumer impersonation grant"
|
||||
},
|
||||
{
|
||||
"subject": {
|
||||
"kind": "ServiceAccount",
|
||||
"namespace": "access-engine",
|
||||
"name": "access-engine-informed-decision-sitting",
|
||||
"authenticated_username": "system:serviceaccount:access-engine:access-engine-informed-decision-sitting"
|
||||
},
|
||||
"binding": "access-engine-informed-decision-sitting-tokenreviewer",
|
||||
"role": {
|
||||
"kind": "ClusterRole",
|
||||
"name": "access-engine-informed-decision-sitting-tokenreviewer"
|
||||
},
|
||||
"capability": {
|
||||
"apiGroups": [
|
||||
"authentication.k8s.io"
|
||||
],
|
||||
"resources": [
|
||||
"tokenreviews"
|
||||
],
|
||||
"verbs": [
|
||||
"create"
|
||||
]
|
||||
},
|
||||
"scope": "cluster-scoped TokenReview API create; not a secrets-read grant or protected-consumer impersonation grant"
|
||||
},
|
||||
{
|
||||
"subject": {
|
||||
"kind": "ServiceAccount",
|
||||
"namespace": "access-engine",
|
||||
"name": "access-engine-informed-decision-t03",
|
||||
"authenticated_username": "system:serviceaccount:access-engine:access-engine-informed-decision-t03"
|
||||
},
|
||||
"binding": "access-engine-informed-decision-t03-tokenreviewer",
|
||||
"role": {
|
||||
"kind": "ClusterRole",
|
||||
"name": "access-engine-informed-decision-t03-tokenreviewer"
|
||||
},
|
||||
"capability": {
|
||||
"apiGroups": [
|
||||
"authentication.k8s.io"
|
||||
],
|
||||
"resources": [
|
||||
"tokenreviews"
|
||||
],
|
||||
"verbs": [
|
||||
"create"
|
||||
]
|
||||
},
|
||||
"scope": "cluster-scoped TokenReview API create; not a secrets-read grant or protected-consumer impersonation grant"
|
||||
},
|
||||
{
|
||||
"subject": {
|
||||
"kind": "ServiceAccount",
|
||||
"namespace": "access-engine",
|
||||
"name": "access-engine-ops-warden-fetch",
|
||||
"authenticated_username": "system:serviceaccount:access-engine:access-engine-ops-warden-fetch"
|
||||
},
|
||||
"binding": "access-engine-ops-warden-fetch-tokenreviewer",
|
||||
"role": {
|
||||
"kind": "ClusterRole",
|
||||
"name": "access-engine-ops-warden-fetch-tokenreviewer"
|
||||
},
|
||||
"capability": {
|
||||
"apiGroups": [
|
||||
"authentication.k8s.io"
|
||||
],
|
||||
"resources": [
|
||||
"tokenreviews"
|
||||
],
|
||||
"verbs": [
|
||||
"create"
|
||||
]
|
||||
},
|
||||
"scope": "cluster-scoped TokenReview API create; not a secrets-read grant or protected-consumer impersonation grant"
|
||||
},
|
||||
{
|
||||
"subject": {
|
||||
"kind": "ServiceAccount",
|
||||
"namespace": "access-engine",
|
||||
"name": "access-engine-ops-warden",
|
||||
"authenticated_username": "system:serviceaccount:access-engine:access-engine-ops-warden"
|
||||
},
|
||||
"binding": "access-engine-ops-warden-tokenreviewer",
|
||||
"role": {
|
||||
"kind": "ClusterRole",
|
||||
"name": "access-engine-ops-warden-tokenreviewer"
|
||||
},
|
||||
"capability": {
|
||||
"apiGroups": [
|
||||
"authentication.k8s.io"
|
||||
],
|
||||
"resources": [
|
||||
"tokenreviews"
|
||||
],
|
||||
"verbs": [
|
||||
"create"
|
||||
]
|
||||
},
|
||||
"scope": "cluster-scoped TokenReview API create; not a secrets-read grant or protected-consumer impersonation grant"
|
||||
},
|
||||
{
|
||||
"subject": {
|
||||
"kind": "ServiceAccount",
|
||||
"namespace": "access-engine",
|
||||
"name": "access-engine-secrets-engine",
|
||||
"authenticated_username": "system:serviceaccount:access-engine:access-engine-secrets-engine"
|
||||
},
|
||||
"binding": "access-engine-secrets-engine-tokenreviewer",
|
||||
"role": {
|
||||
"kind": "ClusterRole",
|
||||
"name": "access-engine-secrets-engine-tokenreviewer"
|
||||
},
|
||||
"capability": {
|
||||
"apiGroups": [
|
||||
"authentication.k8s.io"
|
||||
],
|
||||
"resources": [
|
||||
"tokenreviews"
|
||||
],
|
||||
"verbs": [
|
||||
"create"
|
||||
]
|
||||
},
|
||||
"scope": "cluster-scoped TokenReview API create; not a secrets-read grant or protected-consumer impersonation grant"
|
||||
},
|
||||
{
|
||||
"subject": {
|
||||
"kind": "ServiceAccount",
|
||||
"namespace": "access-engine",
|
||||
"name": "access-engine-tenant-engine",
|
||||
"authenticated_username": "system:serviceaccount:access-engine:access-engine-tenant-engine"
|
||||
},
|
||||
"binding": "access-engine-tenant-engine-tokenreviewer",
|
||||
"role": {
|
||||
"kind": "ClusterRole",
|
||||
"name": "access-engine-tenant-engine-tokenreviewer"
|
||||
},
|
||||
"capability": {
|
||||
"apiGroups": [
|
||||
"authentication.k8s.io"
|
||||
],
|
||||
"resources": [
|
||||
"tokenreviews"
|
||||
],
|
||||
"verbs": [
|
||||
"create"
|
||||
]
|
||||
},
|
||||
"scope": "cluster-scoped TokenReview API create; not a secrets-read grant or protected-consumer impersonation grant"
|
||||
},
|
||||
{
|
||||
"subject": {
|
||||
"kind": "ServiceAccount",
|
||||
"namespace": "access-engine",
|
||||
"name": "access-engine-user-engine",
|
||||
"authenticated_username": "system:serviceaccount:access-engine:access-engine-user-engine"
|
||||
},
|
||||
"binding": "access-engine-user-engine-tokenreviewer",
|
||||
"role": {
|
||||
"kind": "ClusterRole",
|
||||
"name": "access-engine-user-engine-tokenreviewer"
|
||||
},
|
||||
"capability": {
|
||||
"apiGroups": [
|
||||
"authentication.k8s.io"
|
||||
],
|
||||
"resources": [
|
||||
"tokenreviews"
|
||||
],
|
||||
"verbs": [
|
||||
"create"
|
||||
]
|
||||
},
|
||||
"scope": "cluster-scoped TokenReview API create; not a secrets-read grant or protected-consumer impersonation grant"
|
||||
}
|
||||
],
|
||||
"daily_red_batch_item": {
|
||||
"id": "FLEX-WP-0036-T05-tokenreview-twins",
|
||||
"request": "Authorize exact nine access-engine ServiceAccount TokenReview create bindings, together with exact binding revocation recovery; dependent namespace/pin rollout stays gated on T03/T04 and ordered predecessor evidence.",
|
||||
"new_subject_count": 9,
|
||||
"scope_list_field": "new_access_scope",
|
||||
"execution_state": "not executed",
|
||||
"scope_limits": "No secrets-read grants, protected-consumer mapping expansion, namespace/data deletion or consumer endpoint change."
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,190 @@
|
|||
{
|
||||
"schema": "build-mode-prepared-blue-change-v1",
|
||||
"workplan": "FLEX-WP-0036",
|
||||
"task": "FLEX-WP-0036-T05",
|
||||
"status": "PREPARED_SOURCE_ONLY_NATIVE_WAIT",
|
||||
"target": "access-engine namespace and nine side-by-side releases",
|
||||
"prerequisites": [
|
||||
"Ordered preceding critical-path live evidence by coordinator",
|
||||
"T03 image compatibility rollout verified on existing pins",
|
||||
"T04 exact artifact availability and same-digest twin provenance verified",
|
||||
"Old T05 declared external dependency qualified by coordinator; worker did not inspect wait task",
|
||||
"Actual live pin inventory/digest and CPU headroom reconciled before server dry-run"
|
||||
],
|
||||
"expected_result": "Nine renamed releases Ready; /healthz200; anonymous protected routes401; exact twin image/policy and caller/network scope unchanged",
|
||||
"abort_owner": "Custodian coordinator acting as Access runtime change owner",
|
||||
"abort_conditions": [
|
||||
"Any source/live twin digest or package argv mismatch",
|
||||
"Any unproven preceding critical-path gate",
|
||||
"Insufficient headroom or rollout stall",
|
||||
"New pin cannot TokenReview or cannot match old decision behavior",
|
||||
"Any changed caller principal, ingress/egress grant or shared durable writer state"
|
||||
],
|
||||
"verification": [
|
||||
"Fresh helm template + server dry-run for exact source revision, namespace and pin manifests",
|
||||
"Reconcile live nine-pin inventory and image digests against source",
|
||||
"Ready, healthz200 and anonymous401 each new pin",
|
||||
"Live authenticated owner allow/deny correspondence with old twin; no consumer switch in T05"
|
||||
],
|
||||
"rollback": "Suspend newly introduced GitOps reconciliation and scale only newly created access-engine Deployments to zero. Leave flex-auth releases, namespace, data and caller configuration intact. Do not delete old/new namespaces, RBAC or durable storage in this blue scope; destructive cleanup goes to the red batch.",
|
||||
"scope_limits": [
|
||||
"No consumer endpoint/egress flip",
|
||||
"No live mutation executed",
|
||||
"No new caller grants or secrets",
|
||||
"No dual repository publication or unproven new image paths claimed",
|
||||
"No durable writer twins generated"
|
||||
],
|
||||
"pins": [
|
||||
{
|
||||
"name": "access-engine-application-readback",
|
||||
"image": "forgejo.coulomb.social/coulomb/flex-auth@sha256:e8c57c18a4a9d5583be099146984f7e9cd881446218b65aeefd7619b18aa9229",
|
||||
"caller_auth": {
|
||||
"mode": "enforce",
|
||||
"kubernetesURL": "https://10.43.0.1",
|
||||
"audience": "access-engine"
|
||||
},
|
||||
"consumer": {
|
||||
"isolated": false,
|
||||
"namespace": "user-engine",
|
||||
"podName": "user-engine"
|
||||
},
|
||||
"request_cpu": "5m",
|
||||
"request_memory": "32Mi"
|
||||
},
|
||||
{
|
||||
"name": "access-engine-informed-decision-lifecycle",
|
||||
"image": "forgejo.coulomb.social/coulomb/flex-auth@sha256:e8c57c18a4a9d5583be099146984f7e9cd881446218b65aeefd7619b18aa9229",
|
||||
"caller_auth": {
|
||||
"mode": "enforce",
|
||||
"kubernetesURL": "https://10.43.0.1",
|
||||
"binding": "informed-decision=system:serviceaccount:informed-decision:review",
|
||||
"audience": "access-engine"
|
||||
},
|
||||
"consumer": {
|
||||
"isolated": false,
|
||||
"namespace": "informed-decision",
|
||||
"podName": "informed-decision"
|
||||
},
|
||||
"request_cpu": "5m",
|
||||
"request_memory": "32Mi"
|
||||
},
|
||||
{
|
||||
"name": "access-engine-informed-decision-sitting",
|
||||
"image": "forgejo.coulomb.social/coulomb/flex-auth@sha256:bdac62bc3f089cba0df7c35eeb8d0d9b3af2d4bae5e4ea4767516c1ed20b1e99",
|
||||
"caller_auth": {
|
||||
"mode": "enforce",
|
||||
"kubernetesURL": "https://10.43.0.1",
|
||||
"binding": "informed-decision=system:serviceaccount:informed-decision:review",
|
||||
"audience": "access-engine"
|
||||
},
|
||||
"consumer": {
|
||||
"isolated": false,
|
||||
"namespace": "informed-decision",
|
||||
"podName": "informed-decision"
|
||||
},
|
||||
"request_cpu": "5m",
|
||||
"request_memory": "32Mi"
|
||||
},
|
||||
{
|
||||
"name": "access-engine-informed-decision-t03",
|
||||
"image": "forgejo.coulomb.social/coulomb/flex-auth@sha256:6600c7a62c36dfb4fbb42590722613827d07ff49875b1f0f4b5de63d8351658d",
|
||||
"caller_auth": {
|
||||
"mode": "enforce",
|
||||
"kubernetesURL": "https://10.43.0.1",
|
||||
"binding": "informed-decision=system:serviceaccount:informed-decision:review",
|
||||
"audience": "access-engine"
|
||||
},
|
||||
"consumer": {
|
||||
"isolated": false,
|
||||
"namespace": "informed-decision",
|
||||
"podName": "informed-decision"
|
||||
},
|
||||
"request_cpu": "5m",
|
||||
"request_memory": "32Mi"
|
||||
},
|
||||
{
|
||||
"name": "access-engine-ops-warden-fetch",
|
||||
"image": "forgejo.coulomb.social/coulomb/flex-auth@sha256:419bc37420d0d0a5304a8587448f1fdbf87117fb041fb43081b0449b4a7bb13b",
|
||||
"caller_auth": {
|
||||
"mode": "enforce",
|
||||
"kubernetesURL": "https://10.43.0.1",
|
||||
"binding": "key-cape=system:serviceaccount:ops-warden:ops-warden",
|
||||
"extraBindings": [
|
||||
"railiance-platform=system:serviceaccount:ops-warden:ops-warden"
|
||||
],
|
||||
"audience": "access-engine"
|
||||
},
|
||||
"consumer": {
|
||||
"isolated": true
|
||||
},
|
||||
"request_cpu": "25m",
|
||||
"request_memory": "32Mi"
|
||||
},
|
||||
{
|
||||
"name": "access-engine-ops-warden",
|
||||
"image": "forgejo.coulomb.social/coulomb/flex-auth@sha256:138aa3471c46bca6e814691fa1e6520aedda3dffd743e6b09141ab433afdb64b",
|
||||
"caller_auth": {
|
||||
"mode": "enforce",
|
||||
"kubernetesURL": "https://10.43.0.1",
|
||||
"binding": "ops-warden=system:serviceaccount:ops-warden:ops-warden",
|
||||
"audience": "access-engine"
|
||||
},
|
||||
"consumer": {
|
||||
"isolated": true
|
||||
},
|
||||
"request_cpu": "25m",
|
||||
"request_memory": "32Mi"
|
||||
},
|
||||
{
|
||||
"name": "access-engine-secrets-engine",
|
||||
"image": "forgejo.coulomb.social/coulomb/flex-auth@sha256:05a03a8790c2210c48ea92391441c77ddf640d0cd32f5ec09838f5393171fcbd",
|
||||
"caller_auth": {
|
||||
"mode": "enforce",
|
||||
"kubernetesURL": "https://10.43.0.1",
|
||||
"binding": "secrets-engine=system:serviceaccount:secrets-engine:secrets-engine",
|
||||
"audience": "access-engine"
|
||||
},
|
||||
"consumer": {
|
||||
"isolated": false,
|
||||
"namespace": "secrets-engine",
|
||||
"podName": "secrets-engine"
|
||||
},
|
||||
"request_cpu": "25m",
|
||||
"request_memory": "32Mi"
|
||||
},
|
||||
{
|
||||
"name": "access-engine-tenant-engine",
|
||||
"image": "forgejo.coulomb.social/coulomb/flex-auth@sha256:05a03a8790c2210c48ea92391441c77ddf640d0cd32f5ec09838f5393171fcbd",
|
||||
"caller_auth": {
|
||||
"mode": "enforce",
|
||||
"kubernetesURL": "https://10.43.0.1",
|
||||
"binding": "tenant-engine=system:serviceaccount:tenant-engine:tenant-engine",
|
||||
"audience": "access-engine"
|
||||
},
|
||||
"consumer": {
|
||||
"isolated": false,
|
||||
"namespace": "tenant-engine",
|
||||
"podName": "tenant-engine"
|
||||
},
|
||||
"request_cpu": "25m",
|
||||
"request_memory": "32Mi"
|
||||
},
|
||||
{
|
||||
"name": "access-engine-user-engine",
|
||||
"image": "forgejo.coulomb.social/coulomb/flex-auth@sha256:138aa3471c46bca6e814691fa1e6520aedda3dffd743e6b09141ab433afdb64b",
|
||||
"caller_auth": {
|
||||
"mode": "enforce",
|
||||
"kubernetesURL": "https://10.43.0.1",
|
||||
"binding": "user-engine=system:serviceaccount:user-engine:user-engine",
|
||||
"audience": "access-engine"
|
||||
},
|
||||
"consumer": {
|
||||
"isolated": false,
|
||||
"namespace": "user-engine",
|
||||
"podName": "user-engine"
|
||||
},
|
||||
"request_cpu": "25m",
|
||||
"request_memory": "32Mi"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -6,6 +6,8 @@ cd "$(dirname "${BASH_SOURCE[0]}")/.."
|
|||
|
||||
python3 tests/validate_app_toml.py
|
||||
python3 tests/test_outbox_chart.py
|
||||
python3 tools/gen_runtime_twins.py --check
|
||||
python3 tests/test_runtime_twins.py
|
||||
|
||||
for values in values/stage1.yaml values/stage2-canary.yaml values/stage3-production.yaml \
|
||||
values/user-engine.yaml values/tenant-engine.yaml values/ops-warden.yaml; do
|
||||
|
|
|
|||
69
tests/test_runtime_twins.py
Normal file
69
tests/test_runtime_twins.py
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Source render integration tests; never contacts the cluster."""
|
||||
import copy
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import unittest
|
||||
import yaml
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
spec = importlib.util.spec_from_file_location("twins", ROOT / "tools/gen_runtime_twins.py")
|
||||
twins = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(twins)
|
||||
|
||||
def render(chart, values, namespace, name):
|
||||
output = subprocess.check_output(["helm", "template", name, str(ROOT / "charts" / chart), "-f", str(ROOT / "values" / values), "--namespace", namespace], text=True)
|
||||
return {obj["kind"]: obj for obj in yaml.safe_load_all(output) if obj}
|
||||
|
||||
def after(args, flag):
|
||||
return [args[i + 1] for i, item in enumerate(args[:-1]) if item == flag]
|
||||
|
||||
class RuntimeTwinTests(unittest.TestCase):
|
||||
def test_actual_nine_twin_render_security_and_content(self):
|
||||
for pin in twins.PINS:
|
||||
with self.subTest(pin=pin):
|
||||
source = yaml.safe_load((ROOT / "values" / (pin + ".yaml")).read_text())
|
||||
new = yaml.safe_load((ROOT / "values/access-engine" / (pin + ".yaml")).read_text())
|
||||
self.assertEqual(new, twins.twin(source))
|
||||
old_objects = render("flex-auth", pin + ".yaml", "flex-auth", source["name"])
|
||||
objects = render("access-engine", "access-engine/" + pin + ".yaml", "access-engine", new["name"])
|
||||
self.assertEqual(set(objects), set(old_objects))
|
||||
for kind in ("Deployment", "Service", "ServiceAccount", "NetworkPolicy"):
|
||||
self.assertEqual(objects[kind]["metadata"]["namespace"], "access-engine")
|
||||
deployment = objects["Deployment"]
|
||||
pod = deployment["spec"]["template"]["spec"]
|
||||
container = pod["containers"][0]
|
||||
old_container = old_objects["Deployment"]["spec"]["template"]["spec"]["containers"][0]
|
||||
self.assertEqual(container["image"], old_container["image"])
|
||||
self.assertEqual(container["args"][:len(source["args"])], source["args"])
|
||||
self.assertEqual(after(container["args"], "--caller-audience"), ["access-engine"])
|
||||
self.assertEqual(after(container["args"], "--caller-binding"), after(old_container["args"], "--caller-binding"))
|
||||
self.assertEqual(after(container["args"], "--readback-caller"), after(old_container["args"], "--readback-caller"))
|
||||
self.assertEqual(pod["serviceAccountName"], new["name"])
|
||||
mount = next(m["mountPath"] for m in container["volumeMounts"] if m["name"] == "access-engine-reviewer")
|
||||
self.assertEqual(after(container["args"], "--caller-reviewer-token-file"), [mount + "/token"])
|
||||
self.assertEqual(after(container["args"], "--caller-ca-file"), [mount + "/ca.crt"])
|
||||
self.assertFalse(pod["automountServiceAccountToken"])
|
||||
self.assertEqual(objects["ClusterRole"]["rules"], old_objects["ClusterRole"]["rules"])
|
||||
binding = objects["ClusterRoleBinding"]
|
||||
self.assertEqual(binding["subjects"], [{"kind": "ServiceAccount", "name": new["name"], "namespace": "access-engine"}])
|
||||
self.assertEqual(binding["roleRef"]["name"], new["name"] + "-tokenreviewer")
|
||||
self.assertEqual(objects["NetworkPolicy"]["spec"]["ingress"], old_objects["NetworkPolicy"]["spec"]["ingress"])
|
||||
self.assertEqual(objects["NetworkPolicy"]["spec"]["egress"], old_objects["NetworkPolicy"]["spec"]["egress"])
|
||||
self.assertEqual(objects["Service"]["spec"]["selector"], deployment["spec"]["selector"]["matchLabels"])
|
||||
self.assertEqual(deployment["spec"]["selector"]["matchLabels"], {"app.kubernetes.io/name": new["name"]})
|
||||
self.assertTrue(container["securityContext"]["readOnlyRootFilesystem"])
|
||||
self.assertFalse(container["securityContext"]["allowPrivilegeEscalation"])
|
||||
self.assertEqual(container["livenessProbe"], old_container["livenessProbe"])
|
||||
self.assertEqual(container["readinessProbe"], old_container["readinessProbe"])
|
||||
|
||||
def test_refuse_unpinned_unenforced_and_shared_writer(self):
|
||||
source = yaml.safe_load((ROOT / "values/user-engine.yaml").read_text())
|
||||
for field, value in (("image", {"digest": "latest"}), ("callerAuth", {"mode": "disabled"}), ("outbox", {"enabled": True}), ("auditDelivery", {"enabled": True}), ("name", "other-pin")):
|
||||
with self.subTest(field=field):
|
||||
bad = copy.deepcopy(source); bad[field] = value
|
||||
with self.assertRaises(ValueError): twins.twin(bad)
|
||||
self.assertNotIn("namespace", source)
|
||||
|
||||
if __name__ == "__main__": unittest.main()
|
||||
49
tools/gen_runtime_twins.py
Normal file
49
tools/gen_runtime_twins.py
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Prepare exact-content runtime twins. Local files only; never applies to Kubernetes.
|
||||
|
||||
Namespace/release/chart/ServiceAccount and caller audience move to access-engine.
|
||||
Image repository+digest and policy/registry argv remain exact existing-twin values
|
||||
until publication/path migration is separately proven.
|
||||
"""
|
||||
from pathlib import Path
|
||||
import argparse
|
||||
import copy
|
||||
import re
|
||||
import yaml
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
PINS = ("ops-warden", "ops-warden-fetch", "secrets-engine", "user-engine", "tenant-engine", "informed-decision-lifecycle", "informed-decision-sitting", "informed-decision-t03", "application-readback")
|
||||
|
||||
def twin(source):
|
||||
result = copy.deepcopy(source)
|
||||
if not re.fullmatch(r"sha256:[0-9a-f]{64}", source.get("image", {}).get("digest", "")):
|
||||
raise ValueError("existing twin must have an immutable SHA256 digest")
|
||||
name = source.get("name", "")
|
||||
if not name.startswith("flex-auth-"):
|
||||
raise ValueError("existing pin must have an exact flex-auth release name")
|
||||
if source.get("callerAuth", {}).get("mode") != "enforce":
|
||||
raise ValueError("new twin requires existing enforced caller authentication")
|
||||
if source.get("outbox", {}).get("enabled") or source.get("auditDelivery", {}).get("enabled"):
|
||||
raise ValueError("durable outbox/audit twins require independent source/custody; never share writer state")
|
||||
result["name"] = "access-engine-" + name[len("flex-auth-"):]
|
||||
result["namespace"] = "access-engine"
|
||||
result["callerAuth"]["audience"] = "access-engine"
|
||||
return result
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--check", action="store_true")
|
||||
args = parser.parse_args()
|
||||
for pin in PINS:
|
||||
source = yaml.safe_load((ROOT / "values" / (pin + ".yaml")).read_text())
|
||||
target = ROOT / "values" / "access-engine" / (pin + ".yaml")
|
||||
result = twin(source)
|
||||
if args.check:
|
||||
if not target.exists() or yaml.safe_load(target.read_text()) != result:
|
||||
raise SystemExit(f"runtime twin drift: {target}")
|
||||
else:
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
target.write_text("# GENERATED by tools/gen_runtime_twins.py; same image/package as existing twin.\n" + yaml.safe_dump(result, sort_keys=False))
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
34
values/access-engine/application-readback.yaml
Normal file
34
values/access-engine/application-readback.yaml
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
# GENERATED by tools/gen_runtime_twins.py; same image/package as existing twin.
|
||||
name: access-engine-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
|
||||
audience: access-engine
|
||||
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
|
||||
namespace: access-engine
|
||||
30
values/access-engine/informed-decision-lifecycle.yaml
Normal file
30
values/access-engine/informed-decision-lifecycle.yaml
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
# GENERATED by tools/gen_runtime_twins.py; same image/package as existing twin.
|
||||
name: access-engine-informed-decision-lifecycle
|
||||
image:
|
||||
repository: forgejo.coulomb.social/coulomb/flex-auth
|
||||
digest: sha256:e8c57c18a4a9d5583be099146984f7e9cd881446218b65aeefd7619b18aa9229
|
||||
args:
|
||||
- serve
|
||||
- --addr
|
||||
- 0.0.0.0:8080
|
||||
- --registry
|
||||
- /opt/flex-auth/examples/informed-decision-lifecycle/registry.json
|
||||
- --policy
|
||||
- /opt/flex-auth/examples/informed-decision-lifecycle/policy.md
|
||||
callerAuth:
|
||||
mode: enforce
|
||||
kubernetesURL: https://10.43.0.1
|
||||
binding: informed-decision=system:serviceaccount:informed-decision:review
|
||||
audience: access-engine
|
||||
consumer:
|
||||
isolated: false
|
||||
namespace: informed-decision
|
||||
podName: informed-decision
|
||||
resources:
|
||||
requests:
|
||||
cpu: 5m
|
||||
memory: 32Mi
|
||||
limits:
|
||||
cpu: 300m
|
||||
memory: 192Mi
|
||||
namespace: access-engine
|
||||
30
values/access-engine/informed-decision-sitting.yaml
Normal file
30
values/access-engine/informed-decision-sitting.yaml
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
# GENERATED by tools/gen_runtime_twins.py; same image/package as existing twin.
|
||||
name: access-engine-informed-decision-sitting
|
||||
image:
|
||||
repository: forgejo.coulomb.social/coulomb/flex-auth
|
||||
digest: sha256:bdac62bc3f089cba0df7c35eeb8d0d9b3af2d4bae5e4ea4767516c1ed20b1e99
|
||||
args:
|
||||
- serve
|
||||
- --addr
|
||||
- 0.0.0.0:8080
|
||||
- --registry
|
||||
- /opt/flex-auth/examples/informed-decision-lifecycle-review/registry.json
|
||||
- --policy
|
||||
- /opt/flex-auth/examples/informed-decision-lifecycle-review/policy.md
|
||||
callerAuth:
|
||||
mode: enforce
|
||||
kubernetesURL: https://10.43.0.1
|
||||
binding: informed-decision=system:serviceaccount:informed-decision:review
|
||||
audience: access-engine
|
||||
consumer:
|
||||
isolated: false
|
||||
namespace: informed-decision
|
||||
podName: informed-decision
|
||||
resources:
|
||||
requests:
|
||||
cpu: 5m
|
||||
memory: 32Mi
|
||||
limits:
|
||||
cpu: 300m
|
||||
memory: 192Mi
|
||||
namespace: access-engine
|
||||
30
values/access-engine/informed-decision-t03.yaml
Normal file
30
values/access-engine/informed-decision-t03.yaml
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
# GENERATED by tools/gen_runtime_twins.py; same image/package as existing twin.
|
||||
name: access-engine-informed-decision-t03
|
||||
image:
|
||||
repository: forgejo.coulomb.social/coulomb/flex-auth
|
||||
digest: sha256:6600c7a62c36dfb4fbb42590722613827d07ff49875b1f0f4b5de63d8351658d
|
||||
args:
|
||||
- serve
|
||||
- --addr
|
||||
- 0.0.0.0:8080
|
||||
- --registry
|
||||
- /opt/flex-auth/examples/informed-decision-t03/registry.json
|
||||
- --policy
|
||||
- /opt/flex-auth/examples/informed-decision-t03/policy.md
|
||||
callerAuth:
|
||||
mode: enforce
|
||||
kubernetesURL: https://10.43.0.1
|
||||
binding: informed-decision=system:serviceaccount:informed-decision:review
|
||||
audience: access-engine
|
||||
consumer:
|
||||
isolated: false
|
||||
namespace: informed-decision
|
||||
podName: informed-decision
|
||||
resources:
|
||||
requests:
|
||||
cpu: 5m
|
||||
memory: 32Mi
|
||||
limits:
|
||||
cpu: 300m
|
||||
memory: 192Mi
|
||||
namespace: access-engine
|
||||
23
values/access-engine/ops-warden-fetch.yaml
Normal file
23
values/access-engine/ops-warden-fetch.yaml
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
# GENERATED by tools/gen_runtime_twins.py; same image/package as existing twin.
|
||||
name: access-engine-ops-warden-fetch
|
||||
image:
|
||||
repository: forgejo.coulomb.social/coulomb/flex-auth
|
||||
digest: sha256:419bc37420d0d0a5304a8587448f1fdbf87117fb041fb43081b0449b4a7bb13b
|
||||
args:
|
||||
- serve
|
||||
- --addr
|
||||
- 0.0.0.0:8080
|
||||
- --registry
|
||||
- /opt/flex-auth/examples/ops-warden-fetch/registry_snapshot.json
|
||||
- --policy
|
||||
- /opt/flex-auth/examples/ops-warden-fetch/policy_package.md
|
||||
callerAuth:
|
||||
mode: enforce
|
||||
kubernetesURL: https://10.43.0.1
|
||||
binding: key-cape=system:serviceaccount:ops-warden:ops-warden
|
||||
extraBindings:
|
||||
- railiance-platform=system:serviceaccount:ops-warden:ops-warden
|
||||
audience: access-engine
|
||||
consumer:
|
||||
isolated: true
|
||||
namespace: access-engine
|
||||
21
values/access-engine/ops-warden.yaml
Normal file
21
values/access-engine/ops-warden.yaml
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
# GENERATED by tools/gen_runtime_twins.py; same image/package as existing twin.
|
||||
name: access-engine-ops-warden
|
||||
image:
|
||||
repository: forgejo.coulomb.social/coulomb/flex-auth
|
||||
digest: sha256:138aa3471c46bca6e814691fa1e6520aedda3dffd743e6b09141ab433afdb64b
|
||||
args:
|
||||
- serve
|
||||
- --addr
|
||||
- 0.0.0.0:8080
|
||||
- --registry
|
||||
- /opt/flex-auth/examples/ops-warden/production_registry_snapshot.json
|
||||
- --policy
|
||||
- /opt/flex-auth/examples/ops-warden/policy_package.md
|
||||
callerAuth:
|
||||
mode: enforce
|
||||
kubernetesURL: https://10.43.0.1
|
||||
binding: ops-warden=system:serviceaccount:ops-warden:ops-warden
|
||||
audience: access-engine
|
||||
consumer:
|
||||
isolated: true
|
||||
namespace: access-engine
|
||||
23
values/access-engine/secrets-engine.yaml
Normal file
23
values/access-engine/secrets-engine.yaml
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
# GENERATED by tools/gen_runtime_twins.py; same image/package as existing twin.
|
||||
name: access-engine-secrets-engine
|
||||
image:
|
||||
repository: forgejo.coulomb.social/coulomb/flex-auth
|
||||
digest: sha256:05a03a8790c2210c48ea92391441c77ddf640d0cd32f5ec09838f5393171fcbd
|
||||
args:
|
||||
- serve
|
||||
- --addr
|
||||
- 0.0.0.0:8080
|
||||
- --registry
|
||||
- /opt/flex-auth/examples/secrets-engine/registry_snapshot.json
|
||||
- --policy
|
||||
- /opt/flex-auth/examples/secrets-engine/policy_package.md
|
||||
callerAuth:
|
||||
mode: enforce
|
||||
kubernetesURL: https://10.43.0.1
|
||||
binding: secrets-engine=system:serviceaccount:secrets-engine:secrets-engine
|
||||
audience: access-engine
|
||||
consumer:
|
||||
isolated: false
|
||||
namespace: secrets-engine
|
||||
podName: secrets-engine
|
||||
namespace: access-engine
|
||||
23
values/access-engine/tenant-engine.yaml
Normal file
23
values/access-engine/tenant-engine.yaml
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
# GENERATED by tools/gen_runtime_twins.py; same image/package as existing twin.
|
||||
name: access-engine-tenant-engine
|
||||
image:
|
||||
repository: forgejo.coulomb.social/coulomb/flex-auth
|
||||
digest: sha256:05a03a8790c2210c48ea92391441c77ddf640d0cd32f5ec09838f5393171fcbd
|
||||
args:
|
||||
- serve
|
||||
- --addr
|
||||
- 0.0.0.0:8080
|
||||
- --registry
|
||||
- /opt/flex-auth/examples/tenant-engine/registry_snapshot.json
|
||||
- --policy
|
||||
- /opt/flex-auth/examples/tenant-engine/policy_package.md
|
||||
callerAuth:
|
||||
mode: enforce
|
||||
kubernetesURL: https://10.43.0.1
|
||||
binding: tenant-engine=system:serviceaccount:tenant-engine:tenant-engine
|
||||
audience: access-engine
|
||||
consumer:
|
||||
isolated: false
|
||||
namespace: tenant-engine
|
||||
podName: tenant-engine
|
||||
namespace: access-engine
|
||||
23
values/access-engine/user-engine.yaml
Normal file
23
values/access-engine/user-engine.yaml
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
# GENERATED by tools/gen_runtime_twins.py; same image/package as existing twin.
|
||||
name: access-engine-user-engine
|
||||
image:
|
||||
repository: forgejo.coulomb.social/coulomb/flex-auth
|
||||
digest: sha256:138aa3471c46bca6e814691fa1e6520aedda3dffd743e6b09141ab433afdb64b
|
||||
args:
|
||||
- serve
|
||||
- --addr
|
||||
- 0.0.0.0:8080
|
||||
- --registry
|
||||
- /opt/flex-auth/examples/user-engine/registry_snapshot.json
|
||||
- --policy
|
||||
- /opt/flex-auth/examples/user-engine/policy_package.md
|
||||
callerAuth:
|
||||
mode: enforce
|
||||
kubernetesURL: https://10.43.0.1
|
||||
binding: user-engine=system:serviceaccount:user-engine:user-engine
|
||||
audience: access-engine
|
||||
consumer:
|
||||
isolated: false
|
||||
namespace: user-engine
|
||||
podName: user-engine
|
||||
namespace: access-engine
|
||||
|
|
@ -181,7 +181,7 @@ args reference.
|
|||
|
||||
```task
|
||||
id: FLEX-WP-0036-T05
|
||||
status: todo
|
||||
status: wait
|
||||
priority: medium
|
||||
depends_on: [FLEX-WP-0036-T03, FLEX-WP-0036-T04]
|
||||
state_hub_task_id: "13b93d7a-8341-5c76-9ae8-0f36b45b10fc"
|
||||
|
|
@ -197,6 +197,24 @@ that each twin pair runs the same digest.
|
|||
Gate: every new pin Ready, `/healthz` 200, anonymous requests 401; founder go.
|
||||
Blocked on `FLEX-WP-0020-T11` closing.
|
||||
|
||||
2026-10-11: native wait qualified once against existing `depends_on` T03/T04:
|
||||
coordinator must prove predecessor image rollout on existing pins before any
|
||||
new namespace/pin creation. No wait-task inspection or new grant was needed.
|
||||
Actual source preparation adds the renamed chart, exact-content generated twins,
|
||||
explicit audience/reviewer paths and render/fixture correspondence checks.
|
||||
Twins retain the exact existing image repository/digest and policy argv; these
|
||||
are migration compatibility details until an identical digest is published under
|
||||
the renamed repository and the baked new paths are proven. No namespace or
|
||||
release has been created by this preparation. Local source preparation is reversible blue work under
|
||||
`the-custodian/canon/standards/build-mode-change-authorization_v0.1.md`.
|
||||
Native rollout creates nine new ServiceAccount subjects bound to cluster-scoped
|
||||
`create` on `authentication.k8s.io/tokenreviews`; matching the prior role's verbs
|
||||
does not make those new principals an unchanged access scope. It is one red
|
||||
access-scope-expansion item in the single daily batch, including exact binding
|
||||
revocation recovery. No per-item founder request is needed. The first prepared
|
||||
blue receipt is preserved as superseded by the classification correction; no
|
||||
native action occurred. T03/T04 and ordered predecessor gates remain unchanged.
|
||||
|
||||
## Consumers switch over, one at a time
|
||||
|
||||
```task
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue