mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-10 02:25:56 +00:00
Persist rootless qualification receipts
This commit is contained in:
@@ -7338,6 +7338,9 @@ standalone `secure-runtime-rootless-v1` receipt only after exact socket
|
||||
ownership, daemon rootless attestation, installer pinning, direct telemetry,
|
||||
same-family typed-helper fallback, recovery without collector restart,
|
||||
ambiguity refusal, authority isolation, and cleanup are recorded.
|
||||
Receipt output uses a dedicated root-owned, mode-`0700` `/opt/pulse/result`
|
||||
boundary instead of volatile `/run` state, so systemd user-manager teardown
|
||||
cannot erase a passing result before the wrapper retains it.
|
||||
The wrapper must compile every Go artifact with mandatory VCS stamping so a
|
||||
missing revision or unavailable clean-worktree proof fails before live evidence
|
||||
can be emitted; the receipt and validator retain the exact artifact hashes and
|
||||
|
||||
@@ -261,7 +261,10 @@ network-isolated Ubuntu/systemd hosts for real rootless Docker and Podman,
|
||||
never mounts a host daemon socket, and records each host identity separately.
|
||||
The exact qualification packet remains outside the image layers; the image
|
||||
must pre-create its root-owned, mode-`0700` packet destination before the
|
||||
wrapper injects artifacts into each stopped disposable container.
|
||||
wrapper injects artifacts into each stopped disposable container. The image
|
||||
must also pre-create the separate root-owned, mode-`0700` `/opt/pulse/result`
|
||||
output boundary; receipts are written there instead of volatile `/run` state so
|
||||
systemd user-manager cleanup cannot erase completed qualification evidence.
|
||||
Rootless Docker uses its supported `slirp4netns` driver inside the outer
|
||||
`--network none` container only; an unsupported `host` RootlessKit driver or
|
||||
an outer default route invalidates the proof. The disposable host uses a
|
||||
|
||||
@@ -38,6 +38,8 @@ const (
|
||||
rootlessQualOptIn = "PULSE_SECURE_RUNTIME_ROOTLESS_QUALIFICATION"
|
||||
rootlessQualOptInValue = "disposable-v1"
|
||||
rootlessQualMarker = "/etc/pulse-secure-runtime-rootless-qualification"
|
||||
rootlessQualReceiptPath = "/opt/pulse/result/rootless-receipt.json"
|
||||
rootlessQualResultDir = "/opt/pulse/result"
|
||||
rootlessQualFixture = "pulse-rootless-qualification-fixture:v1"
|
||||
rootlessQualRunningName = "pulse-rootless-running"
|
||||
rootlessQualExitedName = "pulse-rootless-exited"
|
||||
@@ -520,8 +522,16 @@ func rootlessQualRequireDisposableHost(t *testing.T, runtimeKind, receiptPath st
|
||||
if err != nil || strings.TrimSpace(string(marker)) != rootlessQualOptInValue {
|
||||
t.Fatalf("disposable marker is absent or invalid: %v", err)
|
||||
}
|
||||
if !filepath.IsAbs(receiptPath) || filepath.Clean(receiptPath) != receiptPath {
|
||||
t.Fatalf("PULSE_ROOTLESS_RECEIPT must be an exact absolute path: %q", receiptPath)
|
||||
if receiptPath != rootlessQualReceiptPath {
|
||||
t.Fatalf("PULSE_ROOTLESS_RECEIPT must use the dedicated durable output path %q: %q", rootlessQualReceiptPath, receiptPath)
|
||||
}
|
||||
resultInfo, err := os.Lstat(rootlessQualResultDir)
|
||||
if err != nil {
|
||||
t.Fatalf("inspect rootless qualification result directory: %v", err)
|
||||
}
|
||||
resultStat, ok := resultInfo.Sys().(*syscall.Stat_t)
|
||||
if !ok || !resultInfo.IsDir() || resultInfo.Mode()&os.ModeSymlink != 0 || resultInfo.Mode().Perm() != 0o700 || resultStat.Uid != 0 {
|
||||
t.Fatalf("rootless qualification result directory must be a root-owned, non-symlink directory with mode 0700: mode=%s stat=%#v", resultInfo.Mode(), resultInfo.Sys())
|
||||
}
|
||||
osRelease := string(rootlessQualReadFile(t, "/etc/os-release"))
|
||||
if !strings.Contains(osRelease, "VERSION_ID=\"24.04\"") && !strings.Contains(osRelease, "VERSION_ID=24.04") {
|
||||
@@ -1597,7 +1607,9 @@ func TestRootlessQualificationGuardAndWrapperInvariants(t *testing.T) {
|
||||
`run_runtime docker`, `run_runtime podman`, `PULSE_ROOTLESS_RUNTIME=${runtime_name}`, `--privileged`,
|
||||
`pulse-secure-runtime-rootless-qualification`, `qualification result != \"passed\"`,
|
||||
`openssl pkeyutl -sign -rawin -inkey`, `qualification output directory must have exact mode 0700`,
|
||||
`install -d -m 0700 /opt/pulse/packet`,
|
||||
`install -d -m 0700 /opt/pulse/packet /opt/pulse/result`,
|
||||
`PULSE_ROOTLESS_RECEIPT=/opt/pulse/result/rootless-receipt.json`,
|
||||
`docker cp "${container_id}:/opt/pulse/result/rootless-receipt.json"`,
|
||||
`capture_qualification_container_diagnostics`, `journalctl --no-pager -n 2000`,
|
||||
`302a300506032b6570032100`, `len(spki) != len(prefix) + 32`,
|
||||
} {
|
||||
@@ -1608,7 +1620,7 @@ func TestRootlessQualificationGuardAndWrapperInvariants(t *testing.T) {
|
||||
if count := strings.Count(script, `-buildvcs=true`); count != 3 {
|
||||
t.Fatalf("rootless qualification wrapper must require VCS metadata for all three Go artifacts: got %d", count)
|
||||
}
|
||||
for _, forbidden := range []string{"/var/run/docker.sock:", "/run/podman/podman.sock:", "/sys/fs/cgroup:/sys/fs/cgroup", "--cgroupns=host", "-v $", "--volume"} {
|
||||
for _, forbidden := range []string{"/var/run/docker.sock:", "/run/podman/podman.sock:", "/sys/fs/cgroup:/sys/fs/cgroup", "/run/rootless-receipt.json", "--cgroupns=host", "-v $", "--volume"} {
|
||||
if strings.Contains(script, forbidden) {
|
||||
t.Fatalf("rootless qualification wrapper contains forbidden host-runtime mount marker %q", forbidden)
|
||||
}
|
||||
@@ -1621,7 +1633,7 @@ func TestRootlessQualificationGuardAndWrapperInvariants(t *testing.T) {
|
||||
if createIndex < 0 || trackIndex < 0 || trackIndex < createIndex {
|
||||
t.Fatal("rootless wrapper must track the exact container ID only after docker create succeeds")
|
||||
}
|
||||
packetDirectoryIndex := strings.Index(script, `install -d -m 0700 /opt/pulse/packet`)
|
||||
packetDirectoryIndex := strings.Index(script, `install -d -m 0700 /opt/pulse/packet /opt/pulse/result`)
|
||||
packetCopyIndex := strings.Index(script, `docker cp "${PACKET_DIR}/." "${container_id}:/opt/pulse/packet"`)
|
||||
if packetDirectoryIndex < 0 || packetCopyIndex < 0 || packetCopyIndex < packetDirectoryIndex {
|
||||
t.Fatal("rootless wrapper must create the private packet destination in the image before artifact injection")
|
||||
|
||||
@@ -198,7 +198,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
ln -sf /dev/null /etc/systemd/system/docker.socket && \
|
||||
ln -sf /dev/null /etc/systemd/system/podman.service && \
|
||||
ln -sf /dev/null /etc/systemd/system/podman.socket && \
|
||||
install -d -m 0700 /opt/pulse/packet && \
|
||||
install -d -m 0700 /opt/pulse/packet /opt/pulse/result && \
|
||||
printf '%s\n' disposable-v1 >/etc/pulse-secure-runtime-rootless-qualification && \
|
||||
rm -f /etc/machine-id && touch /etc/machine-id && \
|
||||
systemctl set-default multi-user.target
|
||||
@@ -253,7 +253,7 @@ run_runtime() {
|
||||
if ! docker exec \
|
||||
-e PULSE_SECURE_RUNTIME_ROOTLESS_QUALIFICATION=disposable-v1 \
|
||||
-e "PULSE_ROOTLESS_RUNTIME=${runtime_name}" \
|
||||
-e PULSE_ROOTLESS_RECEIPT=/run/rootless-receipt.json \
|
||||
-e PULSE_ROOTLESS_RECEIPT=/opt/pulse/result/rootless-receipt.json \
|
||||
-e PULSE_ROOTLESS_SOURCE_HASHES=/opt/pulse/packet/source-hashes.json \
|
||||
-e "PULSE_ROOTLESS_SOURCE_COMMIT=${SOURCE_COMMIT}" \
|
||||
-e PULSE_SECURE_RUNTIME_COLLECTOR=/opt/pulse/packet/pulse-agent \
|
||||
@@ -268,7 +268,12 @@ run_runtime() {
|
||||
return 1
|
||||
fi
|
||||
|
||||
docker cp "${container_id}:/run/rootless-receipt.json" "${local_receipt}"
|
||||
if ! docker exec "${container_id}" test -f /opt/pulse/result/rootless-receipt.json; then
|
||||
capture_qualification_container_diagnostics "${runtime_name}" "${container_id}"
|
||||
echo "ERROR: ${runtime_name} qualification did not retain its durable receipt" >&2
|
||||
return 1
|
||||
fi
|
||||
docker cp "${container_id}:/opt/pulse/result/rootless-receipt.json" "${local_receipt}"
|
||||
capture_qualification_container_diagnostics "${runtime_name}" "${container_id}"
|
||||
chmod 0600 "${local_receipt}" "${OUTPUT_DIR}/${runtime_name}-test.log"
|
||||
remove_qualification_container_strict "${container_id}"
|
||||
|
||||
Reference in New Issue
Block a user