Repository navigation
feat: support custom user accounts via ALCLESS_GROUP (macOS and Linux) - #91
Open
sonhanguyen wants to merge 5 commits into
Open
sonhanguyen wants to merge 5 commits into
sonhanguyen wants to merge 5 commits into
Conversation
Add support for using pre-existing user accounts with alcless via the ALCLESS_GROUP environment variable. When ALCLESS_GROUP is set: - Instance name is used as the username (instead of alcless_USER_INSTANCE) - Discovery is done via group membership - Pre-existing users can be set up for alcless with sudoers and group membership Backwards compatible: without ALCLESS_GROUP, behavior is unchanged. This commit also includes: - Comprehensive test coverage for the new functionality - Updated documentation in README and command help text - Fixed assert.Error usage in store tests - Removed inconsequential test coverage workflow step Signed-off-by: Harry Nguyen <sonhanguyen@users.noreply.github.com>
Add GroupUsers and GroupSetupCmds to the Linux userutil implementation. These were previously implemented only on Darwin, so ALCLESS_GROUP group mode failed to compile on Linux (pkg/store and cmd/.../create import both functions unconditionally). GroupUsers reads a group's supplementary members via `getent group` and merges in every user whose primary GID matches the group, via `getent passwd`, deduping the result. GroupSetupCmds creates the group (idempotently, via `groupadd -f`) and adds the instance user to it (idempotently, via `usermod -aG`). Signed-off-by: Harry Nguyen <sonhanguyen@users.noreply.github.com>
Two fixes to the Darwin group-mode implementation: - GroupUsers used `dscl . list ... GroupMembership`, which silently returns empty output on this platform's dscl instead of the group's members. Switch to `dscl . -read`, which returns the real value, and extract the output parser into parseGroupMembership so it can be tested directly. A missing group (eDSRecordNotFound) now returns no members instead of an error, matching the Linux behavior. - GroupSetupCmds interpolated groupName into a `sudo sh -c "dscl ..."` string, and relied on `2>/dev/null || true` for -create idempotency and `-append` for membership (which duplicates an entry on repeat invocations). Pass groupName as a direct dscl argument instead of a shell string, check group existence up front via the new groupExists helper to decide whether -create is needed, and use `-merge` (adds a value only if not already present) instead of `-append`. Signed-off-by: Harry Nguyen <sonhanguyen@users.noreply.github.com>
In group mode the instance name is an existing username, and delete looked it up and ran DeleteUserCmds without checking that it was ever a member of ALCLESS_GROUP. `alclessctl delete <name>` could therefore delete any account on the host that happens to already exist, and remove its /etc/sudoers.d entry, as long as the account satisfied store.ValidateName. Check group membership before building the delete commands, and refuse with an error when the account is not a member. Signed-off-by: Harry Nguyen <sonhanguyen@users.noreply.github.com>
pkg/store/store_test.go and pkg/userutil/userutil_test.go (added in the custom-account commit this branch builds on) were missing the Apache license header every other file in the repo carries, which fails this repo's `addlicense -check` lint job. Unrelated to the group-mode work in this PR; fixing it here since it otherwise blocks CI on this branch. Signed-off-by: Harry Nguyen <sonhanguyen@users.noreply.github.com>
sonhanguyen
marked this pull request as ready for review
September 23, 2026 13:32
Owner
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.
Summary
Adds a "group mode" for using pre-existing user accounts with alcless, selected by the
ALCLESS_GROUPenvironment variable, and implements it for both macOS and Linux.This supersedes #42, which I closed. That earlier version was macOS-only and had a few unsafe corners; this one adds the Linux implementation and fixes the issues below.
What group mode does
When
ALCLESS_GROUPis set:alcless_${USER}_${INSTANCE}This lets people manage the sandbox accounts themselves (home directory, shell, group membership) rather than having alcless own them.
Platform support
GroupUsersreads supplementary members fromgetent groupand also includes users whose primary group is the target group (getent passwd).GroupSetupCmdsusesgroupadd -fandusermod -aG, both idempotent.dscl . -read(the previousdscl . list /Groups/<g> GroupMembershipreturned empty output on my machine and never listed any members). Group setup passes the group name as a directdsclargument instead of interpolating it into a rootsh -c, and uses-mergeinstead of-appendso a repeatcreatedoes not duplicate the membership entry.Safety
In group mode an instance name is a real username, so
deletenow refuses to remove an account that is not a member ofALCLESS_GROUP, instead of running account deletion on any existing account name.Testing
go test ./...on macOS and on Linux (in a golang:1.26 container).