Linux User and Group Administration with sudo and PAM
I administered users and groups with useradd/usermod, scoped privilege escalation through targeted sudoers rules, and tightened password policy with PAM. Granting specific sudo commands instead of full root enforced least privilege with an auditable trail.
Objective & Context
Account management is foundational access control. This lab creates service and human accounts, manages supplementary groups, writes least-privilege sudoers entries via drop-ins, and sets password aging, aligning to NIST AC-2/AC-6.
Learning Objectives
- Create users with chosen shells, home directories, and supplementary groups.
- Grant least-privilege sudo rights with validated visudo edits.
- Enforce password aging, warnings, and account expiry with chage.
- Inspect the passwd, shadow, and group databases and know what each stores.
- Explain where PAM fits in the authentication flow.
Environment & Prerequisites
Prerequisites
- Linux with shadow-utils (useradd, chage) and sudo installed.
- Root or sudo access; user administration requires elevation.
- A test username that does not collide with real accounts (deploy is used here).
- Basic terminal comfort from the earlier fundamentals labs.
Step-by-Step Execution
1. Create a user with a primary and supplementary group [ROOT REQUIRED]
sudo groupadd -f web && sudo useradd -m -s /bin/bash -G web deploy && sudo passwd deployExpected output:
(passwd prompts twice for the new password)
$ id deploy
uid=1002(deploy) gid=1002(deploy) groups=1002(deploy),1003(web)
What this teaches: -m creates the home directory, -s sets the login shell, -G adds supplementary groups; id then confirms the resulting identity before you grant it any access. Two prerequisites are baked in: groupadd -f creates the web group first because useradd fails when -G names a group that does not exist, and passwd is interactive and must run as root.
2. Grant a scoped sudo rule [ROOT REQUIRED]
printf '%s\n' 'deploy ALL=(root) NOPASSWD: /usr/bin/systemctl restart nginx' | sudo tee /etc/sudoers.d/deploy-nginx && sudo chmod 0440 /etc/sudoers.d/deploy-nginx && sudo visudo -cExpected output:
/etc/sudoers: parsed OK
/etc/sudoers.d/deploy-nginx: parsed OK
What this teaches: Production sudo policy lives in drop-in files under /etc/sudoers.d, one file per concern, so package updates to /etc/sudoers never collide with your rules. sudo silently ignores drop-ins whose names contain a dot or end with a tilde, and requires mode 0440 (visudo -c flags anything else). The -c check parses the whole policy before you rely on it; a syntax error in sudoers breaks sudo for everyone, which is why you validate every write. Scoping to one command (systemctl restart nginx) follows least privilege.
3. Enforce password aging
chage -M 90 -W 7 deploy && chage -l deployExpected output:
Maximum number of days between password change : 90
What this teaches: chage -M 90 forces rotation every 90 days and -W 7 warns a week ahead; chage -l audits the policy. Aging is enforced at login through the shadow database and PAM.
How It Works
Linux identity rests on three databases: /etc/passwd maps names to UIDs, shells, and home directories; /etc/shadow holds the password hashes readable only by root; /etc/group maps group names to GIDs and member lists. useradd writes to all three consistently, which is why hand-editing them is discouraged.
sudo is a privilege broker, not just a root switch. /etc/sudoers declares who may run what as whom, and visudo exists because a syntax error there breaks sudo for everyone including root's recovery path. Scoped rules (one user, one command, optionally NOPASSWD) implement least privilege; the server variant of this lab extends the pattern with sudoers.d drop-ins and role groups.
PAM (Pluggable Authentication Modules) is the framework that actually verifies credentials at login, su, and sudo time. Each service has a stack in /etc/pam.d/ chaining modules for auth, account policy (like aging), session setup, and password changes. chage writes policy into the shadow database; PAM enforces it on the next login.
Validation & Testing
As the new user, confirm the single allowed sudo command works and any other is denied and logged. Pass criteria: least-privilege sudo enforced, group membership grants the intended file access, and password aging applies.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| sudo: user is not in the sudoers file | No rule grants the user elevation | Add a scoped rule via visudo from an existing sudoer or root |
| visudo reports a syntax error | The sudoers line has a typo or bad ordering | Fix within visudo; never bypass it by editing the file directly |
| New user cannot log in | Password unset, shell invalid, or home missing | Set a password, verify the shell exists in /etc/shells, check -m created the home |
| chage policy not enforced | The service does not consult PAM account modules | Confirm the service stack in /etc/pam.d/ includes pam_unix account checks |
| userdel leaves files behind | Default userdel keeps the home directory | Use userdel -r, and audit with find / -user oldname afterwards |
| Group membership not effective yet | Groups are assigned at login time | Log out and back in, or use newgrp / sg for the current session |
Advanced: Troubleshooting
- Broken sudoers: always edit with
visudo; a syntax error can lock out sudo entirely. - Group change not active: supplementary group changes need a fresh login.
- Locked account: check
passwd -Sand PAM faillock state.
Key Results
- Replaced blanket root with command-scoped sudo rules.
- Managed access via supplementary groups, not per-file grants.
- Enforced 90-day password aging with expiry warnings.
- Kept an auditable trail of privileged command use.
Key Takeaways
- passwd, shadow, and group are the three identity databases; useradd keeps them consistent.
- Always edit sudo through visudo; a syntax error locks out all elevation.
- Scope sudo rules to specific commands for least privilege.
- chage writes aging policy; PAM enforces it at login.
- Group changes take effect at next login.
Practice Challenges
Challenge 1: Least-privilege deploy
Goal: create a ci user that may only restart nginx and read its logs via sudo, then prove anything else is denied.
Hint: test with sudo -l -U ci and attempt a blocked command.
Challenge 2: Aging audit
Goal: list every human user whose password is older than 90 days or never expires, using chage -l in a loop.
Hint: filter /etc/passwd for UID >= 1000 first.