Files
dejima-ci-hardener/examples/docker-compose-privileged.yml
T
Brad Anderson d88b172da1
tests / runner-env (confined) (pull_request) Failing after 1m49s
tests / tests (pull_request) Successful in 1m51s
tests / compose-schema (pull_request) Successful in 1m35s
tests / runner-env (privileged) (pull_request) Successful in 1m24s
Let one host carry runners for more than one registration token
A registration token is minted at a scope and serves only repositories inside
it, so serving both an organization and a user account from one machine takes
two registrations. There was no way to express that: one invocation owned
~/runners, and a second would have overwritten the first.

The obstacle was that the label did too much. It named the runs-on label AND
the compose project, the container names and the volumes - and two groups on
one host usually want the SAME label, since a job from either scope should
reach a runner. Sharing the label was fine; sharing an identity meant the
second group replacing the first.

So identity is now --name, separate from --label. It scopes the directory, the
compose project, the containers, the volumes and the runner names in the forge;
the label scopes nothing and is free to be shared. --name defaults to the label,
so a host with one group needs no new argument and gets what it got before.

A name containing a slash or a space is refused rather than producing a
directory or a compose project that does not mean what it says.

Three tests: two groups sharing a label collide nowhere and appear in nothing
of each other's; the default keeps a single group working without --name; and
unusable names are refused. Also rendered two groups end to end and confirmed
separate projects, separate container names and two 0600 token files.

79 ok, 0 failed.
2026-08-08 18:29:10 -07:00

78 lines
3.6 KiB
YAML

# The privileged runner, on its own, in its own file.
#
# One service, deliberately. This runner is handed the Docker daemon and the
# host's network namespace, because one job cannot work without them, and
# neither grant can be reduced without breaking that job. A job on this runner
# can take the machine it runs on.
#
# So the machine it runs on should be one that exists for that and holds
# nothing else: no other service, no route to a LAN or management network, no
# credentials beyond its own registration token, rebuilt from a snapshot on a
# schedule. That is why this is a separate file rather than another service
# alongside the confined runners - a separate host is the intended
# arrangement, and a compose file that assumes it is harder to drift away from
# than one that has to be split later.
#
# Where the two tiers do share a machine, be clear about what that is: label
# tiering, not host separation. A job denied the socket still shares a kernel
# with a runner that has it. That is a real reduction over granting everything
# to everything, and it is not isolation between the tiers.
#
# @@NOTE_BEGIN@@
# TEMPLATE. `dejima-ci-runners` renders this to ~/runners/docker-compose.yml,
# substituting the @@MARKER@@ values. This header is replaced in the rendered
# copy.
# @@NOTE_END@@
name: "@@PROJECT@@"
services:
"@@NAME@@":
# Pin the tag. This process executes untrusted code on every pull request,
# and :latest means the thing doing that can change without you deciding
# it should.
image: "@@IMAGE@@"
restart: unless-stopped
container_name: "@@NAME@@"
networks: [runners]
environment:
# Without this the config file is not read AT ALL - the runner starts on
# built-in defaults instead, and on this tier the defaults happen to
# resemble the intended configuration closely enough that the mistake is
# invisible. Check it first when an edit appears to do nothing.
CONFIG_FILE: /config.yaml
GITEA_INSTANCE_URL: "@@INSTANCE_URL@@"
GITEA_RUNNER_NAME: "@@NAME@@"
# Needed only until registration completes; the identity then lives in
# the volume below. Comes from a .env file beside this one (chmod 600)
# so it is not in the file you commit, and is worth removing once
# registration succeeds - anyone holding a registration token can
# register a runner and start receiving jobs, secrets included.
GITEA_RUNNER_REGISTRATION_TOKEN: ${GITEA_RUNNER_REGISTRATION_TOKEN:-}
volumes:
# The runner's own access to the daemon, so it can create job
# containers. On this tier the config file ALSO passes the socket into
# those containers, which is the grant that makes this host sacrificial;
# on the confined tier it does not. Same bind, different consequence,
# decided entirely by container.docker_host in the config.
- /var/run/docker.sock:/var/run/docker.sock
- ./config.yaml:/config.yaml:ro
# Its own registration identity. Never shared with another runner.
- "@@NAME@@-data:/data"
# No bind for /tmp/dejima-ci. The job container gets that path because
# the workflow asks for it and container.valid_volumes permits it, and
# the host daemon resolves the source host-side. This container never
# sees it.
networks:
# This runner's own network, for reaching your Gitea instance. The job
# network is deliberately absent here as well: jobs on this tier are in the
# host's network namespace, which is granted by container.network in the
# config file and is not something compose can express or restrain.
runners:
volumes:
"@@NAME@@-data":