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
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ module follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
- The runner-token store reads as part of the runner family: `tokens` is now `runner_tokens`, alongside `runners` and `runner_defaults`. The automatic `Sensitive` wrap on lookup moves with the new key; the host's secret store file must follow the rename.
- The apply script now installs on any declared-standalone host (`standalone.manage: true`), not only with the self-update loop, so manual applies and the loop share the same single apply command; enabling the loop on a host not declared standalone fails at compile time.
- The liveness healthcheck now installs on any declared-standalone host (`standalone.manage: true`), not only with the self-update loop, so a standalone host without the loop still has its manager service and rootless Docker daemon monitored (cadence `standalone.healthcheck_interval`); the loop's own supervision checks (apply-timer state, checkout staleness, bootstrap-gem presence) layer on only when the loop is enabled.
- Freshly provisioned hosts now get a 165536-wide subordinate-ID range, which satisfies the ID-mapping requirement of nested rootless BuildKit builds.
- Freshly provisioned hosts get a 165536-wide subordinate-ID range out of the box, so nested rootless BuildKit builds work without tuning.
- `rootless_docker.subid_count` is now a declared minimum width the module enforces. A managed host narrower than declared is widened in place and the rootless daemon restarted so the change takes effect; a range too narrow to work fails the apply with guidance instead of failing later inside a job; and a range that does not mirror the host, or overlaps another user's allocation, is flagged on every apply.
- Subordinate UID/GID ranges are now provisioned by `rootless_docker.manage`, together with the other rootless-Docker prerequisites, instead of by the runner-account toggle: rootless Docker can now be brought up on a host whose runner account is owned by another system. An existing range entry is never overwritten, and `runner_account.manage` keeps owning the group, user, and home.
- The managed runner service's `DOCKER_HOST` is now module-owned and derived from `runner_account.uid`; `runner_service.environment` carries additional variables only and rejects a `DOCKER_HOST` line, and a managed service requires `runner_account.uid`.

Expand Down
12 changes: 9 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,13 @@ by construction where the module creates the account.

With `rootless_docker.manage` on, the module brings up the rootless-Docker user daemon:
provisions the subordinate UID/GID ranges the daemon needs (`subid_start`/`subid_count`,
default `231072`/`165536`; an existing entry is never overwritten), enables lingering and runs
default `231072`/`165536`) and enforces `subid_count` as a grow-only minimum width. A
module-owned range at the declared start narrower than `subid_count` is widened in place (a
single locked `usermod` del-before-add) and the rootless daemon restarted so its user namespace
picks up the new width; a foreign or already-wider range is never rewritten (a too-narrow
foreign range fails the preflight, a wide-enough one warns). Widening an in-service host
interrupts running jobs (rootless mode has no live-restore), so it is best scheduled around
them; CI-side job `retry` covers a job caught by the restart. It then enables lingering and runs
`dockerd-rootless-setuptool.sh install` [\[4\]](#ref-4) as the runner user (guarded on installed state, so it
runs only until the rootless daemon's user unit exists). The ranges are provisioned for the
runner user whether the module owns the account ([`runner_account`](#runner_account))
Expand All @@ -318,8 +324,8 @@ it is the `no-detach-netns` drop-in, layered on top as a systemd override so bot
the other changing.

The whole chain sits behind a fail-loud preflight that asserts the prerequisites (`newuidmap` present,
subordinate IDs of at least 65,536, lingering enabled, cgroup v2) and aborts with a clear
message — only active when `rootless_docker.manage` is `true`. On a host where everything is already in place the
the runner user's subordinate IDs totalling at least the declared `subid_count`, lingering enabled,
cgroup v2) and aborts with a clear message — only active when `rootless_docker.manage` is `true`. On a host where everything is already in place the
chain is a no-op.

The toggle also **stops and masks the rootful system
Expand Down
17 changes: 11 additions & 6 deletions REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -276,9 +276,11 @@ running the daemon unprivileged.
Options:

* **:manage** `Boolean`: Whether to bring up the rootless docker user daemon: provision the
subordinate UID/GID ranges (an existing entry is never overwritten, and
the runner account may be owned elsewhere), enable lingering and run
`dockerd-rootless-setuptool.sh install` (guarded, as the runner user),
subordinate UID/GID ranges and enforce `subid_count` as a grow-only minimum
width (a module-owned range narrower than declared is widened in place and
the rootless daemon restarted; a foreign or already-wider range is never
rewritten, and the runner account may be owned elsewhere), enable lingering
and run `dockerd-rootless-setuptool.sh install` (guarded, as the runner user),
behind a fail-loud preflight that asserts the prerequisites instead of
half-installing. Also stops and masks the rootful system
`docker.service`/`docker.socket` and `containerd.service`, which a fresh
Expand All @@ -288,10 +290,13 @@ not managed (it would be the module's only system-wide write); without it
CPU/IO job limits are silently unenforced — see the README Limitations.
Default false.
* **:subid_start** `Integer[1]`: First subordinate UID/GID allocated to the runner account (field two of
the `/etc/subuid` entry format, per `subid(5)`). Default 231072.
the `/etc/subuid` entry format, per `subid(5)`). Default 231072. shadow's
own auto-allocation hands out 65536-wide ranges from 100000, so the third
auto-allocated user on a host starts at 231072; a range overlapping another
user's is warned about at compile time.
* **:subid_count** `Integer[65536]`: Number of subordinate UIDs/GIDs allocated (field three of the entry;
rootless docker needs at least 65536). Default 165536, which also covers
BuildKit's rootless image layout.
rootless docker needs at least 65536), enforced as a grow-only minimum
width. Default 165536, which also covers BuildKit's rootless image layout.

##### <a name="-rootless_gitlab_runner--runner_service"></a>`runner_service`

Expand Down
15 changes: 15 additions & 0 deletions lib/facter/rootless_gitlab_runner_subids.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# frozen_string_literal: true

require 'puppet_x/rootless_gitlab_runner/subids'

# Structured view of the host's subordinate UID/GID allocations, backing the
# compile-time width and overlap advisories in rootless_gitlab_runner. rspec
# compiles a catalog and cannot see host state, so the class reads this fact to
# compare the declared range against what /etc/subuid and /etc/subgid grant.
# Value shape: { 'subuid' => { '<owner>' => [ { 'start', 'count' } ] }, 'subgid' => {…} }.
# Parsing lives in PuppetX::RootlessGitlabRunner::Subids so it is testable
# without Facter; this fact is the thin IO adapter. Puppet puts each module's
# lib/ on $LOAD_PATH after pluginsync, so the require resolves on the agent.
Facter.add(:rootless_gitlab_runner_subids) do
setcode { PuppetX::RootlessGitlabRunner::Subids.read }
end
34 changes: 34 additions & 0 deletions lib/puppet_x/rootless_gitlab_runner/subids.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# frozen_string_literal: true

module PuppetX
module RootlessGitlabRunner
# Pure parsing of the subordinate UID/GID files (subuid(5)), split from the
# fact that reads them so the logic is unit-testable without Facter or the
# filesystem. The fact (and any future consumer) share this one parser.
module Subids
FILES = { 'subuid' => '/etc/subuid', 'subgid' => '/etc/subgid' }.freeze

module_function

# Parse subuid/subgid lines into { owner => [ { 'start', 'count' } ] }.
# 'owner' is field one verbatim (the name usermod writes); malformed and
# non-numeric lines are skipped.
def parse(lines)
lines.each_with_object({}) do |line, owners|
owner, start, count = line.strip.split(':')
next if owner.nil? || start !~ /\A\d+\z/ || count !~ /\A\d+\z/

(owners[owner] ||= []) << { 'start' => start.to_i, 'count' => count.to_i }
end
end

# Read and parse each file, keyed by kind. An unreadable file yields an
# empty map, so a host without it simply carries no advisory input.
def read(files = FILES)
files.transform_values do |path|
File.readable?(path) ? parse(File.foreach(path)) : {}
end
end
end
end
end
17 changes: 11 additions & 6 deletions manifests/init.pp
Original file line number Diff line number Diff line change
Expand Up @@ -149,9 +149,11 @@
# running the daemon unprivileged.
# @option rootless_docker [Boolean] :manage
# Whether to bring up the rootless docker user daemon: provision the
# subordinate UID/GID ranges (an existing entry is never overwritten, and
# the runner account may be owned elsewhere), enable lingering and run
# `dockerd-rootless-setuptool.sh install` (guarded, as the runner user),
# subordinate UID/GID ranges and enforce `subid_count` as a grow-only minimum
# width (a module-owned range narrower than declared is widened in place and
# the rootless daemon restarted; a foreign or already-wider range is never
# rewritten, and the runner account may be owned elsewhere), enable lingering
# and run `dockerd-rootless-setuptool.sh install` (guarded, as the runner user),
# behind a fail-loud preflight that asserts the prerequisites instead of
# half-installing. Also stops and masks the rootful system
# `docker.service`/`docker.socket` and `containerd.service`, which a fresh
Expand All @@ -162,11 +164,14 @@
# Default false.
# @option rootless_docker [Integer[1]] :subid_start
# First subordinate UID/GID allocated to the runner account (field two of
# the `/etc/subuid` entry format, per `subid(5)`). Default 231072.
# the `/etc/subuid` entry format, per `subid(5)`). Default 231072. shadow's
# own auto-allocation hands out 65536-wide ranges from 100000, so the third
# auto-allocated user on a host starts at 231072; a range overlapping another
# user's is warned about at compile time.
# @option rootless_docker [Integer[65536]] :subid_count
# Number of subordinate UIDs/GIDs allocated (field three of the entry;
# rootless docker needs at least 65536). Default 165536, which also covers
# BuildKit's rootless image layout.
# rootless docker needs at least 65536), enforced as a grow-only minimum
# width. Default 165536, which also covers BuildKit's rootless image layout.
# @param runner_service
# The systemd system service running the runner manager, privilege-dropped
# to the runner account.
Expand Down
Loading