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
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -327,10 +327,12 @@ 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`) 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
module-owned range at the declared start narrower than `subid_count` is widened in place by
adding the missing tail as a further contiguous range: a pure `usermod --add`, which grows the
range even while the runner user's session is live and leaves the existing mappings as an
untouched prefix. The rootless daemon is then 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
Expand Down
70 changes: 48 additions & 22 deletions manifests/rootless_docker.pp
Original file line number Diff line number Diff line change
Expand Up @@ -57,19 +57,22 @@
# --noop cannot see, and drives every outcome from that one source:
# create — no entry yet: write start:count (the greenfield path). Always
# declared and grep-guarded, so it is correct even without the fact.
# widen — one entry, at the declared start, narrower than declared: rewrite
# it wider. The old width comes from the fact, so the command is
# fully literal — no shell arithmetic — and the guard re-asserts
# that exact line still exists, so a host changed since fact
# collection is skipped, not mis-edited. shadow 4.8.1 deletes
# before adding in the one locked invocation (never momentarily
# absent), and widening at an unchanged start preserves every
# existing inner->outer mapping (a prefix superset), so container
# and image ownership is untouched.
# widen — a contiguous block anchored at the declared start but narrower
# than declared: add only the missing tail as a further contiguous
# range. The add is pure (no --del), so it succeeds even while the
# runner user has a live session; --del would trip usermod's
# running-process guard on the lingering systemd --user manager and
# fail on exactly the converged hosts that need a widen. The tail
# offset comes from the fact, so the command is literal, and the
# guard re-asserts the covered width still matches, so a host
# changed since fact collection is skipped, not double-added. The
# existing lower ranges are left untouched (a prefix superset) and
# rootlesskit maps every range, so the effective width is their sum
# and container/image ownership is untouched.
# advise — everything else is neither created nor widened: a wider-than-
# declared entry, or a foreign/multi-entry range wide enough in
# sum, warns (declared data must mirror the host); a too-narrow
# foreign range fails loud at the preflight below.
# declared entry, or a foreign-start, gapped, or overlapping range
# wide enough in sum, warns (declared data must mirror the host); a
# too-narrow foreign range fails loud at the preflight below.
# warning() is a log line, not a resource, so the apply stays change-free and
# --noop clean (an exec/notify warning would report a change every apply);
# only the exact-match state is silent. Guards test host state and exit 0/1 —
Expand All @@ -79,8 +82,8 @@
$subid_last = $subid_first + $subid_count - 1
$subid_report = $facts['rootless_gitlab_runner_subids']
$subid_files = {
'subuid' => { 'add' => '--add-subuids', 'del' => '--del-subuids' },
'subgid' => { 'add' => '--add-subgids', 'del' => '--del-subgids' },
'subuid' => { 'add' => '--add-subuids' },
'subgid' => { 'add' => '--add-subgids' },
}
$subid_files.each |$f, $flag| {
# This file's entries, from the fact: all owners, and the runner user's own
Expand All @@ -97,14 +100,36 @@
before => Exec['rootless_gitlab_runner preflight'],
}

# widen / advise on the runner user's own entry.
if $ranges.length == 1 and $ranges[0]['start'] == $subid_first {
$have = $ranges[0]['count']
# widen / advise on the runner user's own entries. The create path and an
# additive widen both leave the runner's ranges as one contiguous block
# anchored at the declared start; detect that shape and its covered width by
# folding the ranges (ascending) from subid_first, breaking on the first
# gap, overlap, or foreign start.
$sorted = $ranges.sort |$a, $b| { $a['start'] - $b['start'] }
$fold = $sorted.reduce([$subid_first, true]) |$acc, $r| {
($acc[1] and $r['start'] == $acc[0]) ? {
true => [$acc[0] + $r['count'], true],
default => [$acc[0], false],
}
}
$anchored = $ranges.length > 0 and $fold[1]
$have = $fold[0] - $subid_first # covered width, valid when anchored

if $anchored {
if $have < $subid_count {
$old_last = $subid_first + $have - 1
# Grow-only, additive: add only the missing tail as a contiguous range.
# A pure --add succeeds even while the runner user has a live session;
# a --del would trip usermod's running-process guard on the lingering
# systemd --user manager, so delete-and-re-add fails on exactly the
# converged hosts that need a widen. The existing lower ranges are an
# untouched prefix, and rootlesskit maps every range, so the effective
# width is their sum. onlyif re-asserts the covered width the fact saw,
# so a host changed since fact collection is skipped (not double-added),
# and the widen no-ops once the sum reaches the count.
$tail_first = $subid_first + $have
exec { "rootless_gitlab_runner ${f} widen":
command => "usermod ${flag['del']} ${subid_first}-${old_last} ${flag['add']} ${subid_first}-${subid_last} ${runner_user}",
onlyif => "grep -qxF '${runner_user}:${subid_first}:${have}' /etc/${f}",
command => "usermod ${flag['add']} ${tail_first}-${subid_last} ${runner_user}",
onlyif => "awk -F: -v u='${runner_user}' '\$1==u{s+=\$3} END{exit !(s==${have})}' /etc/${f}",
path => ['/usr/sbin', '/usr/bin', '/bin'],
provider => 'shell',
before => Exec['rootless_gitlab_runner preflight'],
Expand All @@ -118,8 +143,9 @@
], ' '))
}
} elsif $ranges.length > 0 {
# Foreign start, or multiple entries: never rewritten. Wide enough in sum
# warns; too narrow is caught loud by the preflight, not here.
# Not an anchored contiguous block: a foreign start, a gap, or an internal
# overlap. Never rewritten. Wide enough in sum warns; too narrow is caught
# loud by the preflight, not here.
$summed = $ranges.reduce(0) |$m, $r| { $m + $r['count'] }
if $summed >= $subid_count {
warning(join([
Expand Down
64 changes: 56 additions & 8 deletions spec/classes/rootless_gitlab_runner_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -677,12 +677,11 @@ def module_warnings
let(:params) { base_params }
let(:facts) { subid_facts([{ 'start' => 231_072, 'count' => 65_536 }]) }

{ 'subuid' => ['--del-subuids', '--add-subuids'],
'subgid' => ['--del-subgids', '--add-subgids'] }.each do |f, (del, add)|
it "widens #{f} in place with a literal usermod, guarded on the exact current line" do
{ 'subuid' => '--add-subuids', 'subgid' => '--add-subgids' }.each do |f, add|
it "widens #{f} by adding only the missing tail, guarded on the covered width" do
is_expected.to contain_exec("rootless_gitlab_runner #{f} widen").with(
'command' => "usermod #{del} 231072-296607 #{add} 231072-396607 gitlab-runner",
'onlyif' => "grep -qxF 'gitlab-runner:231072:65536' /etc/#{f}",
'command' => "usermod #{add} 296608-396607 gitlab-runner",
'onlyif' => "awk -F: -v u='gitlab-runner' '$1==u{s+=$3} END{exit !(s==65536)}' /etc/#{f}",
'provider' => 'shell',
).that_comes_before('Exec[rootless_gitlab_runner preflight]')
end
Expand Down Expand Up @@ -777,6 +776,55 @@ def module_warnings
)
end
end

context 'an additive-widened range: two contiguous entries summing to the count' do
let(:params) { base_params }
let(:facts) do
subid_facts([{ 'start' => 231_072, 'count' => 65_536 },
{ 'start' => 296_608, 'count' => 100_000 }])
end

it 'is satisfied: no widen and no advisory' do
expect(module_warnings).to be_empty
%w[subuid subgid].each do |f|
is_expected.not_to contain_exec("rootless_gitlab_runner #{f} widen")
end
end
end

context 'an additive-widened range that a raised subid_count outgrows' do
let(:params) do
{ 'rootless_docker' => struct_param('rootless_docker', 'manage' => true, 'subid_count' => 262_144),
'runner_account' => account_with_uid(4242) }
end
let(:facts) do
subid_facts([{ 'start' => 231_072, 'count' => 65_536 },
{ 'start' => 296_608, 'count' => 100_000 }])
end

it 'adds a further tail from the covered width to the raised width' do
is_expected.to contain_exec('rootless_gitlab_runner subuid widen')
.with_command('usermod --add-subuids 396608-493215 gitlab-runner')
.with_onlyif("awk -F: -v u='gitlab-runner' '$1==u{s+=$3} END{exit !(s==165536)}' /etc/subuid")
end
end

context 'a gapped set of entries anchored at the declared start' do
let(:params) { base_params }
let(:facts) do
subid_facts([{ 'start' => 231_072, 'count' => 65_536 },
{ 'start' => 300_000, 'count' => 100_000 }])
end

it 'is not treated as contiguous: no widen, and a does-not-mirror advisory' do
expect(module_warnings).to include(
match(%r{/etc/subuid for gitlab-runner does not mirror the declared range 231072:165536}),
)
%w[subuid subgid].each do |f|
is_expected.not_to contain_exec("rootless_gitlab_runner #{f} widen")
end
end
end
end

context 'with rootless_docker.manage, a custom declared range and a narrower host range' do
Expand All @@ -787,10 +835,10 @@ def module_warnings
end
let(:facts) { subid_facts([{ 'start' => 300_000, 'count' => 70_000 }]) }

it 'computes both inclusive bounds from the fact width and the declared width' do
it 'adds only the missing tail from the fact width to the declared width' do
is_expected.to contain_exec('rootless_gitlab_runner subuid widen')
.with_command('usermod --del-subuids 300000-369999 --add-subuids 300000-499999 gitlab-runner')
.with_onlyif("grep -qxF 'gitlab-runner:300000:70000' /etc/subuid")
.with_command('usermod --add-subuids 370000-499999 gitlab-runner')
.with_onlyif("awk -F: -v u='gitlab-runner' '$1==u{s+=$3} END{exit !(s==70000)}' /etc/subuid")
end

it 'carries the declared non-default count into the summed preflight' do
Expand Down