mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-23 20:25:56 +00:00
Give RC qualification a local host-interface canary (#2328)
Split out of #2323. The v6.4.5-rc.4 installed-acceptance run (36547091547) failed before the lab because the no-network qualification container had no non-loopback IPv4 canary. This carries only the qualification and demo-resolver workflow fixes, their deployment-installability contract text and the workflow tests, so promotion isn't waiting on #2323's conflicting UI changes.
This commit is contained in:
@@ -224,7 +224,7 @@ jobs:
|
||||
COPY --from=dind /usr/local/bin/ /usr/local/bin/
|
||||
COPY pulse-qualification-docker.service /etc/systemd/system/pulse-qualification-docker.service
|
||||
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
||||
ca-certificates curl dbus systemd systemd-sysv util-linux && \
|
||||
ca-certificates curl dbus systemd systemd-sysv util-linux iproute2 && \
|
||||
apt-get clean && \
|
||||
systemctl mask dev-hugepages.mount sys-fs-fuse-connections.mount systemd-remount-fs.service docker.service docker.socket && \
|
||||
systemctl enable pulse-qualification-docker.service
|
||||
@@ -269,7 +269,19 @@ jobs:
|
||||
sleep 2
|
||||
done
|
||||
docker exec "${container}" sh -c \
|
||||
'for command in curl docker dockerd id nsenter runuser systemctl; do command -v "$command" >/dev/null || exit 1; done'
|
||||
'for command in curl docker dockerd id ip nsenter runuser systemctl; do command -v "$command" >/dev/null || exit 1; done'
|
||||
# The immutable RC test needs a reachable non-loopback host-interface
|
||||
# canary to prove that the helper's private network cannot reach it.
|
||||
# Both veth ends stay inside this --network none namespace; neither
|
||||
# endpoint is attached to the runner or an egress bridge.
|
||||
docker exec "${container}" sh -ceu '
|
||||
ip link add pulse-can0 type veth peer name pulse-can1
|
||||
ip address add 192.0.2.1/32 dev pulse-can0
|
||||
ip link set pulse-can1 up
|
||||
ip link set pulse-can0 up
|
||||
test -z "$(ip -4 route show default)"
|
||||
ip -4 address show dev pulse-can0 | grep -F "192.0.2.1/32"
|
||||
'
|
||||
docker exec "${container}" systemctl start pulse-qualification-docker.service
|
||||
for attempt in $(seq 1 60); do
|
||||
if docker exec "${container}" docker info >/dev/null 2>&1; then
|
||||
|
||||
@@ -74,6 +74,13 @@ jobs:
|
||||
mock_update_interval: ${{ steps.runtime_profile.outputs.mock_update_interval }}
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
fetch-tags: true
|
||||
|
||||
- name: Resolve target tag and demo environment
|
||||
id: target
|
||||
env:
|
||||
@@ -137,13 +144,6 @@ jobs:
|
||||
echo "environment_name=$ENVIRONMENT_NAME" >> "$GITHUB_OUTPUT"
|
||||
echo "Resolved demo deployment: tag=${TAG}, target=${TARGET}, environment=${ENVIRONMENT_NAME}"
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
fetch-tags: true
|
||||
|
||||
- name: Validate governed release line for selected tag
|
||||
env:
|
||||
WORKFLOW_OUTPUT_1: ${{ steps.target.outputs.tag }}
|
||||
|
||||
@@ -1296,6 +1296,14 @@ artifact-selection behaviour.
|
||||
privileged systemd container must mount that exact snapshot read-only and
|
||||
must never execute directly from the download directory. The post-run
|
||||
attester must consume the same snapshot paths.
|
||||
The outer systemd qualification container must retain `--network none`:
|
||||
it may create a non-loopback host-interface canary only as a veth pair
|
||||
wholly inside that container's network namespace, with no runner bridge,
|
||||
egress attachment, or default IPv4 route. The immutable RC lab uses this
|
||||
reachable local canary to distinguish host-network access from the typed
|
||||
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.
|
||||
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
|
||||
@@ -3831,6 +3839,10 @@ bypass the preflight. That dry run must call `update-demo-server.yml` in
|
||||
verification-only mode against the latest stable release. It must prove
|
||||
Tailscale, SSH host identity, runtime version, frontend parity, public health,
|
||||
and browser smoke without changing the host.
|
||||
The reusable demo workflow's resolver must check out the repository before it
|
||||
calls `scripts/write_github_output.py` to emit the selected tag and target.
|
||||
This checkout is credential-free; a verification-only dry run must not fail
|
||||
before its demo checks merely because the output helper is absent.
|
||||
That same release-validation boundary also owns draft-versus-published asset
|
||||
state. Every normal `.github/workflows/create-release.yml` cut validates the
|
||||
uploaded packet while the release is still a draft and must pass `draft=true`
|
||||
|
||||
@@ -2392,6 +2392,29 @@ func TestUpdateDemoWorkflowUsesGovernedNetworkPath(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateDemoResolverChecksOutOutputHelperBeforeUse(t *testing.T) {
|
||||
workflowBytes, err := os.ReadFile(repoFile(".github", "workflows", "update-demo-server.yml"))
|
||||
if err != nil {
|
||||
t.Fatalf("read update-demo-server workflow: %v", err)
|
||||
}
|
||||
workflow := string(workflowBytes)
|
||||
resolveStart := strings.Index(workflow, "\n resolve:\n")
|
||||
updateStart := strings.Index(workflow, "\n update-demo:\n")
|
||||
if resolveStart < 0 || updateStart <= resolveStart {
|
||||
t.Fatal("update-demo-server workflow must retain separate resolve and update-demo jobs")
|
||||
}
|
||||
resolve := workflow[resolveStart:updateStart]
|
||||
checkout := strings.Index(resolve, "- name: Checkout repository")
|
||||
target := strings.Index(resolve, "- name: Resolve target tag and demo environment")
|
||||
helper := strings.Index(resolve, "python3 scripts/write_github_output.py tag \"$TAG\"")
|
||||
if checkout < 0 || target <= checkout || helper <= target {
|
||||
t.Fatal("demo resolver must check out its source before using scripts/write_github_output.py")
|
||||
}
|
||||
if !strings.Contains(resolve[checkout:target], "persist-credentials: false") {
|
||||
t.Fatal("demo resolver checkout must not persist GitHub credentials")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDemoMutationAndRecoverySharePhysicalTargetLock(t *testing.T) {
|
||||
updateBytes, err := os.ReadFile(repoFile(".github", "workflows", "update-demo-server.yml"))
|
||||
if err != nil {
|
||||
@@ -2824,12 +2847,15 @@ func TestSecureRuntimeQualificationPacketIsHostedAndReleaseBound(t *testing.T) {
|
||||
`git remote set-url origin https://github.com/rcourtman/Pulse.git`,
|
||||
"ca-certificates curl dbus systemd systemd-sysv util-linux",
|
||||
`docker:27.5.1-dind@sha256:f649ef046008ca7f926a2571c32b0ac22e5c59eb61b959617f9acc2a4c638cf5`,
|
||||
`for command in curl docker dockerd id nsenter runuser systemctl`,
|
||||
`for command in curl docker dockerd id ip nsenter runuser systemctl`,
|
||||
`--host=unix:///var/run/docker.sock`,
|
||||
`--storage-driver=vfs`,
|
||||
`--bridge=none`,
|
||||
`--iptables=false`,
|
||||
`--network none`,
|
||||
`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)"`,
|
||||
`pulse-secure-runtime-fixture:v7`,
|
||||
"--verify-release-packet-only",
|
||||
"--verified-packet-dir",
|
||||
@@ -2847,6 +2873,16 @@ func TestSecureRuntimeQualificationPacketIsHostedAndReleaseBound(t *testing.T) {
|
||||
t.Fatalf("post-publication secure-runtime qualification missing %q", required)
|
||||
}
|
||||
}
|
||||
canaryIndex := strings.Index(qualificationWorkflow, `ip link add pulse-can0 type veth peer name pulse-can1`)
|
||||
labIndex := strings.Index(qualificationWorkflow, `--env PULSE_SECURE_RUNTIME_SYSTEMD_LAB=1`)
|
||||
if canaryIndex <= privilegedExecutionIndex || labIndex <= canaryIndex {
|
||||
t.Fatal("the isolated host-interface canary must be configured before running the immutable RC lab")
|
||||
}
|
||||
if !strings.Contains(qualificationWorkflow, `--privileged \
|
||||
--network none \
|
||||
--cgroupns=host`) {
|
||||
t.Fatal("the outer systemd lab must retain its no-network namespace")
|
||||
}
|
||||
for _, forbidden := range []string{
|
||||
`/var/run/docker.sock:/var/run/docker.sock`,
|
||||
`--host=tcp://`,
|
||||
|
||||
Reference in New Issue
Block a user