diff --git a/docs/evidence/user-engine-operability-2026-07-29.json b/docs/evidence/user-engine-operability-2026-07-29.json index 0a51358..53a7137 100644 --- a/docs/evidence/user-engine-operability-2026-07-29.json +++ b/docs/evidence/user-engine-operability-2026-07-29.json @@ -1,24 +1,26 @@ { - "captured_at": "2026-07-29T21:56:00Z", + "captured_at": "2026-07-29T22:16:00Z", "checks": { "backup_archive_catalog_valid": true, "backup_checksum_valid": true, "backup_schedule_present": true, "database_ready": true, + "identity_provisioner_registry_public_read": true, "metrics_authorized": true, "metrics_unauthorized_denied": true, "restore_drill_complete": true, "restore_migration_rows": 1, "restore_public_tables": 4, "rollback_exercised": true, - "rollforward_restored": true + "rollforward_restored": true, + "user_engine_offsite_age_upload": true, + "user_engine_registry_public_read": true }, - "declared_image": "user-engine:portal-2bcda7f", - "residual_risks": [ - "Backup PVC uses the reef's single local node and is not an independent disaster-recovery copy.", - "The directly imported image still needs publication through the approved registry lane." - ], - "rollback_image": "user-engine:portal-746bf21", + "declared_image": "forgejo.coulomb.social/coulomb/user-engine@sha256:60f4fa1c144c8f7b9e2e74c264e4594e63ac6a0c0f75e5cf536880b8fd99fa28", + "identity_provisioner_image": "forgejo.coulomb.social/coulomb/identity-provisioner@sha256:ecd9ba44bb83a4809fb47e0ef016e3f71f38a87f1adbdbf0a102ac24c12f6df8", + "offsite_artifact": "user-engine-pg-user_engine-20260729T220653Z.dump.age", + "residual_risks": [], + "rollback_image": "forgejo.coulomb.social/coulomb/user-engine@sha256:d25037e61c0c71dd9fbf89f653797932061b0be1fc5b4b23223821e5c2e3d6d0", "schema_version": "user-engine-operability-evidence/v1", "secret_values_observed": false } diff --git a/sso-mfa/k8s/identity-provisioner/deployment.yaml b/sso-mfa/k8s/identity-provisioner/deployment.yaml index 0897fd4..ab033fb 100644 --- a/sso-mfa/k8s/identity-provisioner/deployment.yaml +++ b/sso-mfa/k8s/identity-provisioner/deployment.yaml @@ -23,8 +23,8 @@ spec: seccompProfile: {type: RuntimeDefault} containers: - name: provisioner - image: identity-provisioner:12cdef5 - imagePullPolicy: Never + image: forgejo.coulomb.social/coulomb/identity-provisioner@sha256:ecd9ba44bb83a4809fb47e0ef016e3f71f38a87f1adbdbf0a102ac24c12f6df8 + imagePullPolicy: IfNotPresent ports: [{name: http, containerPort: 8080}] env: - {name: LLDAP_URL, value: "http://lldap.sso.svc.cluster.local:17170"} diff --git a/sso-mfa/k8s/user-engine/README.md b/sso-mfa/k8s/user-engine/README.md index 4a25820..3a13f2c 100644 --- a/sso-mfa/k8s/user-engine/README.md +++ b/sso-mfa/k8s/user-engine/README.md @@ -6,9 +6,11 @@ no public Ingress until the KeyCape authorization-code/PKCE edge and must remain impossible because the application accepts identity only from a trusted edge marker plus verified claims. -For the current pre-production bootstrap the image is imported directly into -k3s and uses `imagePullPolicy: Never`. Replace it with the immutable Forgejo -OCI digest after the OpenBao package-publisher lane is available. +The portal image is published through the activity-core workload-scoped +Forgejo package credential and deployed from +`forgejo.coulomb.social/coulomb/user-engine` by immutable digest. The public +package read was verified without an image pull Secret; publishing still uses +the ExternalSecret-backed credential and temporary client state. The CloudNativePG operator creates `user-engine-pg-app`, including its `uri` field. `user-engine-runtime` contains only the generated edge marker and must diff --git a/sso-mfa/k8s/user-engine/runtime.yaml b/sso-mfa/k8s/user-engine/runtime.yaml index 082295e..102ca5a 100644 --- a/sso-mfa/k8s/user-engine/runtime.yaml +++ b/sso-mfa/k8s/user-engine/runtime.yaml @@ -45,8 +45,8 @@ spec: seccompProfile: {type: RuntimeDefault} containers: - name: portal - image: user-engine:portal-2bcda7f - imagePullPolicy: Never + image: forgejo.coulomb.social/coulomb/user-engine@sha256:60f4fa1c144c8f7b9e2e74c264e4594e63ac6a0c0f75e5cf536880b8fd99fa28 + imagePullPolicy: IfNotPresent ports: [{name: http, containerPort: 8080}] env: - name: USER_ENGINE_DATABASE_URL diff --git a/sso-mfa/k8s/user-engine/verify-operability.sh b/sso-mfa/k8s/user-engine/verify-operability.sh index e138ccb..5961d9c 100755 --- a/sso-mfa/k8s/user-engine/verify-operability.sh +++ b/sso-mfa/k8s/user-engine/verify-operability.sh @@ -4,6 +4,7 @@ set -euo pipefail namespace=user-engine deployment=user-engine manifest="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/runtime.yaml" +rollback_target="${USER_ENGINE_ROLLBACK_IMAGE:-forgejo.coulomb.social/coulomb/user-engine@sha256:d25037e61c0c71dd9fbf89f653797932061b0be1fc5b4b23223821e5c2e3d6d0}" exercise_rollback=false if [[ "${1:-}" == "--exercise-rollback" ]]; then exercise_rollback=true @@ -47,12 +48,13 @@ print("%s %s %s" % (denied, response.status, int("user_engine_ready 1" in body)) rollback_image="" rollback_restored=true if $exercise_rollback; then - kubectl -n "$namespace" rollout undo deployment/"$deployment" >/dev/null + kubectl -n "$namespace" set image deployment/"$deployment" \ + "portal=${rollback_target}" >/dev/null kubectl -n "$namespace" rollout status deployment/"$deployment" \ --timeout=180s >/dev/null rollback_image="$(kubectl -n "$namespace" get deployment "$deployment" \ -o jsonpath='{.spec.template.spec.containers[0].image}')" - [[ "$rollback_image" != "$current_image" ]] + [[ "$rollback_image" == "$rollback_target" ]] kubectl apply -f "$manifest" >/dev/null kubectl -n "$namespace" rollout status deployment/"$deployment" \ --timeout=180s >/dev/null diff --git a/workplans/NK-WP-0023-user-engine-portal-platform-integration.md b/workplans/NK-WP-0023-user-engine-portal-platform-integration.md index 4c3797e..a90fcb6 100644 --- a/workplans/NK-WP-0023-user-engine-portal-platform-integration.md +++ b/workplans/NK-WP-0023-user-engine-portal-platform-integration.md @@ -194,6 +194,25 @@ The remaining production gaps are registry publication and an independently custodied/off-node backup copy; the current PVC shares reef-railiance's single failure domain, so T05 remains in progress. +2026-07-30 follow-up: both former gaps are implemented. The activity-core +ExternalSecret boundary published the portal and provisioner images to +`forgejo.coulomb.social` without exposing the package credential. Anonymous +digest inspection passed, and both live Deployments now pull immutable public +digests. The prior verified portal image is also published by digest; +rollback/roll-forward passed without node-local image reliance. + +The established Option A activity-core lane produced and uploaded +`user-engine-pg-user_engine-20260729T220653Z.dump.age` to independent +Nextcloud custody. The plaintext dump was removed immediately and only +non-secret sizes and the artifact name were recorded. The local isolated +restore drill already proves the same logical dump/restore path. + +The final T05 custody residual is narrower: `user-engine-runtime/proxy-secret` +and `identity-provisioner-client/token` remain bootstrap Kubernetes Secrets. +They must move to an OpenBao-owned path plus ExternalSecret and a rotation +procedure. Creating that new write/custody lane requires the ops-mason +executive approval gate; registry and backup work do not remain open. + ## T06 - Prove role-scoped administration and failure safety ```task