From c9915434d815be3d001f3aa1e9bf8af014443ab3 Mon Sep 17 00:00:00 2001 From: "courtmanr@gmail.com" Date: Tue, 29 Sep 2026 17:12:27 +0100 Subject: [PATCH] Let the RC attester read the lab's root-owned evidence With the host-interface canary in place, v6.4.5-rc.4 passed the schema-v7 systemd and rootful Docker lab (run 36594651903), but the attestation step then failed with Permission denied on receipt.json, which the privileged lab writes as root into the bind-mounted evidence directory. Hand that directory to the runner user after a passing lab and before attestation, without touching its content. --- .github/workflows/qualify-secure-runtime-release.yml | 5 +++++ .../v6/internal/subsystems/deployment-installability.md | 4 ++++ scripts/installtests/build_release_assets_test.go | 6 ++++++ 3 files changed, 15 insertions(+) diff --git a/.github/workflows/qualify-secure-runtime-release.yml b/.github/workflows/qualify-secure-runtime-release.yml index 348c337f31..659aa475e1 100644 --- a/.github/workflows/qualify-secure-runtime-release.yml +++ b/.github/workflows/qualify-secure-runtime-release.yml @@ -331,6 +331,11 @@ jobs: --env "PULSE_SECURE_RUNTIME_TRANSCRIPT_RECORD_PATH=docs/release-control/v6/internal/records/secure-agent-runtime-systemd-transcript-v7-${TAG}.jsonl" \ "${container}" \ sh -c 'cd /workspace/scripts/installtests && exec /harness/installtests-linux-amd64.test -test.run "^TestSecureRuntimeSystemdDockerV7Lab$" -test.count=1 -test.v' + # The lab writes its receipt and transcript as root inside the + # privileged host. Hand the evidence directory to the runner user so + # the attester can read it and write the attestation beside it. The + # evidence content itself is not touched. + docker exec "${container}" chown -R "$(id -u):$(id -g)" /evidence elapsed="$(( $(date +%s) - start_epoch ))" if [[ "${elapsed}" -lt 1 ]]; then elapsed=1; fi echo "elapsed_seconds=${elapsed}" >> "$GITHUB_OUTPUT" diff --git a/docs/release-control/v6/internal/subsystems/deployment-installability.md b/docs/release-control/v6/internal/subsystems/deployment-installability.md index 2520ea971c..96cff70a01 100644 --- a/docs/release-control/v6/internal/subsystems/deployment-installability.md +++ b/docs/release-control/v6/internal/subsystems/deployment-installability.md @@ -1304,6 +1304,10 @@ artifact-selection behaviour. helper's private network isolation. Canary setup and the no-default-route check must fail qualification before that lab if the boundary is absent; they must not skip the lab or change the release packet under test. + The lab writes its receipt and transcript as root inside that container. + After a passing lab and before attestation, the workflow must hand the + evidence directory to the runner user without altering its content, so the + attester can read the receipt and write the attestation beside it. PVE jobs must consume their runner users' persistent local Go and npm caches directly; disposable-runner Actions cache restore/save phases must remain disabled because archiving those same caches adds network work after the diff --git a/scripts/installtests/build_release_assets_test.go b/scripts/installtests/build_release_assets_test.go index 96e889fd40..1f8fca5a6a 100644 --- a/scripts/installtests/build_release_assets_test.go +++ b/scripts/installtests/build_release_assets_test.go @@ -2856,6 +2856,7 @@ func TestSecureRuntimeQualificationPacketIsHostedAndReleaseBound(t *testing.T) { `ip link add pulse-can0 type veth peer name pulse-can1`, `ip address add 192.0.2.1/32 dev pulse-can0`, `test -z "$(ip -4 route show default)"`, + `docker exec "${container}" chown -R "$(id -u):$(id -g)" /evidence`, `pulse-secure-runtime-fixture:v7`, "--verify-release-packet-only", "--verified-packet-dir", @@ -2878,6 +2879,11 @@ func TestSecureRuntimeQualificationPacketIsHostedAndReleaseBound(t *testing.T) { if canaryIndex <= privilegedExecutionIndex || labIndex <= canaryIndex { t.Fatal("the isolated host-interface canary must be configured before running the immutable RC lab") } + ownershipIndex := strings.Index(qualificationWorkflow, `docker exec "${container}" chown -R "$(id -u):$(id -g)" /evidence`) + attestIndex := strings.Index(qualificationWorkflow, `--output "${evidence_dir}/attestation.json"`) + if ownershipIndex <= labIndex || attestIndex <= ownershipIndex { + t.Fatal("the lab evidence must be handed to the runner user after the lab and before attestation") + } if !strings.Contains(qualificationWorkflow, `--privileged \ --network none \ --cgroupns=host`) {