Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 62 additions & 18 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,59 @@
# syntax=docker/dockerfile:1.5

# ---------------------------------------------------------------------------
# Stage 1a: Build gosu and containerd from source (Go 1.24.x)
# with google.golang.org/grpc >= 1.79.3 and Go >= 1.24.13
# ---------------------------------------------------------------------------
FROM --platform=$TARGETPLATFORM golang:1.24.13 AS builder-containerd

ARG CONTAINERD_VERSION_TAG=v2.2.2
ARG GOSU_VERSION=1.19
ARG GRPC_FIX_VERSION=1.79.3

# Build gosu
RUN set -eux; \
CGO_ENABLED=0 go install -ldflags '-s -w' \
"github.com/tianon/gosu@${GOSU_VERSION}"; \
cp /go/bin/gosu /usr/local/bin/gosu

# Build containerd binaries (containerd, ctr, containerd-shim-runc-v2)
RUN --mount=type=cache,target=/go/pkg/mod \
--mount=type=cache,target=/root/.cache/go-build \
set -eux; \
git clone --depth 1 --branch "${CONTAINERD_VERSION_TAG}" \
https://github.com/containerd/containerd.git /build/containerd; \
cd /build/containerd; \
go get "google.golang.org/grpc@v${GRPC_FIX_VERSION}"; \
go mod tidy; \
go mod vendor; \
make STATIC=1 binaries; \
cp bin/containerd bin/ctr bin/containerd-shim-runc-v2 /usr/local/bin/

# ---------------------------------------------------------------------------
# Stage 1b: Build dockerd from source (Go 1.25.x – moby v29.3.1 requires >= 1.25.5)
# with google.golang.org/grpc >= 1.79.3
# ---------------------------------------------------------------------------
FROM --platform=$TARGETPLATFORM golang:1.25.8 AS builder-moby

ARG MOBY_VERSION_TAG=docker-v29.3.1
ARG GRPC_FIX_VERSION=1.79.3

# Build dockerd (moby engine)
RUN --mount=type=cache,target=/go/pkg/mod \
--mount=type=cache,target=/root/.cache/go-build \
set -eux; \
git clone --depth 1 --branch "${MOBY_VERSION_TAG}" \
https://github.com/moby/moby.git /build/moby; \
cd /build/moby; \
go get "google.golang.org/grpc@v${GRPC_FIX_VERSION}"; \
go mod tidy; \
go mod vendor; \
CGO_ENABLED=0 go build -o /usr/local/bin/dockerd \
-ldflags '-s -w' ./cmd/dockerd

# ---------------------------------------------------------------------------
# Stage 2: Final image
# ---------------------------------------------------------------------------
FROM --platform=$TARGETPLATFORM ubuntu:25.10

ARG RUNNER_VERSION=2.333.1
Expand All @@ -15,15 +70,10 @@ ARG COMPOSE_VERSION=2.40.3
ARG COMPOSE_SHA256_AMD64=dba9d98e1ba5bfe11d88c99b9bd32fc4a0624a30fafe68eea34d61a3e42fd372
ARG COMPOSE_SHA256_ARM64=d26373b19e89160546d15407516cc59f453030d9bc5b43ba7faf16f7b4980137

# Docker Engine + containerd pinned versions (fixes CVE in Go dependency <1.79.3)
# Docker Engine + containerd apt versions (binaries overridden by source-built in builder stage)
ARG DOCKER_VERSION=5:29.3.1-1~ubuntu.25.10~questing
ARG CONTAINERD_VERSION=2.2.2-1~ubuntu.25.10~questing

# Gosu checksums from: https://github.com/tianon/gosu/releases/tag/1.19
ARG GOSU_VERSION=1.19
ARG GOSU_SHA256_AMD64=52c8749d0142edd234e9d6bd5237dff2d81e71f43537e2f4f66f75dd4b243dd0
ARG GOSU_SHA256_ARM64=3a8ef022d82c0bc4a98bcb144e77da714c25fcfa64dccc57f6aba7ae47ff1a44

# Node.js LTS pinned version
ARG NODE_VERSION=22

Expand Down Expand Up @@ -58,18 +108,12 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
docker-ce-cli="${DOCKER_VERSION}" \
containerd.io="${CONTAINERD_VERSION}"

# Install gosu from official release with checksum verification (apt version ships vulnerable Go stdlib)
RUN set -eux; \
case "${TARGETARCH}" in \
arm64) CHECKSUM="${GOSU_SHA256_ARM64}" ;; \
amd64) CHECKSUM="${GOSU_SHA256_AMD64}" ;; \
*) echo "Unsupported: ${TARGETARCH}" >&2; exit 1 ;; \
esac; \
curl -fL "https://github.com/tianon/gosu/releases/download/${GOSU_VERSION}/gosu-${TARGETARCH}" \
-o /usr/sbin/gosu; \
echo "${CHECKSUM} /usr/sbin/gosu" | sha256sum -c -; \
chmod +x /usr/sbin/gosu; \
gosu --version
# Override apt-installed binaries with source-built versions (fixes CVE in grpc < 1.79.3 and Go < 1.24.13)
COPY --from=builder-containerd /usr/local/bin/gosu /usr/sbin/gosu
COPY --from=builder-containerd /usr/local/bin/containerd /usr/bin/containerd
COPY --from=builder-containerd /usr/local/bin/containerd-shim-runc-v2 /usr/bin/containerd-shim-runc-v2
COPY --from=builder-containerd /usr/local/bin/ctr /usr/bin/ctr
COPY --from=builder-moby /usr/local/bin/dockerd /usr/bin/dockerd

# Create runner user WITHOUT blanket sudo access
RUN useradd -m runner
Expand Down
14 changes: 8 additions & 6 deletions entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -140,19 +140,21 @@ cd /actions-runner
--work "${WORK_DIR}" \
--replace

# Clear token from environment after registration
RUNNER_TOKEN_FILE_PATH="${RUNNER_TOKEN_FILE:-}"
# Save token for deregistration, then clear from environment
RUNNER_TOKEN_CLEANUP_FILE="/home/runner/.runner-token-cleanup"
printf '%s' "${RUNNER_TOKEN}" > "$RUNNER_TOKEN_CLEANUP_FILE"
chmod 600 "$RUNNER_TOKEN_CLEANUP_FILE"
unset RUNNER_TOKEN

cleanup() {
echo "Unregistering runner..."
if [ -n "${RUNNER_TOKEN_FILE_PATH}" ] && [ -f "${RUNNER_TOKEN_FILE_PATH}" ]; then
if [ -f "$RUNNER_TOKEN_CLEANUP_FILE" ]; then
local token
token="$(cat "$RUNNER_TOKEN_FILE_PATH")"
token="$(cat "$RUNNER_TOKEN_CLEANUP_FILE")"
./config.sh remove --unattended --token "$token" || true
rm -f "$RUNNER_TOKEN_CLEANUP_FILE"
else
echo "WARNING: Cannot unregister -- RUNNER_TOKEN already cleared from environment."
echo "Use RUNNER_TOKEN_FILE for automatic deregistration on shutdown."
echo "WARNING: Cannot unregister -- cleanup token file not found."
fi
}

Expand Down
68 changes: 68 additions & 0 deletions tests/test_token_cleanup.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
source "$SCRIPT_DIR/helpers.sh"

test_header "Token Cleanup File for Deregistration"

CLEANUP_FILE="/home/runner/.runner-token-cleanup"

# Token file is created with correct permissions and content
output=$(run_in_image "
TOKEN='test-secret-token-abc123'
printf '%s' \"\$TOKEN\" > $CLEANUP_FILE
chmod 600 $CLEANUP_FILE

# Verify file exists
[ -f $CLEANUP_FILE ] && echo 'EXISTS' || echo 'MISSING'
")
assert_eq "$output" "EXISTS" "Cleanup token file is created at $CLEANUP_FILE"

# Token file has mode 600 (owner read/write only)
output=$(run_in_image "
printf '%s' 'test-token' > $CLEANUP_FILE
chmod 600 $CLEANUP_FILE
stat -c '%a' $CLEANUP_FILE
")
assert_eq "$output" "600" "Cleanup token file has mode 600"

# Token content is preserved correctly
output=$(run_in_image "
printf '%s' 'my-secret-runner-token' > $CLEANUP_FILE
chmod 600 $CLEANUP_FILE
cat $CLEANUP_FILE
")
assert_eq "$output" "my-secret-runner-token" "Token content is read back correctly"

# Token file is owned by runner user
output=$(run_in_image "
gosu runner bash -c \"printf '%s' 'test-token' > $CLEANUP_FILE && chmod 600 $CLEANUP_FILE\"
stat -c '%U' $CLEANUP_FILE
")
assert_eq "$output" "runner" "Cleanup token file is owned by runner user"

# Token file is removed after cleanup reads it
output=$(run_in_image "
printf '%s' 'test-token' > $CLEANUP_FILE
chmod 600 $CLEANUP_FILE
# Simulate what cleanup() does after reading
cat $CLEANUP_FILE >/dev/null
rm -f $CLEANUP_FILE
[ -f $CLEANUP_FILE ] && echo 'STILL_EXISTS' || echo 'REMOVED'
")
assert_eq "$output" "REMOVED" "Cleanup token file is deleted after use"

# Token file is not visible to other users
output=$(run_in_image "
printf '%s' 'test-token' > $CLEANUP_FILE
chmod 600 $CLEANUP_FILE
# Try reading as nobody (should fail)
su -s /bin/bash nobody -c 'cat $CLEANUP_FILE 2>&1' || echo 'PERMISSION_DENIED'
")
assert_contains "$output" "PERMISSION_DENIED" "Token file is not readable by other users"

# Cleanup path in entrypoint.sh matches expected location
output=$(run_in_image "grep -c 'RUNNER_TOKEN_CLEANUP_FILE=\"$CLEANUP_FILE\"' /entrypoint.sh || echo 0")
assert_eq "$output" "1" "Entrypoint uses $CLEANUP_FILE as cleanup path"

test_summary
Loading