From d710bf00886b6124d86196997643a961f1bede4f Mon Sep 17 00:00:00 2001 From: tegwick Date: Mon, 14 Sep 2026 01:08:50 +0200 Subject: [PATCH] Deploy approval service and verify native persistence and audit Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a09cbb-87c6-7900-a145-4ce53ba9f1a6 --- Makefile | 2 +- deploy/README.md | 30 +++++-- deploy/approval-engine.yaml | 3 +- deploy/keycape-ingress.yaml | 21 +++++ ...2026-09-14-approval-engine-deployment.json | 84 +++++++++++++++++++ ...duction-readiness-and-consumer-adoption.md | 19 ++++- 6 files changed, 151 insertions(+), 8 deletions(-) create mode 100644 deploy/keycape-ingress.yaml create mode 100644 docs/evidence/2026-09-14-approval-engine-deployment.json diff --git a/Makefile b/Makefile index 192ec65..1361fa5 100644 --- a/Makefile +++ b/Makefile @@ -23,7 +23,7 @@ image-release: image-build image-scan ## Build, scan, then push. Push only runs @docker inspect --format '{{index .RepoDigests 0}}' $(IMAGE):$(VERSION) deploy-dry-run: ## Validate Kubernetes manifests without applying them - kubectl apply --dry-run=client -f deploy/approval-engine.yaml -f deploy/networkpolicies.yaml + kubectl apply --dry-run=client -f deploy/approval-engine.yaml -f deploy/networkpolicies.yaml -f deploy/keycape-ingress.yaml help: ## Show this help @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} \ diff --git a/deploy/README.md b/deploy/README.md index 77e4b38..6b370cc 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -1,11 +1,11 @@ # Deployment gates -The checked-in StatefulSet is a reviewed release input, not evidence of a live -deployment. SQLite is intentionally limited to one replica, `ReadWriteOnce` +The StatefulSet is deployed as of 2026-09-14. The metadata receipt is +`docs/evidence/2026-09-14-approval-engine-deployment.json`. SQLite is intentionally limited to one replica, `ReadWriteOnce` storage, and an `OnDelete` update: never start two writers against a copied database. -**Image: pinned, published, not rolled out.** Both `image:` references carry +**Image: pinned, published and deployed.** Both `image:` references carry `sha256:251941a5cb2724b57cc32cff6b693b1ab0be695bee4f56f02d51961189c0fa49` (registry tag `0.1.0-hfact-be1a388`, source `be1a388`). Both references MUST stay identical immutable digests: migration and server share one database. @@ -20,9 +20,29 @@ This is artifact evidence; native identity, audit delivery and production restor remain deployment gates. Never roll the old v3 image over a v5 database; use the matching verified pre-migration backup for rollback. -Gates 1 and 2 below are the outstanding ones; nothing is deployed today. +The existing KeyCape consumer registration and CCR-2026-0021 audit projection +were already verified by their owners. Live inspection confirmed the projection +Ready, exact key name, receiver c82e0442 and existing producer ingress; custody +was reused without reading or rotating its value. Added the exact namespace AND +pod-label ingress to KeyCape for JWKS (`deploy/keycape-ingress.yaml`). -Before applying: +The pod requests 25m CPU/64Mi with unchanged 500m/256Mi limits: the node had +3975m of its 4000m CPU reserved, so the prior 50m request could not schedule. +Initial native observation was 1m CPU/20Mi. No other workload was resized. +Kubernetes token automount is disabled; the service uses no Kubernetes API. + +The live service passed schema-v5 integrity, current JWKS reachability, anonymous +and invalid-bearer 401, first heartbeat delivery with zero pending outbox, +pod replacement with unchanged persisted outbox, verified online backup and an +isolated restore opened by the actual engine. An independent 0600 backup copy +was inspected locally. No live database was overwritten. The initial backup +copies are test evidence, not a long-term backup schedule or disaster-recovery +acceptance. Capacity and independent retention remain operational follow-up. + +The real PEP adoption is still T05: CCR-2026-0019's operator group/reader, +requesting identity and real human approval are not supplied by deployment. + +For a future reinstall or release: 1. Register the exact `approval-engine` audience, caller scopes, and service clients in KeyCape. Confirm the in-cluster JWKS endpoint and configured diff --git a/deploy/approval-engine.yaml b/deploy/approval-engine.yaml index 601785a..8e1b534 100644 --- a/deploy/approval-engine.yaml +++ b/deploy/approval-engine.yaml @@ -35,6 +35,7 @@ spec: labels: app.kubernetes.io/name: approval-engine spec: + automountServiceAccountToken: false securityContext: runAsNonRoot: true runAsUser: 10001 @@ -78,7 +79,7 @@ spec: ports: - {name: http, containerPort: 8080} resources: - requests: {cpu: 50m, memory: 64Mi} + requests: {cpu: 25m, memory: 64Mi} limits: {cpu: 500m, memory: 256Mi} securityContext: allowPrivilegeEscalation: false diff --git a/deploy/keycape-ingress.yaml b/deploy/keycape-ingress.yaml new file mode 100644 index 0000000..6fb8843 --- /dev/null +++ b/deploy/keycape-ingress.yaml @@ -0,0 +1,21 @@ +# APPROVAL-WP-0002-T03 / SECRETS-WP-0010-T03: exact verifier JWKS peer. +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-approval-engine-to-keycape + namespace: sso +spec: + podSelector: + matchLabels: + app.kubernetes.io/name: keycape + policyTypes: [Ingress] + ingress: + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: approval-engine + podSelector: + matchLabels: + app.kubernetes.io/name: approval-engine + ports: + - {protocol: TCP, port: 8080} diff --git a/docs/evidence/2026-09-14-approval-engine-deployment.json b/docs/evidence/2026-09-14-approval-engine-deployment.json new file mode 100644 index 0000000..319174d --- /dev/null +++ b/docs/evidence/2026-09-14-approval-engine-deployment.json @@ -0,0 +1,84 @@ +{ + "storage": { + "schema_version": 5, + "expected_schema_version": 5, + "schema_current": true, + "persistent": true, + "required_tables": { + "approvals": true, + "entries": true, + "outbox": true + }, + "integrity": [ + "ok" + ], + "foreign_key_violations": 0, + "ok": true + }, + "outbox": { + "total": 1, + "pending": 0, + "max_age_seconds": 0, + "attempts": 1, + "failed_pending": 0, + "counts": { + "issuance": 0, + "use": 0, + "supersession": 0, + "revocation": 0, + "heartbeat": 1 + } + }, + "restored": { + "schema_version": 5, + "expected_schema_version": 5, + "schema_current": true, + "persistent": true, + "required_tables": { + "approvals": true, + "entries": true, + "outbox": true + }, + "integrity": [ + "ok" + ], + "foreign_key_violations": 0, + "ok": true + }, + "readiness": 200, + "anonymous_refusal": 401, + "invalid_bearer_refusal": 401, + "jwks_reachable": true, + "timestamp": "2026-09-13T23:06:37.566144+00:00", + "status": "deployed_and_restart_restore_verified", + "cluster_uid": "a553c742-0115-43d4-99a4-a5ca56fe0786", + "pod_uid_before_restart": "95215659-3b17-4ccf-ab8c-98418439cdbe", + "pod_uid_after_restart": "7b8c05d9-f1d5-403d-abb3-2250bff9db5e", + "image": "forgejo.coulomb.social/coulomb/approval-engine@sha256:251941a5cb2724b57cc32cff6b693b1ab0be695bee4f56f02d51961189c0fa49", + "resources": { + "limits": { + "cpu": "500m", + "memory": "256Mi" + }, + "requests": { + "cpu": "25m", + "memory": "64Mi" + } + }, + "caller_token_automount": false, + "tests": { + "repository": 156, + "changed_manifest": 6 + }, + "backup": { + "persistent_path": "/data/t03-initial-backup-20260914.sqlite", + "independent_local_copy": "/tmp/t03-approval-backup/restored.sqlite", + "restore_exercise": "isolated copy opened with actual engine; live database was not overwritten", + "retention": "initial test backups only; long-term backup schedule/retention not established" + }, + "limitations": [ + "No real approval created, human entry supplied, or credential consumed.", + "Consumer client-side reader and requesting identity remain unadmitted.", + "Heartbeat acknowledgment/outbox persistence proved; independent audit archive retrieval remains Audit Core owner work." + ] +} diff --git a/workplans/APPROVAL-WP-0002-production-readiness-and-consumer-adoption.md b/workplans/APPROVAL-WP-0002-production-readiness-and-consumer-adoption.md index b26d6c5..4d1059f 100644 --- a/workplans/APPROVAL-WP-0002-production-readiness-and-consumer-adoption.md +++ b/workplans/APPROVAL-WP-0002-production-readiness-and-consumer-adoption.md @@ -342,11 +342,28 @@ retry-attempt state, and migration/backup/atomicity tests are in place. ```task id: APPROVAL-WP-0002-T03 -status: wait +status: done priority: high state_hub_task_id: "f0aa2e6d-19e6-5b43-886c-efa4e3de5f22" ``` +**Completed 2026-09-14 during SECRETS-WP-0010-T03.** Existing service registration +and audit custody had already passed owner acceptance; their stale absence was +not carried forward. Deployed the pinned schema-v5 image with the existing ESO +projection. Added exact KeyCape JWKS ingress, disabled unused SA token mounting, +and set a 25m CPU request to fit the node's measured remaining reservation +(500m limit unchanged; observed 1m CPU/20Mi). All 156 tests and six changed-manifest +checks pass. Native ready/schema/integrity/JWKS, anonymous/invalid-bearer refusal, +heartbeat acknowledgment and zero-pending outbox pass. Replaced the pod and +verified persistence; made an online 0600 backup, independently copied/checked +it and opened an isolated restore with the actual engine. Live DB not overwritten. + +Receipt: `docs/evidence/2026-09-14-approval-engine-deployment.json`. T05 retains +the real PEP claim/check/consume and human/caller admission. Long-term backup +retention and independent receiver archive evidence remain operational/owner +follow-up, not implied by this initial restore exercise. + + Add the governed image/deployment surface, health and readiness behavior, resource bounds, and fail-closed caller configuration. A local WSGI development server is not production evidence.