diff --git a/Makefile b/Makefile index 28732f2..7b24eb8 100644 --- a/Makefile +++ b/Makefile @@ -45,6 +45,8 @@ POLICY_NEXUS_TARGET ?= railiance01 POLICY_NEXUS_IMAGE_REPOSITORY ?= $(shell python3 -c 'import json; print(json.load(open("$(POLICY_NEXUS_BINDING)"))["image_repository"])') POLICY_NEXUS_IMAGE_DIGEST ?= $(shell python3 -c 'import json; print(json.load(open("$(POLICY_NEXUS_BINDING)")).get("image_digest") or "")') POLICY_NEXUS_PUBLICATION_DIGEST ?= $(shell python3 -c 'import json; print(json.load(open("$(POLICY_NEXUS_BINDING)")).get("publication_manifest_digest") or "")') +POLICY_NEXUS_SOURCE_INVENTORY_DIGEST ?= $(shell python3 -c 'import json; print(json.load(open("$(POLICY_NEXUS_BINDING)")).get("source_inventory_digest") or "")') +POLICY_NEXUS_SOURCE_SET_DIGEST ?= $(shell python3 -c 'import json; print(json.load(open("$(POLICY_NEXUS_BINDING)")).get("source_set_digest") or "")') POLICY_NEXUS_REVISION ?= COULOMBCORE_KUBECONFIG ?= $(HOME)/.kube/config @@ -598,19 +600,25 @@ policy-nexus-render: policy-nexus-check-binding ## Render the rApp chart with th $(MAKE) -C "$(POLICY_NEXUS_RAPP)" render \ IMAGE_REPOSITORY="$(POLICY_NEXUS_IMAGE_REPOSITORY)" \ IMAGE_DIGEST="$(POLICY_NEXUS_IMAGE_DIGEST)" \ - PUBLICATION_MANIFEST_DIGEST="$(POLICY_NEXUS_PUBLICATION_DIGEST)" + PUBLICATION_MANIFEST_DIGEST="$(POLICY_NEXUS_PUBLICATION_DIGEST)" \ + SOURCE_INVENTORY_DIGEST="$(POLICY_NEXUS_SOURCE_INVENTORY_DIGEST)" \ + SOURCE_SET_DIGEST="$(POLICY_NEXUS_SOURCE_SET_DIGEST)" policy-nexus-server-dry-run: policy-nexus-check-binding ## Validate the approved policy package against Railiance01 $(MAKE) -C "$(POLICY_NEXUS_RAPP)" server-dry-run TARGET="$(POLICY_NEXUS_TARGET)" \ IMAGE_REPOSITORY="$(POLICY_NEXUS_IMAGE_REPOSITORY)" \ IMAGE_DIGEST="$(POLICY_NEXUS_IMAGE_DIGEST)" \ - PUBLICATION_MANIFEST_DIGEST="$(POLICY_NEXUS_PUBLICATION_DIGEST)" + PUBLICATION_MANIFEST_DIGEST="$(POLICY_NEXUS_PUBLICATION_DIGEST)" \ + SOURCE_INVENTORY_DIGEST="$(POLICY_NEXUS_SOURCE_INVENTORY_DIGEST)" \ + SOURCE_SET_DIGEST="$(POLICY_NEXUS_SOURCE_SET_DIGEST)" policy-nexus-deploy: policy-nexus-check-binding ## Deploy the approved policy release through its rApp package $(MAKE) -C "$(POLICY_NEXUS_RAPP)" deploy TARGET="$(POLICY_NEXUS_TARGET)" \ IMAGE_REPOSITORY="$(POLICY_NEXUS_IMAGE_REPOSITORY)" \ IMAGE_DIGEST="$(POLICY_NEXUS_IMAGE_DIGEST)" \ - PUBLICATION_MANIFEST_DIGEST="$(POLICY_NEXUS_PUBLICATION_DIGEST)" + PUBLICATION_MANIFEST_DIGEST="$(POLICY_NEXUS_PUBLICATION_DIGEST)" \ + SOURCE_INVENTORY_DIGEST="$(POLICY_NEXUS_SOURCE_INVENTORY_DIGEST)" \ + SOURCE_SET_DIGEST="$(POLICY_NEXUS_SOURCE_SET_DIGEST)" policy-nexus-status: ## Show the policy-nexus Helm release state $(MAKE) -C "$(POLICY_NEXUS_RAPP)" status TARGET="$(POLICY_NEXUS_TARGET)" @@ -619,7 +627,9 @@ policy-nexus-smoke: policy-nexus-check-binding ## Verify public content, TLS, an $(MAKE) -C "$(POLICY_NEXUS_RAPP)" verify-live TARGET="$(POLICY_NEXUS_TARGET)" \ IMAGE_REPOSITORY="$(POLICY_NEXUS_IMAGE_REPOSITORY)" \ IMAGE_DIGEST="$(POLICY_NEXUS_IMAGE_DIGEST)" \ - PUBLICATION_MANIFEST_DIGEST="$(POLICY_NEXUS_PUBLICATION_DIGEST)" + PUBLICATION_MANIFEST_DIGEST="$(POLICY_NEXUS_PUBLICATION_DIGEST)" \ + SOURCE_INVENTORY_DIGEST="$(POLICY_NEXUS_SOURCE_INVENTORY_DIGEST)" \ + SOURCE_SET_DIGEST="$(POLICY_NEXUS_SOURCE_SET_DIGEST)" policy-nexus-rollback: ## Roll back to a named previous Helm revision @test -n "$(POLICY_NEXUS_REVISION)" || { echo "POLICY_NEXUS_REVISION is required" >&2; exit 2; } diff --git a/bindings/policy-nexus-production.json b/bindings/policy-nexus-production.json index 85ec028..553a4f7 100644 --- a/bindings/policy-nexus-production.json +++ b/bindings/policy-nexus-production.json @@ -9,7 +9,9 @@ "release": "policy-nexus", "hostname": "policy.coulomb.social", "image_repository": "forgejo.coulomb.social/coulomb/policy-nexus", - "image_digest": "sha256:23318442d1b3a5bdced54f0b407fa5b32834020b80c60d72dea04c03bfbf51e7", - "publication_manifest_digest": "1d8ae5cd683e385d82d29a78729948f7be95d902401a5fbdcfb5e8230cdbfee6", + "image_digest": "sha256:0d4786096ef557c3edb12da34a15ad28c6fdf5ccc6d204ffa0d9122cc421cf95", + "publication_manifest_digest": "f8c4d52fca28c3ce0925f9295b8cd6d27714761fe7b55a48e7f53b2cbae5ad43", + "source_inventory_digest": "6635743cdc73e182373a3e050cc6548ba3daecbe8daa3a0dd778013f1590ce00", + "source_set_digest": "dfb7fbc15f3da6656807abf305857345209eecbc3ff6c05cb43b0b54b9f94163", "residual_risk_owner": "the-custodian" } diff --git a/docs/policy-nexus-production.md b/docs/policy-nexus-production.md index ab435a3..fd33d54 100644 --- a/docs/policy-nexus-production.md +++ b/docs/policy-nexus-production.md @@ -9,17 +9,21 @@ the chart. ## Binding a release The source-controlled binding is -`bindings/policy-nexus-production.json`. Before deployment, replace both null -values and set `status` to `release-approved`: +`bindings/policy-nexus-production.json`. Before deployment, record all four +candidate identities and set `status` to `release-approved`: - `image_digest`: the registry-resolved `sha256:...` digest of the image published by `policy-nexus`; - `publication_manifest_digest`: the 64-hex SHA-256 printed by the release verifier for the exact `publication-manifest.json` in that image. +- `source_inventory_digest`: the 64-hex SHA-256 of the exact + `source-inventory.json` in that image; +- `source_set_digest`: the deterministic 64-hex digest of every upstream + repository revision audited for that candidate. -Tags are not accepted as production identity. The image digest and publication -digest must be reviewed in the same change; this binds runtime bytes to policy -source provenance without making this repository a second policy authority. +Tags are not accepted as production identity. All four identities must be +reviewed in the same change; this binds runtime bytes to policy source +provenance without making this repository a second policy authority. ## Preflight and deployment @@ -51,6 +55,6 @@ make policy-nexus-rollback POLICY_NEXUS_REVISION= make policy-nexus-smoke ``` -After an emergency rollback, restore the binding file to the two immutable +After an emergency rollback, restore the binding file to the four immutable identities actually serving and capture the smoke output. The package promises restart recovery on the single-node reef, not multi-node high availability. diff --git a/tools/check-policy-nexus-binding.py b/tools/check-policy-nexus-binding.py index 98af389..325d075 100644 --- a/tools/check-policy-nexus-binding.py +++ b/tools/check-policy-nexus-binding.py @@ -25,6 +25,12 @@ def main() -> int: r"[a-f0-9]{64}", binding.get("publication_manifest_digest") or "" ): parser.error("binding publication_manifest_digest must be 64 lowercase hex") + if not re.fullmatch( + r"[a-f0-9]{64}", binding.get("source_inventory_digest") or "" + ): + parser.error("binding source_inventory_digest must be 64 lowercase hex") + if not re.fullmatch(r"[a-f0-9]{64}", binding.get("source_set_digest") or ""): + parser.error("binding source_set_digest must be 64 lowercase hex") if binding.get("hostname") != "policy.coulomb.social": parser.error("binding hostname must be policy.coulomb.social")