diff --git a/CHANGELOG.md b/CHANGELOG.md index 1bbe1f2..0437948 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Cargo.toml metadata aligned with uutils ecosystem conventions - Tool crate descriptions normalized to `"tool ~ (shadow-rs) verb phrase"` format - Edition 2024 consistently applied across root and workspace packages +- `make install` now defaults to 14 standalone per-tool binaries with + least-privilege setuid layout matching GNU shadow-utils (#138). Only + `passwd`/`chfn`/`chsh`/`newgrp` are setuid-root; the other 10 are `0755`. + The previous multicall install is available as `make install-multicall`. ### Fixed diff --git a/Makefile b/Makefile index ce854d1..21312e0 100644 --- a/Makefile +++ b/Makefile @@ -1,35 +1,55 @@ PREFIX ?= /usr/local BINDIR ?= $(PREFIX)/sbin -PROFILE ?= release -TOOLS = passwd pwck useradd userdel usermod chpasswd chage \ - groupadd groupdel groupmod grpck chfn chsh newgrp +# Tools that need setuid-root to allow non-root callers (change own password, +# GECOS, shell, effective group). +SETUID_TOOLS = passwd chfn chsh newgrp -.PHONY: all build test install uninstall clean +# Root-only tools (no setuid; fail at getuid() check for non-root callers). +ROOT_TOOLS = useradd userdel usermod chpasswd chage \ + groupadd groupdel groupmod pwck grpck + +ALL_TOOLS = $(SETUID_TOOLS) $(ROOT_TOOLS) + +.PHONY: all build build-multicall test install install-multicall uninstall clean all: build build: - cargo build --profile $(PROFILE) + cargo build --release --workspace --bins --exclude shadow-rs + +build-multicall: + cargo build --release --bin shadow-rs test: cargo test --workspace +# Default install: 14 standalone per-tool binaries with least-privilege setuid +# layout matching GNU shadow-utils. Only passwd/chfn/chsh/newgrp are setuid. install: build - install -Dm755 target/$(PROFILE)/shadow-rs $(DESTDIR)$(BINDIR)/shadow-rs - @for tool in $(TOOLS); do \ + @for tool in $(SETUID_TOOLS); do \ + install -Dm4755 target/release/$$tool $(DESTDIR)$(BINDIR)/$$tool || exit 1; \ + done + @for tool in $(ROOT_TOOLS); do \ + install -Dm0755 target/release/$$tool $(DESTDIR)$(BINDIR)/$$tool || exit 1; \ + done + @echo "Installed $(words $(ALL_TOOLS)) standalone binaries to $(DESTDIR)$(BINDIR)/" + @echo " setuid (4755): $(SETUID_TOOLS)" + @echo " root-only (0755): $(ROOT_TOOLS)" + +# Opt-in install: single multicall binary with symlinks. Smaller footprint but +# larger setuid attack surface — the binary is installed setuid-root, so all 14 +# applets run with euid=root when invoked via symlink. Intended for +# container/embedded use where disk savings matter and attack surface does not. +install-multicall: build-multicall + install -Dm4755 target/release/shadow-rs $(DESTDIR)$(BINDIR)/shadow-rs + @for tool in $(ALL_TOOLS); do \ ln -sf shadow-rs $(DESTDIR)$(BINDIR)/$$tool; \ done - @# Setuid-root for tools that need it. chmod on symlinks affects the - @# underlying multicall binary — this is intentional for drop-in - @# replacement since all traditional shadow-utils tools are setuid-root. - @for tool in passwd chfn chsh newgrp; do \ - chmod 4755 $(DESTDIR)$(BINDIR)/$$tool 2>/dev/null || true; \ - done - @echo "Installed shadow-rs + $(words $(TOOLS)) symlinks to $(DESTDIR)$(BINDIR)/" + @echo "Installed multicall shadow-rs + $(words $(ALL_TOOLS)) symlinks to $(DESTDIR)$(BINDIR)/" uninstall: - @for tool in $(TOOLS); do \ + @for tool in $(ALL_TOOLS); do \ rm -f $(DESTDIR)$(BINDIR)/$$tool; \ done rm -f $(DESTDIR)$(BINDIR)/shadow-rs diff --git a/README.md b/README.md index 379d661..3bef675 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,25 @@ docker compose build debian docker compose run --rm debian cargo build --release ``` +### Install + +Default install: 14 standalone per-tool binaries with least-privilege setuid +layout matching GNU shadow-utils. Only `passwd`, `chfn`, `chsh`, `newgrp` are +installed setuid-root; the other 10 are plain `0755`. + +```shell +sudo make install PREFIX=/usr/local +``` + +Alternative: single multicall binary with symlinks. Smaller footprint (~14× +disk savings) but larger setuid attack surface — the binary is installed +setuid-root, so all 14 applets run with `euid=root` when invoked via symlink. +Intended for container/embedded use cases. + +```shell +sudo make install-multicall PREFIX=/usr/local +``` + ### Test All builds and tests run inside Docker containers to isolate from the host