//go:build !workflows /* The workflows program manages the .gitea directory to avoid having to write yaml. It is never invoked directly or built for the distribution tarball. This program is an internal detail of the hakurei project and is not usable on its own. It is not covered by the compatibility promise. # Configuring act_runner It is unreasonable and impractical to bootstrap Rosa OS each time a job runs. This section documents an ideal configuration for exposing the cmd/mbf CI service to all job containers, while avoiding some issues with the container setup documented upstream. Microsoft Github workflows expect a docker socket in the job container. This is problematic because docker has a hard time running inside a docker container. The Gitea act_runner simply bind mounts whatever socket it sees into the container. With a regular docker daemon, this allows not only a simple container escape, but also privilege escalation as unconstrained root in the init namespace. To mitigate this, set up an unprivileged podman daemon and expose its socket to the container instead. On Alpine Linux, this is achieved by: # Configuration for /etc/init.d/podman # See podman-system-service(1) for service description # and available options. #podman_opts="--time 0" # API endpoint in URI form. Leave empty to use defaults. podman_uri="unix:///srv/act_runner/podman" # Setting root user will start rootful service. # Use any other user for rootless mode. podman_user="act_runner" A symlink must then be created at /var/run/docker.sock pointing to the socket owned by the unprivileged daemon. This should be done in a /etc/local.d script. The container can be specified via docker-compose: version: "3.8" services: runner: restart: always image: docker.io/gitea/act_runner:nightly network_mode: host environment: CONFIG_FILE: /config.yaml GITEA_INSTANCE_URL: "https://git.gensokyo.uk" GITEA_RUNNER_LABELS: "rosa:docker://docker.gitea.com/runner-images:ubuntu-latest" GITEA_RUNNER_REGISTRATION_TOKEN: "${REGISTRATION_TOKEN}" GITEA_RUNNER_NAME: "${RUNNER_NAME}" volumes: - ./config.yaml:/config.yaml - ./data:/data - ./podman:/var/run/docker.sock Before starting the container, configure act_runner via config.yaml: container: options: -e PAGER=cat -e MBF_CACHE_DIR=/rosa -e MBF_POISON_OPEN=1 -v /var/lib/rosa:/rosa --security-opt='unmask=/proc/*' --cap-add=SYS_ADMIN --device=/dev/kvm --device=/dev/fuse valid_volumes: - /var/lib/rosa where /var/lib/rosa is the absolute pathname of the cache directory in the init namespace. Setting MBF_POISON_OPEN enables cmd/mbf to run as root. It is also a good idea here to set runner.capacity to reflect the capacity of the guest, so jobs can be consumed quicker. Removing mount points covering /proc enables testing of cmd/hakurei. Exposing the fuse device and adding capability SYS_ADMIN enables testing of cmd/sharefs. Build a statically-linked cmd/mbf: go build -trimpath \ -ldflags='-s -w -buildid= -linkmode external -extldflags=-static' \ ./cmd/mbf Create a directory named "bin" in the cache directory and install the resulting binary. The cmd/mbf CI service should generally run as a separate user. To achieve this, change the socket permission to 0777 after the service starts. On Alpine Linux, this can be achieved by the init script: #!/sbin/openrc-run supervisor=supervise-daemon name="Rosa OS cmd/mbf CI backend" description="Listening service that services CI workloads from act_runner" command=/var/lib/rosa/bin/mbf command_args="-d /var/lib/rosa ci daemon" command_user="${mbf_user:=mbf}" output_logger="logger" error_logger="logger" depend() { need sysfs } start_post() { while [ ! -e /var/lib/rosa/ci ]; do sleep 0.1; done chmod 0777 /var/lib/rosa/ci } It is often a good idea to populate the cache from a mirror service before the first workflow job is started and re-populate it after every cmd/mbf update. # Security The design of Microsoft Github workflows is inherently insecure: it requires internet access just to fetch actions, and these actions all expect internet access as well. Gitea, being a drop-in replacement, retains the same problems. The setup documented above already avoids the obvious container escape via the bind-mounted docker socket, but it is still easily possible to escape as the context of the unconstrained runner user. For this reason, the runner must reside in a hardened guest. Since internet access cannot be disabled, extra care must be taken when setting up the host firewall to prevent access to internal systems from the untrusted guest. */ package workflows //go:generate go run -tags=workflows .