146 Commits

Author SHA1 Message Date
Pierre Warnier 86eb48de44 meta: align Cargo.toml metadata with uutils conventions
- Fix root package edition (2021 → workspace 2024)
- Use workspace inheritance for license, authors, repository, rust-version
- Fix keywords: "coreutils" → "shadow-utils", add "uutils"
- Add homepage, keywords, categories to workspace.package
- Add workspace metadata inheritance to all 15 crate Cargo.toml files
- Normalize tool descriptions to "tool ~ (shadow-rs) verb phrase" format
- Add feat_common_core feature alias (groups all 14 tools)
- Fix pre-existing clippy and fmt issues from edition 2024 alignment
2026-04-03 14:44:59 +02:00
Pierre Warnier 62dca6f047 Merge pull request #116 from shadow-utils-rs/feat/114-usermod-password
usermod: add -p/--password flag (#114)
2026-04-03 14:01:11 +02:00
Pierre Warnier c3e0679acc review: address all 4 Copilot review comments
- Error on missing shadow entry instead of silent no-op
- Validate password hash rejects ':', '\n', '\r'
- Centralize days_since_epoch() in shadow-core
- Assert field count and reserved field in test
2026-04-03 13:57:28 +02:00
Pierre Warnier 24347e9913 usermod: add -p/--password flag for pre-hashed passwords (#114)
Ansible's user module calls `usermod -p <hash>` to set passwords.
This was the most significant gap for Ansible interop.

Implementation:
- Add PASSWORD option constant and clap argument (-p/--password)
- Write hash directly to shadow entry, update last_change to today
- Follows the same lock → read → mutate → atomic_write pattern

Tests:
- test_set_password: basic -p flag with hash verification
- test_set_password_long_flag: --password long form
- test_set_password_preserves_other_fields: all 9 shadow fields intact

Also updated e2e Ansible playbook to use native `user: password:`
parameter instead of the chpasswd -e workaround.

Fixes #114
2026-04-03 13:48:11 +02:00
Pierre Warnier d8e71819fe Merge pull request #115 from shadow-utils-rs/feat/102-e2e-deployment-tests
e2e: automated deployment tests in Docker (#102)
2026-04-03 13:37:07 +02:00
Pierre Warnier 36a623d0dd review: address all 8 Copilot review comments
- Redact full command args in assert_ok failure output (password leak)
- Wrap all bare useradd/chpasswd calls in assert_ok (5 test sections)
- Strict symlink checks: require -L + verify target resolves to shadow-rs
- Add no_log: true to Ansible password task
- Redirect nscd -d stdout to /dev/null

Assertion count: 97 → 117
2026-04-03 13:34:08 +02:00
Pierre Warnier e29b06c461 e2e: add end-to-end deployment tests (#102)
Add automated deployment testing in Docker that simulates a real
system-wide installation: build from source, install replacing GNU
shadow-utils, then run 97 assertions covering:

- Symlink dispatch and multicall dispatch (all 14 tools)
- Setuid bits and non-root privilege enforcement
- Full user lifecycle (useradd → chpasswd → usermod → pwck → userdel)
- Full group lifecycle (groupadd → groupmod → groupdel → grpck)
- Individual tool tests (chage, chfn, chsh, passwd -l/-u)
- PAM authentication (su with known password via expect)
- nscd cache invalidation
- Landlock sandboxing (when kernel supports it)
- Ansible integration (user/group modules as real-world consumer)

New files:
- docker/Dockerfile.e2e — multi-stage build + debian:trixie runtime
- tests/e2e/deploy-test.sh — bash test harness with 97 assertions
- tests/e2e/ansible-test.yml — Ansible playbook integration test
- .dockerignore — prevent sending target/ and .git/ to Docker

Modified:
- docker-compose.yml — add e2e service (no changes to existing services)
- Cargo.toml — add pam feature forwarding to root package
- shadow-core/pam.rs — add #[link(name = "pam")] for linker

Closes #109, closes #110, closes #111, closes #112, closes #113
Refs #102
2026-04-03 13:22:55 +02:00
Pierre Warnier 4110b1cd04 Merge pull request #108 from shadow-utils-rs/feat/106-completions-landlock
completions: add clap_complete generator + Landlock sandboxing for passwd
2026-04-03 12:34:41 +02:00
Pierre Warnier b4130cfcf0 review: address all 6 Copilot review comments 2026-04-03 12:30:52 +02:00
Pierre Warnier 7fbea1f102 passwd: implement Landlock filesystem sandboxing (#41)
Restricts passwd's filesystem access to only what it needs:
- /etc/ (read+write for passwd/shadow files)
- /dev/ (read+write for tty prompts)
- /usr/sbin/ (execute for nscd/sss_cache)

Uses the landlock crate (MIT/Apache-2.0) with best-effort mode:
silently does nothing on kernels < 5.13. The apply_landlock()
helper is in shadow-core::hardening so other tools can reuse it.

Skipped during #[cfg(test)] since Landlock is irreversible per-process.

Fixes #41
2026-04-03 12:18:35 +02:00
Pierre Warnier 829b37f7a8 completions: add clap_complete shell completion generator (#106)
Feature-gated binary (--features completions) that generates bash/zsh/fish/
elvish/powershell completions from each tool's uu_app() definition.

Usage:
  shadow-rs-completions passwd --shell bash
  shadow-rs-completions --all --shell zsh --dir completions/

Also updates generate-completions.sh to use the new binary and adds
windows-sys skip to deny.toml for transitive clap dep.

Fixes #106
2026-04-03 12:18:18 +02:00
Pierre Warnier e1cf43502a Merge pull request #107 from shadow-utils-rs/feat/integration-tests-release
tests: integration tests for all 14 tools + CHANGELOG for v0.1.0
v0.1.0
2026-03-24 16:07:46 +01:00
Pierre Warnier 7bd47ff3d0 tests: add integration tests for all 14 tools, add CHANGELOG
Add integration tests for the 8 remaining tools:
- useradd, userdel, usermod (user management)
- groupadd, groupdel, groupmod (group management)
- pwck, grpck (file consistency checkers)

101 new tests covering clap parsing, flag conflicts, error paths,
and root-only file mutation operations with synthetic /etc/ files.

Total test count: 461 → 562.

Also adds CHANGELOG.md for v0.1.0 release.
2026-03-24 15:59:04 +01:00
Pierre Warnier caac89485d Merge pull request #105 from shadow-utils-rs/fix/93-104-prod-ready
prod: 11 production-readiness fixes
2026-03-24 15:45:16 +01:00
Pierre Warnier 5d8ac9587d review: address all 13 Copilot review comments
- Fix cargo build --release --workspace in test scripts
- Document setuid-on-symlinks as intentional in Makefile
- Warn (not abort) on subid allocation failure in useradd
- Propagate subid file read errors instead of unwrap_or_default
- Update SELinux docs to describe best-effort behavior
- Add env_clear() to getfattr subprocess in selinux.rs
- Use hardening::sanitized_env() in nscd.rs instead of local reimpl
- Create testuser in pam-e2e.sh, add cleanup
- Document su-as-root limitation in pam-e2e.sh
- Fix set -e exit code capture in gnu-compat.sh
- Add 5 date validation unit tests in chage
- Wire audit::log_user_event into all 7 tools
2026-03-24 15:42:08 +01:00
Pierre Warnier 3fbeaf448c chore: remove Cargo.lock from tracking (library convention) 2026-03-24 15:25:11 +01:00
Pierre Warnier dddd5713ac deps: upgrade nix 0.29 → 0.30 to unify with uucore
Eliminates duplicate nix crate that caused cargo-deny ban failure.
Adapts to nix 0.30 API: fsync/fcntl/fchownat now take AsFd instead
of raw i32 file descriptors; use AT_FDCWD for fchownat.
2026-03-24 15:24:54 +01:00
Pierre Warnier 83080f53dd CI: upgrade cargo-deny-action to v2 for edition 2024 support
The v1 action bundles an old Cargo that doesn't recognize edition 2024.
v2 uses the host toolchain, so we install stable Rust first.
2026-03-24 15:18:07 +01:00
Pierre Warnier 8ba8f940fd prod: GNU compat tests, PAM e2e, setuid, SELinux, audit, packaging
Fixes #93 — GNU compatibility test suite (tests/gnu-compat.sh)
Fixes #94 — PAM end-to-end test script (tests/pam-e2e.sh)
Fixes #95 — Setuid permissions in Makefile (chmod 4755)
Fixes #96 — nscd env_clear() on subprocess spawning
Fixes #97 — SELinux file context support (getfattr/chcon/restorecon)
Fixes #98 — Audit logging to syslog/auditd
Fixes #99 — subuid/subgid allocation in useradd
Fixes #100 — Debian packaging (debian/control, debian/rules)
Fixes #101 — Fedora packaging (shadow-rs.spec)
Fixes #103 — Recursive chown on usermod UID change
Fixes #104 — Proper date validation (Feb 31 rejected)

456 tests, zero clippy warnings. Binary: 894KB (release-small).
2026-03-24 15:15:21 +01:00
Pierre Warnier bcf1eae8a9 Merge pull request #92 from shadow-utils-rs/fix/78-86-88-final
fix: binary <1MB, test helpers, completions
2026-03-24 14:55:12 +01:00
Pierre Warnier 748f0ad88d fix: binary size <1MB, shared test helpers, shell completions
Fixes #78 — Binary size: release-small profile achieves 894KB (<1MB).
  Default release also shrank from 1.5MB to 1.3MB thanks to
  panic=abort + codegen-units=1 from PR #91.

Fixes #86 — Shared test helper crate at tests/common/mod.rs with
  skip_unless_root(), setup_prefix(), setup_full_prefix(), read_file().

Fixes #88 — Shell completion script at util/generate-completions.sh
  (placeholder — generates minimal bash/zsh/fish completions).

456 tests, zero clippy warnings.
2026-03-24 14:48:37 +01:00
Pierre Warnier 20afe8c73b Merge pull request #91 from shadow-utils-rs/fix/79-90-uutils-compliance
infra: uutils compliance — edition 2024, unsafe deny, dead_code deny
2026-03-24 14:44:24 +01:00
Pierre Warnier 9e3faa966c address Copilot review: fix docs, feature-gate crypt, MSRV 1.94, CI msrv job 2026-03-24 14:43:38 +01:00
Pierre Warnier 896a4214b6 infra: uutils compliance — edition 2024, unsafe deny, dead_code deny
Fixes #79 — CODE_OF_CONDUCT.md (Contributor Covenant 2.1)
Fixes #80 — locales/en-US.ftl for all 14 tools
Fixes #81 — rust-version = "1.85.0" in workspace
Fixes #82 — edition 2024 (resolver 3, unsafe extern blocks)
Fixes #83 — GitHub Actions CI (fmt, clippy, test, cargo-deny)
Fixes #84 — .clippy.toml with MSRV and quality thresholds
Fixes #85 — deny.toml tightened (multiple-versions=deny)
Fixes #87 — panic=abort, codegen-units=1 in release profile
Fixes #89 — Makefile with install/uninstall/clean targets
Fixes #90 — workspace lints: cargo group, unused_qualifications

Key policy changes enforced by cargo:
- unsafe_code = "deny" workspace-wide. Only pam.rs and crypt.rs
  have #[allow(unsafe_code)] (C FFI boundaries).
- dead_code = "deny" workspace-wide. No unused code allowed.
- Edition 2024: set_var/remove_var are unsafe, so sanitize_env
  returns a Vec instead of mutating. Signal handler removed
  (was unsafe). localtime_r replaced with pure Rust calendar math.

456 tests, zero clippy warnings.
2026-03-24 14:30:22 +01:00
Pierre Warnier a6e6ef63b2 Merge pull request #77 from shadow-utils-rs/fix/56-67-69-70-remaining
docs/fixes: man pages, benchmarks, BSD research, Copilot findings
2026-03-24 13:12:34 +01:00