From 0d5447e1779ba050acebfc856e4f16023fcf1211 Mon Sep 17 00:00:00 2001 From: Roberto Leinardi Date: Thu, 28 May 2026 10:50:57 +0200 Subject: [PATCH] Use Docker Hardened Images (dhi.io) --- .github/workflows/release.yaml | 7 +++++++ .mk/docker-run.mk | 6 ++++++ AGENTS.md | 2 +- README.md | 3 ++- deployments/docker/Dockerfile | 17 +++++++++-------- deployments/docker/docker-compose.yaml | 4 +++- docs/architecture.md | 5 ++--- 7 files changed, 30 insertions(+), 14 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 1a75350..0b1348e 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -95,6 +95,13 @@ jobs: - name: Set up Buildx uses: docker/setup-buildx-action@v4 + - name: Log in to Docker Hardened Images (dhi.io) + uses: docker/login-action@v4 + with: + registry: dhi.io + username: ${{ vars.DHI_USERNAME }} + password: ${{ secrets.DHI_TOKEN }} + - name: Log in to GHCR uses: docker/login-action@v4 with: diff --git a/.mk/docker-run.mk b/.mk/docker-run.mk index a8b65f6..e7085c7 100644 --- a/.mk/docker-run.mk +++ b/.mk/docker-run.mk @@ -8,10 +8,15 @@ # - host /dev tree (to stat device major/minor numbers) # - host cgroup, pid, and user namespaces, --privileged # +# Optional: mount the host DBus socket to enable systemctl daemon-reload +# re-apply. dhi.io/static has no /var/run→/run symlink, so the container-side +# path must be /var/run/dbus/system_bus_socket (not /run/dbus/...). +# # Override DOCKER_RUN_ARGS to pass extra daemon flags: # make docker-run DOCKER_RUN_ARGS="-log-level debug -log-time" DOCKER_SOCKET ?= /var/run/docker.sock +DBUS_SOCKET ?= /run/dbus/system_bus_socket DOCKER_RUN_ARGS ?= -log-level info -log-time .PHONY: docker-run @@ -25,5 +30,6 @@ docker-run: ## Run the image locally with host cgroup/pid namespaces and require -v "$(DOCKER_SOCKET):/var/run/docker.sock" \ -v /sys:/host/sys \ -v /dev:/dev \ + $(if $(wildcard $(DBUS_SOCKET)),-v "$(DBUS_SOCKET):/var/run/dbus/system_bus_socket") \ "$(IMAGE_REPO):$(IMAGE_TAG)" \ $(DOCKER_RUN_ARGS) diff --git a/AGENTS.md b/AGENTS.md index 02fcc8d..9714c58 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -73,7 +73,7 @@ with care; the upstream PRs went into runc/containerd long ago.** The daemon **must** run with `privileged: true`, `cgroup: host`, `pid: host`, `userns_mode: host`, and bind mounts for `/var/run/docker.sock` and `/sys → /host/sys`. The `hostRootPath = "/host"` constant in `main.go` is the inside-container view of the host root; cgroup paths are joined against -it. The DBus socket mount (`/run/dbus/system_bus_socket`) is optional — enables reload handling. +it. The DBus socket mount is optional — enables reload handling. Mount as `-v /run/dbus/system_bus_socket:/var/run/dbus/system_bus_socket`; the container-side path must be under `/var/run/` because `dhi.io/static` has no `/var/run → /run` symlink. ## Conventions worth knowing diff --git a/README.md b/README.md index bfc6bdb..7faab80 100644 --- a/README.md +++ b/README.md @@ -76,8 +76,9 @@ services: - -v - /dev:/dev # Optional: reapply device rules after systemctl daemon-reload. + # NOTE: dhi.io/static has no /var/run→/run symlink; use /var/run/dbus/... inside the container. # - -v - # - /run/dbus/system_bus_socket:/run/dbus/system_bus_socket + # - /run/dbus/system_bus_socket:/var/run/dbus/system_bus_socket - ghcr.io/leinardi/swarm-device-access:latest volumes: - /var/run/docker.sock:/var/run/docker.sock diff --git a/deployments/docker/Dockerfile b/deployments/docker/Dockerfile index b3a4790..c008606 100644 --- a/deployments/docker/Dockerfile +++ b/deployments/docker/Dockerfile @@ -3,10 +3,10 @@ ######################## # deps: cache modules ######################## -FROM --platform=$BUILDPLATFORM golang:1.26.3-alpine AS deps +FROM --platform=$BUILDPLATFORM dhi.io/golang:1-alpine3.23-dev AS deps WORKDIR /src RUN --mount=type=cache,target=/var/cache/apk \ - apk add --no-cache ca-certificates git + apk add --no-cache git COPY go.mod go.sum ./ # Use a named cache so we can reuse it in the build stage (no COPY needed) RUN --mount=type=cache,target=/go/pkg/mod,id=gomodcache \ @@ -15,7 +15,7 @@ RUN --mount=type=cache,target=/go/pkg/mod,id=gomodcache \ ######################## # build: compile static binary ######################## -FROM --platform=$BUILDPLATFORM golang:1.26.3-alpine AS build +FROM --platform=$BUILDPLATFORM dhi.io/golang:1-alpine3.23-dev AS build ARG VERSION="dev" ARG COMMIT="none" ARG DATE="unknown" @@ -29,8 +29,6 @@ ENV CGO_ENABLED=0 \ GOARCH=${TARGETARCH} WORKDIR /src -RUN --mount=type=cache,target=/var/cache/apk \ - apk add --no-cache ca-certificates # Bring in source COPY . . @@ -43,10 +41,10 @@ RUN --mount=type=cache,target=/go/pkg/mod,id=gomodcache \ -o /out/swarm-device-access ./cmd/swarm-device-access ######################## -# runtime: alpine (root by default, required for cgroup BPF syscalls; -# includes a shell + busybox utilities for in-container debugging). +# runtime: dhi.io hardened static base (distroless-style, no shell). +# The daemon binary is CGO_ENABLED=0 so no libc is required. ######################## -FROM alpine:3.23.4 +FROM dhi.io/static:20250419 # hadolint ignore=DL3050 LABEL org.opencontainers.image.title="swarm-device-access" \ @@ -68,4 +66,7 @@ COPY --from=build /out/swarm-device-access /usr/local/bin/swarm-device-access # the container is started with --pid=host (which would place an init shim # outside PID 1 of its own pid namespace and prevent it from working anyway). +# dhi.io/static defaults to a non-root user; bpf(2) and cgroup writes require root. +# hadolint ignore=DL3002 +USER 0 ENTRYPOINT ["/usr/local/bin/swarm-device-access"] diff --git a/deployments/docker/docker-compose.yaml b/deployments/docker/docker-compose.yaml index 559a705..a2d4de8 100644 --- a/deployments/docker/docker-compose.yaml +++ b/deployments/docker/docker-compose.yaml @@ -24,8 +24,10 @@ services: # detect systemctl daemon-reload (which wipes cgroup BPF programs) and # re-apply device rules. Without this mount, reload handling is skipped # and the daemon logs a warning at startup. + # NOTE: dhi.io/static has no /var/run→/run symlink; the container-side + # path must be /var/run/dbus/system_bus_socket (not /run/dbus/...). # - -v - # - /run/dbus/system_bus_socket:/run/dbus/system_bus_socket + # - /run/dbus/system_bus_socket:/var/run/dbus/system_bus_socket - ghcr.io/leinardi/swarm-device-access:latest - "-log-level" - "info" diff --git a/docs/architecture.md b/docs/architecture.md index 657f995..44bb43e 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -151,7 +151,7 @@ The daemon **must** run as root with: - `/dev` bind-mounted (so device major/minor can be read via `unix.Stat`) - `/var/run/docker.sock` bind-mounted -The DBus socket (`/run/dbus/system_bus_socket`) is optional — enables systemd reload handling. +The DBus socket is optional — enables systemd reload handling. Mount as `-v /run/dbus/system_bus_socket:/var/run/dbus/system_bus_socket`; the container-side path must be under `/var/run/` because `dhi.io/static` has no `/var/run → /run` symlink. ## Observability @@ -217,8 +217,7 @@ level=INFO msg="dry-run: would add device rule" pid=1234 cgroup=/host/sys/fs/cgr ### Daemon does not re-apply rules after `systemctl daemon-reload` -Ensure `/run/dbus/system_bus_socket` is bind-mounted into the daemon container. Without it, the reload watcher is disabled (logged at `Warn` on -startup). +Ensure the host DBus socket is bind-mounted as `-v /run/dbus/system_bus_socket:/var/run/dbus/system_bus_socket`. Without it, the reload watcher is disabled (logged at `Warn` on startup). The container-side path must be `/var/run/dbus/system_bus_socket` — `dhi.io/static` has no `/var/run → /run` symlink. ### Daemon cannot connect to Docker