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.
This commit is contained in:
courtmanr@gmail.com
2026-09-29 17:12:27 +01:00
parent 9e8dc122be
commit c9915434d8
3 changed files with 15 additions and 0 deletions
@@ -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"
@@ -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
@@ -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`) {