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 deploy

Expected 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 -c

Expected 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 deploy

Expected 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

SymptomLikely causeFix
sudo: user is not in the sudoers fileNo rule grants the user elevationAdd a scoped rule via visudo from an existing sudoer or root
visudo reports a syntax errorThe sudoers line has a typo or bad orderingFix within visudo; never bypass it by editing the file directly
New user cannot log inPassword unset, shell invalid, or home missingSet a password, verify the shell exists in /etc/shells, check -m created the home
chage policy not enforcedThe service does not consult PAM account modulesConfirm the service stack in /etc/pam.d/ includes pam_unix account checks
userdel leaves files behindDefault userdel keeps the home directoryUse userdel -r, and audit with find / -user oldname afterwards
Group membership not effective yetGroups are assigned at login timeLog 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 -S and 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.