Linux Least Privilege: Mastering sudo, Groups, and ACLs for Secure Access Control
This comprehensive guide demonstrates how to implement least-privilege access in Linux using sudo, user groups, and ACLs, with practical examples covering identity verification, permission layers, service sandboxing, testing strategies, and account offboarding.
Clarify Identity, Session, and Resource Ownership
Start by verifying the actual identity of the current session using id and getent passwd / getent group to query the system's configured identity sources (local files or directory services). Numeric IDs are what the filesystem stores; group names are just labels. Use who and ps -eo user,group,pid,ppid,args to list active sessions and processes — locking an account does not terminate existing sessions or processes. Check the full path permissions with namei -l /path/to/file because a missing execute bit on any parent directory blocks access. Examine numeric ownership and mode with stat -c '%n uid=%u gid=%g mode=%a type=%F' /path and note that ACLs can make the traditional group permission bit reflect the ACL mask rather than the actual group rights. Finally, inspect mount options with findmnt -T /path -o TARGET,SOURCE,FSTYPE,OPTIONS since noexec or read-only mounts can override permission bits.
Define a Task Matrix Before Granting Access
Create a task matrix that separates duties (e.g., read logs, write releases, reload service) and explicitly lists forbidden actions (edit unit files, arbitrary root shell, read secrets without need). This prevents backward reasoning from "which group to add" and enables positive and negative testing later.
Create Accounts and Groups: Separate People from Services
Create role-based groups ( app-readers, app-writers, app-operators, app-editors) with groupadd. Create human accounts ( alice, bob) with useradd --create-home --shell /bin/bash and a system service account ( appsvc) with
useradd --system --home-dir /nonexistent --shell /usr/sbin/nologin. Add users to supplementary groups using usermod -aG to preserve existing memberships. Verify group membership in a new login session ( sudo -iu alice id) because existing sessions retain old credentials. Set account expiration with chage -E 2026-12-31 alice and document the purpose in the comment field (
usermod --comment 'Application runtime account; owner=platform' appsvc).
Traditional Permission Bits First, Then ACLs
Create a shared directory with setgid so new files inherit the group:
sudo install -d -o root -g app-writers -m 2770 /srv/app/releases. For existing directories, change group ownership and setgid non-recursively to avoid affecting unrelated files:
sudo chgrp app-writers /srv/app/releases && sudo chmod g+s /srv/app/releases. Set a restrictive umask ( umask 0027) for new objects. Use conditional execute ( chmod -R g+rX) to add execute only to directories and already-executable files. Apply the sticky bit ( chmod 1777) on world-writable drop-box directories to prevent users from deleting each other's files. Audit world-writable files with sudo find /srv/app -xdev -perm -0002 -printf '%m %u %g %p\n' before fixing.
ACLs for Multi-Role Access: Understand Mask and Inheritance
View ACLs with getfacl -p /path and back them up ( sudo getfacl -R -p /srv/app > app-acl-before.txt). Grant directory traversal and file read to a group: sudo setfacl -m g:app-readers:rx /srv/app/releases. Give a specific user read access to a single file: sudo setfacl -m u:bob:r /srv/app/logs/app.log. Set default ACLs for inheritance:
sudo setfacl -m d:u::rwx,d:g::rwx,d:g:app-readers:rx,d:g:app-writers:rwx,d:m::rwx,d:o::--- /srv/app/releases. The mask limits effective permissions for named users, named groups, and the owning group; reduce it with sudo setfacl -m m::rx /srv/app/releases and verify with getfacl. When expanding the mask, first lower undesired entries. Apply ACLs recursively to existing trees by separating directories and files:
sudo find /srv/app/logs -xdev -type d -exec setfacl -m g:app-readers:rx {} +and
sudo find /srv/app/logs -xdev -type f -exec setfacl -m g:app-readers:r {} +. Remove a specific entry with sudo setfacl -x u:bob /srv/app/logs/app.log.
sudo: Allow Only Explicit Actions and Audit Dependencies
Check a user's effective sudo rights with sudo -l -U alice (covers all included files). Review administrative groups ( getent group sudo, getent group wheel) and sudoers includes ( sudo grep -nE '^%|includedir|include' /etc/sudoers). Edit sudoers with visudo -f /etc/sudoers.d/app-operators. Write tight rules:
%app-operators ALL=(root) /usr/bin/systemctl reload app.service. Prefer a wrapper script that validates arguments and cleans the environment:
#!/usr/bin/env bash; set -euo pipefail; [[ $# -eq 1 && "$1" == reload ]] || { echo 'usage: app-control reload' >&2; exit 64; }; exec /usr/bin/env -i PATH=/usr/sbin:/usr/bin:/sbin:/bin /usr/bin/systemctl reload app.service. Install it securely:
sudo install -o root -g root -m 0755 app-control /usr/local/sbin/app-controland verify the path chain with namei -l /usr/local/sbin/app-control. Define a command alias in sudoers:
Cmnd_Alias APP_RELOAD = /usr/local/sbin/app-control reload; %app-operators ALL=(root) APP_RELOAD. Validate the service unit's full configuration ( systemctl cat app.service,
systemctl show app.service -p User,Group,ExecStart,ExecReload,EnvironmentFiles) and ensure the executable and its dependencies are not writable by the authorized user ( namei -l /etc/systemd/system/app.service, namei -l /usr/local/sbin/app-control). Use sudoedit only for vetted low-risk parameter files. Check the sudo environment with
sudo /usr/bin/env | awk -F= '$1~/^(PATH|HOME|USER|LOGNAME|SHELL)$/ {print}'. Clear authentication cache for testing with sudo -k.
Service Self-Hardening: Sandbox the Runtime
In the systemd unit, set a dedicated user/group, supplementary groups, and umask:
[Service]; User=appsvc; Group=appsvc; SupplementaryGroups=app-readers; UMask=0027. Apply sandboxing:
NoNewPrivileges=yes; ProtectSystem=strict; ProtectHome=yes; ReadWritePaths=/srv/app/data /srv/app/logs. Grant only required capabilities:
CapabilityBoundingSet=CAP_NET_BIND_SERVICE; AmbientCapabilities=CAP_NET_BIND_SERVICE. Scan for file capabilities ( sudo getcap -r /srv/app) and setuid/setgid binaries (
sudo find /srv/app -xdev -type f \( -perm -4000 -o -perm -2000 \) -printf '%m %u %g %p
'). Check SELinux status ( getenforce) and labels ( ls -Zd /srv/app /srv/app/data /srv/app/logs); review recent denials with sudo ausearch -m AVC,USER_AVC -ts recent. For AppArmor, run sudo aa-status and check kernel logs (
sudo journalctl -k --since '-30 min' --no-pager | grep -i apparmor).
Validate with Allow and Deny Tests
Test positive access: sudo -u bob head -n 1 /srv/app/logs/acceptance.log. Test negative access: attempt to create a file as the reader and expect failure. Verify writer creates files with correct group and mode:
sudo -u alice touch /srv/app/releases/acceptance-file; stat -c '%u %g %a %n' /srv/app/releases/acceptance-file; getfacl -p /srv/app/releases/acceptance-file. Preview ACL changes with
sudo setfacl --test -m g:app-readers:rx,m::rwx /srv/app/releases. Restore from backup only after reviewing diffs: sudo setfacl --restore=app-acl-before.txt. Test sudo rule matching without execution: sudo -l -U alice /usr/local/sbin/app-control reload and verify denial of unwanted commands: sudo -l -U alice /bin/bash. Monitor sudo logs: sudo journalctl _COMM=sudo --since '-1 hour' --no-pager.
Handle Copies, Deployments, and Containers
Preserve ACLs and xattrs in backups: sudo rsync -aHAX --numeric-ids /src/ /dst/ and sudo tar --acls --xattrs -cpf archive.tar -C /src dir. Moving files ( mv) may retain source permissions instead of inheriting target default ACLs — test the actual deployment pipeline. Configure logrotate to create new files with the right ownership and group:
/srv/app/logs/app.log { daily; rotate 7; create 0640 appsvc app-readers }and preview with sudo logrotate --debug /etc/logrotate.d/app. In containers, inspect user namespace mapping:
docker inspect container --format '{{json .Config.User}} {{json .HostConfig.UsernsMode}}'. Check the running process's actual credentials: sudo awk '/^(Uid|Gid|Groups|CapEff):/' /proc/PID/status. List open files held by the service user: sudo lsof -u appsvc — permission changes do not revoke already-open descriptors.
Offboarding and Auditing: Remove All Access Vectors
Remove group membership: sudo gpasswd -d alice app-writers (plan session termination). Lock the password: sudo usermod -L alice; sudo passwd -S alice — but also revoke SSH keys, certificates, directory service entries, sudoers rules, and ACLs. Find files owned by the user's UID: sudo find /srv/app -xdev -uid 1001 -printf '%u %g %m %p\n'. Review cron jobs and systemd timers:
sudo crontab -u alice -l; systemctl list-timers --all --no-pager. Search for leftover ACL entries: sudo getfacl -R -p /srv/app | grep -n 'user:alice:'. Record the final authorization state in a structured document covering allowed/denied tasks, dependency review, positive/negative test results, and expiration review.
Three Illustrative Cases
Case 1 — Log Reading: Adding a troubleshooter to a system log group grants access to all service logs. Instead, isolate application logs in a dedicated directory, use a role group with default ACLs, verify traversal rights on each parent directory, and sanitize sensitive fields at log generation time. Case 2 — Service Restart: A fixed systemctl restart sudo rule seems narrow, but if the service runs as root and its start script lives in a publisher-writable directory, the publisher can replace the script and execute arbitrary code as root. Fix by moving the privileged start chain under admin control, running the service as a low-privilege user, and separating writable artifacts from privileged scripts. Case 3 — ACL Mask Side Effect: Adding a read entry for a user causes the tool to recalculate the mask, inadvertently restoring write access for a group that had a write entry but was previously masked. Always review the full ACL and effective permissions after any change; lower undesired entries before raising the mask, then test with representative identities.
Reference Materials
acl manual
setfacl manual
sudoers manual
useradd manual
systemd.exec documentation
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Ops Community
A leading IT operations community where professionals share and grow together.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
