newusers: create or update accounts in batch - #293
Open
pierre-warnier wants to merge 2 commits into
Open
Conversation
newusers is the eighteenth tool and the third of the nine that both Debian
and Fedora ship and we did not. One input line of seven fields produces a
whole account: the passwd record, the hashed shadow record, a group, and the
home directory with the skeleton copied in.
Every line is parsed, every name validated, every group resolved and every
password hashed before anything is written. A batch with one bad line leaves
the system exactly as it was, including for the good lines above it -- the
failure mode is "nothing happened", not "the first two hundred accounts
exist and the rest do not". That is the whole reason to reach for this tool
over a shell loop around useradd.
Home directory creation moved to shadow_core::home rather than being copied.
useradd's version forces the umask so the requested mode is exact, and hands
over ownership through a descriptor opened O_NOFOLLOW so nobody who can write
the parent can swap in a symlink between the mkdir and the chown. A second
copy of that is a second chance to get it wrong, and the mistake would be
silent. useradd now calls the shared one and its own tests still pass.
Three deliberate divergences, all established by running the GNU tool:
- An empty password field is refused before anything is written. Hashing
"" gives a valid hash that a bare Enter matches. GNU passes the empty
field to PAM, which refuses it *after* creating the account, leaving a
half-made account behind -- verified.
- A pw_gid naming a group that does not exist is refused. GNU falls back to
the user's own ID and creates no group, so the account is left pointing
at a GID that is not there, which grpck then reports.
- A missing parent directory is created, as useradd -b does. GNU's newusers
fails there, which makes it disagree with GNU's own useradd on the same
path.
--badname is implemented rather than stubbed: it drops the portability rules
on the login name while keeping the checks that stop a name from corrupting
the file or being read as an option, which is what makes the flag safe to
offer at all. It is what lets a domain-qualified name from a directory join
through.
The e2e suite re-hashes the stored field with its own salt and compares, so a
tool that wrote a well-formed hash of the wrong string would fail; the
negative control, the same check against a different password, is asserted to
fail. An earlier version of that assertion ended in `|| true` and was
therefore always green -- worth saying, because it is the failure mode these
checks exist to catch.
Verified: 828 tests on debian, alpine and fedora; make check clean, including
the unprivileged run; 297 e2e assertions against a real install; 45 GNU
comparisons; 22 arm64 assertions under emulation.
for more information, see https://pre-commit.ci
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds
newusers, the eighteenth tool: one line of seven fields becomes a whole account.Stacked on #292. Base retargets to
mainas the stack merges.For each line it writes the
/etc/passwdrecord, the hashed/etc/shadowrecord, a group where one is needed, and the home directory with/etc/skelcopied in.All or nothing
Every line is parsed, every name validated, every group resolved and every password hashed before anything is written. A batch with one bad line leaves the system exactly as it was, including for the good lines above it.
The failure mode is nothing happened, not the first two hundred accounts exist and the rest do not. That is the whole reason to reach for this tool instead of a shell loop around
useradd.Home creation is now shared, not copied
useradd's version forces the umask so the requested mode is exact, and hands ownership over through a descriptor openedO_NOFOLLOW— so nobody who can write the parent can swap in a symlink between themkdirand thechown. Copying that into a second tool is a second chance to get it wrong, silently. It moved toshadow_core::home;useraddcalls it and its own tests still pass.Deliberate divergences
All three established by running the GNU tool, not by reading it:
pw_gidnaming a group that does not existuseradd -buseradd -bdoes--badnameis implemented rather than stubbed: it drops the portability rules on the login name but keeps the checks that stop a name from corrupting the file or being read as an option, which is what makes the flag safe to offer. That is what lets a domain-qualified name from a directory join through.Verification
cargo test --workspacemake checkpam, plus the unprivileged run — exit 0make test-gnu-compatmake test-arm64The e2e section re-hashes the stored field with its own salt and compares, so a tool writing a well-formed hash of the wrong string fails; the negative control — the same check against a different password — is asserted to fail. An earlier draft of that assertion ended in
|| trueand was therefore always green, which is worth saying out loud, because it is exactly the failure mode these checks exist to catch.