Repository navigation
feat(launcher): launch the privileged daemon from the image's own digest - #24
Merged
Merged
Conversation
Add a `launch` subcommand so the Swarm service can run the daemon image itself, unprivileged, with only the Docker socket. The launcher finds its own container in /proc/self/mountinfo, takes the sha256 image ID it runs and creates the privileged daemon container from it, so the launcher and the daemon are always the same digest: image watchers, rolling updates and rollbacks act on the daemon too. daemonSpec is the single source of the daemon's privileged set and uses bind Mounts, not Binds, so a missing host path fails the create. Before creating, the launcher removes a stale swarm-device-access daemon only when its owning launcher is confirmed gone or stopped, or when the old sh wrapper started it; a live owner, a transient inspect error or a foreign container aborts. It attaches and registers the removal wait before the start, streams the daemon's output as its own, stops the daemon by ID and bounds every shutdown by one 20s budget.
Run the daemon image in launcher mode with only the Docker socket and a dry-run daemon behind it: check the daemon container's privileged set, image and owner label, a graceful stop that exits 0 and leaves no daemon, and a killed launcher whose orphaned daemon the next launcher replaces. It creates a privileged container named swarm-device-access, so it runs with the enforcement tests. sweep-test-leaks also removes launcher-created daemons whose launcher container no longer exists.
The reference compose file, the five examples and the README now run ghcr.io/leinardi/swarm-device-access:1 as a `launch` service with only the Docker socket, stop-first updates and a 30s stop grace period; each example keeps its commented -device-allow lines after `--`. The README documents the launch flags, why the daemon runs from the launcher's own image, and retires the docker:29 sh wrapper of earlier releases. SECURITY.md, AGENTS.md, docs/architecture.md, docs/testing.md and the trust-boundary skill name daemonSpec as the single source of the daemon's privileged set.
…r a broken wait establish waited for the call's goroutine after canceling it. The client's hijacked attach reads the upgrade response on a raw connection that no context reaches, so a dockerd that accepts and never answers kept the launcher blocked forever, with the created container and its fixed name left behind. It now returns at the deadline and releases a late result in the background. A broken wait stream stopped the daemon and returned at once, closing the attach stream: the last log lines could be cut and the removal was never confirmed. That path now shares the shutdown budget like the others: stop, poll inspect until the daemon is gone, drain the output.
The recipe's status came from the last command of each pipeline, so a failed docker ps, a failed owner inspect or an earlier docker rm still left make green, with privileged containers behind. List before removing, run under set -eu, and fail on an inspect error other than "No such", instead of skipping the daemon.
Polling for the daemon's removal after a broken wait stream dropped every inspect error but NotFound, so a permission or connection failure surfaced only as the shutdown budget running out. Return the latest inspect error with the timeout.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a
launchmode to the daemon image, so the Swarm service can runghcr.io/leinardi/swarm-device-access:1 launch … -- <daemon flags>unprivileged with only the Docker socket. It replaces thedocker:29sh wrapper.Same digest. The launcher finds its own container ID in
/proc/self/mountinfo, inspects itself and creates the daemon from its ownsha256:image ID. Launcher and daemon therefore always run the same digest, and gantry, rolling updates anddocker service rollbackact on the daemon as well.One privileged set.
daemonSpec(internal/launcher/spec.go) is the single source of truth for the daemon's privileges: privileged, host cgroup, PID and user namespaces, networknone(-host-networkopts into host),AutoRemove, a 10 s stop timeout, and bindMounts(noBinds) for the Docker socket,/sysat/host/sysand/dev.-dbusand-config-dir(read-only) are opt-in.Stale-daemon cleanup. Before creating a daemon, the launcher removes an existing
swarm-device-accesscontainer only in these cases:A live owner, a transient inspect error or a foreign container aborts with an error, and Swarm retries the launcher.
Supervision.
docker runorder: create, attach (the copy goroutine starts before start),ContainerWait(removed), start.stop_grace_period: 30s.Tests.
TestLauncher_RunsDaemonFromItsOwnImageis an integration test in the enforcement group, so it runs only in CI. It covers:sweep-test-leaksalso removes daemons whose launcher no longer exists.Docs.
Local verification:
go test -race ./...,golangci-lint run(including-tags integration) andmake checkpass.make go-test-integrationpasses. It ran dry-run only: the launcher and enforcement tests skip withoutSDA_IT_ENFORCE=1.make docker-buildworks. Inside the built image,launch -helpexits 0 andlaunch -config-dir relativefails with a named error. A barelaunchidentified its own container and then failed only because it had no socket.Pull request checklist
masterbranchmake checklocally before creating the commit and it has run successfullyWIPcommits in this PRType of changes