diff --git a/.dockerignore b/.dockerignore index 179dd82a..73252cfa 100644 --- a/.dockerignore +++ b/.dockerignore @@ -4,7 +4,7 @@ # Allow files and directories. !cmd/ !internal/ -!scripts/docker-entrypoint.sh +!scripts/docker/ !go.* # Ignore unnecessary files inside allowed directories. diff --git a/Dockerfile b/Dockerfile index e88a9379..6f5da4ec 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,3 +1,5 @@ +ARG ALPINE_VERSION=3.20.3 + FROM golang:1.23.2-alpine AS uncloudd ARG TARGETOS @@ -14,9 +16,9 @@ COPY . . RUN GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -o uncloudd cmd/uncloudd/main.go -FROM alpine:latest AS corrosion-download +FROM alpine:${ALPINE_VERSION} AS corrosion-download RUN wget -q -O /tmp/corrosion.tar.gz \ - https://github.com/psviderski/corrosion/releases/latest/download/corrosion-aarch64-unknown-linux-gnu.tar.gz \ + https://github.com/psviderski/corrosion/releases/latest/download/corrosion-aarch64-unknown-linux-gnu.tar.gz \ && tar -xzf /tmp/corrosion.tar.gz -C /tmp \ && install /tmp/corrosion /usr/local/bin/corrosion \ && rm /tmp/corrosion.tar.gz /tmp/corrosion @@ -26,15 +28,23 @@ COPY --from=corrosion-download /usr/local/bin/corrosion /usr/local/bin/corrosion CMD ["corrosion", "agent"] +FROM alpine:${ALPINE_VERSION} AS corrosion-image-tarball +ARG CORROSION_IMAGE="ghcr.io/psviderski/corrosion:latest" + +RUN apk --no-cache add crane +RUN crane pull "${CORROSION_IMAGE}" /corrosion.tar + + FROM docker:27.3.1-dind AS machine RUN apk --no-cache add \ wireguard-tools # Create system group and user 'uncloud'. RUN addgroup -S uncloud && adduser -SHD -h /nonexistent -G uncloud -g "" uncloud -COPY scripts/docker-entrypoint.sh /usr/local/bin/ +COPY --from=corrosion-image-tarball /corrosion.tar /images/corrosion.tar +COPY scripts/docker/dind scripts/docker/entrypoint.sh /usr/local/bin/ COPY --from=uncloudd /build/uncloudd /usr/local/bin/ # TODO: socat to forward uncloud.sock unix socket? -ENTRYPOINT ["docker-entrypoint.sh"] +ENTRYPOINT ["entrypoint.sh"] CMD ["uncloudd"] diff --git a/scripts/docker-entrypoint.sh b/scripts/docker-entrypoint.sh deleted file mode 100755 index a1e86d73..00000000 --- a/scripts/docker-entrypoint.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/sh -set -eu - -dind dockerd & - -exec "$@" diff --git a/scripts/docker/dind b/scripts/docker/dind new file mode 100755 index 00000000..d552d36c --- /dev/null +++ b/scripts/docker/dind @@ -0,0 +1,83 @@ +#!/bin/sh +# This is a fork of https://github.com/moby/moby/blob/65cfcc28ab37cb75e1560e4b4738719c07c6618e/hack/dind +# with a fix for cgroup v2 initialization races. + +set -e + +# DinD: a wrapper script which allows docker to be run inside a docker container. +# Original version by Jerome Petazzoni +# See the blog post: https://www.docker.com/blog/docker-can-now-run-within-docker/ +# +# This script should be executed inside a docker container in privileged mode +# ('docker run --privileged', introduced in docker 0.6). + +# Usage: dind CMD [ARG...] + +# apparmor sucks and Docker needs to know that it's in a container (c) @tianon +# +# Set the container env-var, so that AppArmor is enabled in the daemon and +# containerd when running docker-in-docker. +# +# see: https://github.com/containerd/containerd/blob/787943dc1027a67f3b52631e084db0d4a6be2ccc/pkg/apparmor/apparmor_linux.go#L29-L45 +# see: https://github.com/moby/moby/commit/de191e86321f7d3136ff42ff75826b8107399497 +export container=docker + +# Allow AppArmor to work inside the container; +# +# aa-status +# apparmor filesystem is not mounted. +# apparmor module is loaded. +# +# mount -t securityfs none /sys/kernel/security +# +# aa-status +# apparmor module is loaded. +# 30 profiles are loaded. +# 30 profiles are in enforce mode. +# /snap/snapd/18357/usr/lib/snapd/snap-confine +# ... +# +# Note: https://0xn3va.gitbook.io/cheat-sheets/container/escaping/sensitive-mounts#sys-kernel-security +# +# ## /sys/kernel/security +# +# In /sys/kernel/security mounted the securityfs interface, which allows +# configuration of Linux Security Modules. This allows configuration of +# AppArmor policies, and so access to this may allow a container to disable +# its MAC system. +# +# Given that we're running privileged already, this should not be an issue. +if [ -d /sys/kernel/security ] && ! mountpoint -q /sys/kernel/security; then + mount -t securityfs none /sys/kernel/security || { + echo >&2 'Could not mount /sys/kernel/security.' + echo >&2 'AppArmor detection and --privileged mode might break.' + } +fi + +# Mount /tmp (conditionally) +if ! mountpoint -q /tmp; then + mount -t tmpfs none /tmp +fi + +# cgroup v2: enable nesting +if [ -f /sys/fs/cgroup/cgroup.controllers ]; then + # move the processes from the root group to the /init group, + # otherwise writing subtree_control fails with EBUSY. + # An error during moving non-existent process (i.e., "cat") is ignored. + mkdir -p /sys/fs/cgroup/init + xargs -rn1 < /sys/fs/cgroup/cgroup.procs > /sys/fs/cgroup/init/cgroup.procs || : + # enable controllers with retry on cgroup initialization races + timeout 5s sh -c "until sed -e 's/ / +/g' -e 's/^/+/' < /sys/fs/cgroup/cgroup.controllers \ + > /sys/fs/cgroup/cgroup.subtree_control 2>/dev/null; do sleep 0.1; done" +fi + +# Change mount propagation to shared to make the environment more similar to a +# modern Linux system, e.g. with SystemD as PID 1. +mount --make-rshared / + +if [ $# -gt 0 ]; then + exec "$@" +fi + +echo >&2 'ERROR: No command specified.' +echo >&2 'You probably want to run hack/make.sh, or maybe a shell?' diff --git a/scripts/docker/entrypoint.sh b/scripts/docker/entrypoint.sh new file mode 100755 index 00000000..b54c4775 --- /dev/null +++ b/scripts/docker/entrypoint.sh @@ -0,0 +1,12 @@ +#!/bin/sh +set -eu + +dind dockerd & +echo "Waiting for Docker in Docker to be ready..." +timeout 5s sh -c "until docker info &> /dev/null; do sleep 0.1; done" +echo "Docker in Docker is ready." + +echo "Loading corrosion image from /images/corrosion.tar..." +docker load < /images/corrosion.tar + +exec "$@"