Repository navigation
fix(garm): set USER/LOGNAME in the runner .env to the UID account - #437
Conversation
GARM starts the runner as `runner`, which the runner images create as a
duplicate-UID alias of `ubuntu` (useradd --non-unique --uid "$(id -u ubuntu)",
home /home/ubuntu, /home/runner -> /home/ubuntu). Job steps therefore read
$USER=runner, while every lookup keyed on the UID - getpwuid(1000), and with it
sg, newgrp, chown, and sbuild's schroot user - resolves to `ubuntu`, the first
entry for that UID in /etc/passwd.
A step that passes $USER to one of those tools acted on a different account than
the one the tool checked. Concretely, canonical/toolchains-ci does:
sudo usermod -aG sbuild $USER # adds runner
sg sbuild -c 'mk-sbuild ...' # checks ubuntu -> password prompt
and fails with "Password: Invalid password." because the ubuntu account has a
locked password. This affects any workflow using $USER with a UID-based tool,
not just sbuild.
The runner applies <runner root>/.env to its own environment before any job
starts (actions/runner src/Runner.Listener/Program.cs, LoadAndSetEnv), so
writing USER and LOGNAME there makes $USER agree with the passwd entry the UID
resolves to, and steps inherit it.
The snippet is injected into github_linux_charmed, which scale sets without
runner options reference directly; scale sets with runner options copy that
template as the base for their own, so both get the variables.
Values are written once per boot and are stripped before being rewritten, so a
reused instance cannot accumulate duplicate keys.
|
We need to further discuss what the long term solution to this underlying paradigm of two users would be ( |
cbartz
left a comment
There was a problem hiding this comment.
Makes sense as a stopgap: on the current images it only aligns $USER/$LOGNAME with what whoami/id -un already report, and it matches the legacy runners' ubuntu environment. Two things I'd like before merge, plus a few nits.
To address before merge
-
Resolve the account at runtime instead of hardcoding
ubuntu. The override is applied to every scale set, but it is only correct on images whererunneris a duplicate-UID alias ofubuntu. On an image whererunnerhas its own UID,USER=ubuntuwould be wrong andusermod -aG <group> $USERwould modify the wrong account. Resolving on the instance, e.g.getent passwd "$(id -u runner)" | cut -d: -f1with an unquoted heredoc, is a no-op on non-aliased images, correct on the current ones, and drops the cross-repoRUNNER_UID_USERconstant. -
Open a tracking issue for removing this override and link it from the code. This is a workaround for the duplicate-UID layout of the runner images, and the long-term fix is still to be decided at the design meeting. An issue gives that discussion a home and gives this override a planned removal. Without one, a charm-wide change to
$USERtends to stay forever. Referencing it from thebuild_runner_env_snippetdocstring (and the changelog entry) tells the next reader when it can go.
Nits
- PR description, "What this does not fix": a
CharmedTemplateErrordoesn't make scale sets fall back to the base template._reconcileraises beforeScalesetReconcilerruns, the unit goes toWaitingStatus, and existing scale sets keep whatever template they already reference. - The PR body doesn't follow
.github/pull_request_template.md("What this PR does" / "Why we need it" / checklist). The existing content maps onto it easily, and the checklist (changelog, tests, AI assistance) is worth ticking. - See the inline comments.
AI-generated message.
GARM starts the runner as `runner`, which the runner images create as a duplicate-UID alias of `ubuntu`. Job steps therefore read $USER=runner while every lookup keyed on the UID - getpwuid(1000), and with it sg, newgrp, chown and sbuild's schroot user - resolves to `ubuntu`. A step that hands $USER to one of those tools acts on a different account than the tool checks, e.g. toolchains-ci's `usermod -aG sbuild $USER` followed by `sg sbuild`, which fails with "Password: Invalid password.". The runner applies <runner root>/.env to its own environment before any job starts (actions/runner Program.cs, LoadAndSetEnv), so writing USER and LOGNAME there makes $USER agree with the passwd entry the UID resolves to. The account is resolved on the instance rather than hardcoded, so the snippet stays correct if the image gives the account its own UID. One writer now renders the whole .env block: the account pair is written with printf because the value must expand on the instance, and the tmate variables keep their quoted heredoc so relation-provided values are not expanded. Removal is tracked in #438.
3528fee to
de3dd9c
Compare
The e2e job runs on a GARM-spawned runner, the only place the duplicate-UID
account layout shows up at job level. It printed whoami but never compared it
with $USER, so the toolchains-ci failure ("Password: Invalid password.") went
unnoticed even though the disagreeing value was in the log.
Adds three steps:
- assert $USER and LOGNAME equal the account the UID resolves to
- assert usermod -aG <group> "$USER" then sg <group> succeeds, the pattern from
the report; the step is a new process whose groups predate the usermod, so sg
must resolve the caller from the UID
- report the runner uid/gid entry counts so #438 can tell when the .env override
is no longer needed (non-gating, independent of the invoking account)
A parallel session committed task.md (an unrelated security-group plan) and a '#'-prefixed note inside build_env_snippet's docstring. The note duplicates the TODO(#438) comment below it and reads as literal docstring text. task.md stays on disk, untracked; only its tracking is removed.
cbartz
left a comment
There was a problem hiding this comment.
Approving: all points from my earlier review are addressed. The account is now looked up on the instance, so the override does nothing on images where runner has its own UID; removal is tracked in #438; and the .env writers are merged into one. I checked GARM's github_linux template: it runs chown -R on the runner directory and chown on .env itself after install, so creating .env as root first doesn't affect ownership.
Recommendation before merging: please dispatch the GARM e2e run (garm_e2e_test_run.yaml) on this branch. So far the fix has been tested with unit tests and a dry run on a noble host. The new identity and usermod -aG + sg checks are the only way to confirm that the runner applies the .env override to job steps on a real GARM-spawned runner, including in agent mode.
AI-generated message.
What this PR does
Writes
USERandLOGNAMEto the runner.envfor every GARM scale set, set to the account the runner's UID resolves to. The account is resolved on the instance, not hardcoded.Also adds the missing assertion to the GARM e2e job: it runs on a real GARM-spawned runner and now checks that the job identity matches its UID and that
usermod -aGfollowed bysgsucceeds.Why we need it
GARM starts the runner as
runner, which the runner images create as a duplicate-UID alias ofubuntu:One process, two names, and group membership is stored per name:
So a step that hands
$USERto a UID-keyed tool writes one name and is checked as the other:This is the
Setup build prerequisitesfailure incanonical/toolchains-cion every GARM pool. Legacy runners pass becausesu - ubuntumakes both sidesubuntu.Fix. The runner applies
<runner root>/.envto its own environment before any job starts (actions/runner,src/Runner.Listener/Program.cs,LoadAndSetEnv), and steps inherit it. The charm already deliversACTIONS_RUNNER_HOOK_JOB_STARTED,DOCKERHUB_MIRRORand the tmate variables that way.Where it lands. Injected into
github_linux_charmed, so it reaches every GARM scale set:What this does not fix
The alias remains; only
$USERand$LOGNAMEchange. Removal of this override is tracked in #438.runner.usermod -aG sbuild runnerfollowed bysg sbuildstill ends inPassword: Invalid password.runner:getent group dockerlistsubuntu,sudo -l -U runnerdoes not, and name-keyed policy (limits.conf,pam_access,getpwnam("runner")) is evaluated againstubuntu.systemd-run,systemctl --user,cron,docker execand container steps do not inherit.env.logname/getlogin(). Still empty — no login session, as today.github_linux_charmed. If the charm cannot reach GARM, the unit reportsWaitingStatusbeforeScalesetReconcilerruns: existing scale sets keep whatever template they already reference and no new ones are created, so the block is not silently dropped from live scale sets.Test plan
tox -e unit: 449 passedtox -e lint,tox -e static,tox -e fmt: cleanbash -nUSER=runner(today's layout, before the fix) and 0 withUSER=ubuntu; theusermod -aG+sgstep passes; the layout report prints2 passwd entry/entriesfor the alias layout and1oncerunnerhas its own UIDgarm_e2e_test_run.yamlnow asserts, on a real GARM-spawned runner, that$USERandLOGNAMEequal the UID-resolved account and thatusermod -aG <group> "$USER"thensg <group>succeeds — the pattern from the original report. Before this PR that job printedwhoamibut asserted nothing, so the disagreeing value sat in the logs unnoticed.New unit coverage: no account name is baked into the template, the lookup is emitted, both assignments go through the format string, an empty lookup is guarded, the tmate block keeps its quoted heredoc, the block lands before the base
set -e, and exactly oneUSERassignment is written.Review focus
printfwhile the tmate block keeps a quoted heredoc: the account value must expand on the instance, relation-provided values must not.id -u runneris used instead ofid -u, because cloud-init runs the template as root.runnerhas its own UID the resolved value isrunner, so the override becomes a no-op rather than targeting the wrong account.Potential breaking changes
USER/LOGNAMEchange fromrunnertoubuntuin job steps on the current images. Both names share uid 1000 and gid 1000, so this is name-only forchown,sudo -uandusermod; commands that record the name (group files, logs) now record the oneid -unalready returned.Checklist
CONTRIBUTING.mdhas been updated upon changes to the contribution/development process — not applicabledocs/changelog.mdwith user-relevant changes.copilot-collections.yaml/.github/instructions/: not applicable