Linux User Management
User and group administration, sudo configuration.
User Management
# CREATE USER
useradd username # Minimal (no home, no shell)
useradd -m -s /bin/bash username # With home and shell
useradd -m -s /bin/bash -c "Full Name" username # With comment
# FULL CREATE OPTIONS
useradd \
-m \ # Create home directory
-d /home/username \ # Custom home path
-s /bin/bash \ # Login shell
-c "Full Name" \ # Comment (GECOS)
-g primarygroup \ # Primary group (must exist)
-G wheel,docker,audio \ # Secondary groups
-u 1500 \ # Specific UID
-e 2025-12-31 \ # Account expiration
username
# SYSTEM USER (for services)
useradd -r -s /sbin/nologin -d /var/lib/myapp myapp
# -r = system account (UID below 1000)
# -s /sbin/nologin = can't login interactively
# DELETE USER
userdel username # Keep home directory
userdel -r username # Remove home and mail spool
# MODIFY USER
usermod -aG wheel username # Add to group (APPEND!)
usermod -G wheel,docker username # REPLACE groups (dangerous!)
usermod -s /bin/zsh username # Change shell
usermod -d /home/newdir username # Change home (doesn't move files!)
usermod -d /home/newdir -m username # Change home AND move files
usermod -l newname oldname # Rename user
usermod -L username # Lock account
usermod -U username # Unlock account
# CRITICAL: -aG vs -G
# -G wheel = SET groups to ONLY wheel (removes all others!)
# -aG wheel = APPEND wheel to existing groups (safe)
# ALWAYS use -aG unless you intend to replace all groups
# VIEW USER INFO
id username # UID, GID, groups
id -nG username # Group names only
getent passwd username # Full passwd entry
finger username # Detailed info (if installed)
# PASSWORD MANAGEMENT
passwd username # Set/change password
passwd -l username # Lock account
passwd -u username # Unlock account
passwd -d username # Delete password (dangerous!)
passwd -e username # Force password change on next login
chage -l username # View aging info
chage -M 90 username # Max days between changes
# PASSWORD FILES
cat /etc/passwd | grep username # Basic info
cat /etc/shadow | grep username # Password hash (root only)
# PASSWD FORMAT:
# username:x:UID:GID:comment:home:shell
# evanusmodestus:x:1000:1000:Evan Rosado:/home/evanusmodestus:/bin/zsh
# SHADOW FORMAT:
# username:$hash$:lastchange:min:max:warn:inactive:expire:reserved
# INFRASTRUCTURE: Audit users
# Find users with UID >= 1000 (real users)
awk -F: '$3 >= 1000 {print $1, $3, $7}' /etc/passwd
# Find users with no password
awk -F: '($2 == "" || $2 == "!") {print $1}' /etc/shadow 2>/dev/null
# Find users with shell
awk -F: '$7 !~ /nologin|false/ {print $1, $7}' /etc/passwd
Group Management
# CREATE GROUP
groupadd mygroup # Create group
groupadd -g 2000 mygroup # With specific GID
groupadd -r mygroup # System group (GID < 1000)
# DELETE GROUP
groupdel mygroup # Delete (must have no members)
# MODIFY GROUP
groupmod -n newname oldname # Rename group
groupmod -g 2001 mygroup # Change GID
# GROUP MEMBERSHIP
groups username # Show user's groups
usermod -aG mygroup username # Add user to group
gpasswd -d username mygroup # Remove user from group
# ALTERNATIVE: gpasswd
gpasswd -a username mygroup # Add user
gpasswd -d username mygroup # Remove user
gpasswd -A admin mygroup # Set group admin
gpasswd mygroup # Set group password (rarely used)
# VIEW GROUP INFO
getent group mygroup # Group entry
cat /etc/group | grep mygroup # Raw group file
# GROUP FILE FORMAT:
# groupname:x:GID:members
# wheel:x:10:evanusmodestus,admin
# SPECIAL GROUPS
# wheel - sudo access (RHEL/Arch)
# sudo - sudo access (Debian/Ubuntu)
# docker - Docker socket access
# libvirt - VM management
# audio - Audio device access
# video - Video device access
# COMMON PATTERNS
# Add to wheel for sudo access
usermod -aG wheel username
# Add to docker for non-root docker
usermod -aG docker username
# Add to libvirt for VM management
usermod -aG libvirt username
# INFRASTRUCTURE: List group members
getent group wheel | cut -d: -f4
# All groups with members
awk -F: '$4 != "" {print $1": "$4}' /etc/group
# Find which groups a file belongs to
stat -c '%U:%G' /path/to/file
Sudo & Sudoers
# SUDO BASICS
sudo command # Run as root
sudo -u user command # Run as specific user
sudo -i # Interactive root shell
sudo -s # Shell as root (keeps env)
sudo -l # List allowed commands
sudo -v # Extend sudo timeout
sudo -k # Forget cached credentials
# EDIT SUDOERS (ALWAYS use visudo!)
sudo visudo # Safe editing with syntax check
sudo visudo -f /etc/sudoers.d/myfile # Edit drop-in file
# SUDOERS FILE LOCATIONS
# /etc/sudoers - Main file (package-managed)
# /etc/sudoers.d/ - Drop-in files (recommended)
# SUDOERS SYNTAX
# user host=(runas) commands
# %group host=(runas) commands
# COMMON PATTERNS
# Full sudo access
evanusmodestus ALL=(ALL:ALL) ALL
# Group with full access
%wheel ALL=(ALL:ALL) ALL
%sudo ALL=(ALL:ALL) ALL
# No password required
evanusmodestus ALL=(ALL) NOPASSWD: ALL
# Specific commands only
deploy ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart nginx
backup ALL=(ALL) NOPASSWD: /usr/bin/rsync, /usr/bin/tar
# Command aliases
Cmnd_Alias SERVICES = /usr/bin/systemctl start *, /usr/bin/systemctl stop *, /usr/bin/systemctl restart *
deploy ALL=(ALL) NOPASSWD: SERVICES
# RUN AS SPECIFIC USER
appuser ALL=(postgres) NOPASSWD: /usr/bin/psql
# RESTRICT TO HOST
user hostname=(ALL) ALL
# DROP-IN FILES (recommended)
cat > /etc/sudoers.d/10-deploy <<'EOF'
# Deploy user - service management only
deploy ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart nginx, \
/usr/bin/systemctl restart postgresql, \
/usr/bin/rsync -av * /var/www/
EOF
chmod 440 /etc/sudoers.d/10-deploy
# VALIDATE SYNTAX
visudo -c # Check main file
visudo -cf /etc/sudoers.d/10-deploy # Check drop-in
# INFRASTRUCTURE: Ansible sudo
cat > /etc/sudoers.d/90-ansible <<'EOF'
# Ansible automation user
ansible ALL=(ALL) NOPASSWD: ALL
Defaults:ansible !requiretty
EOF
chmod 440 /etc/sudoers.d/90-ansible
# DEBUG SUDO
sudo -l # What can I run?
sudo -ll # Detailed listing
SSH Access Management
# SSH KEY SETUP
ssh-keygen -t ed25519 -C "user@host" # Generate key
ssh-copy-id user@server # Copy public key to server
# AUTHORIZED_KEYS
# ~/.ssh/authorized_keys format:
# type key comment
# ssh-ed25519 AAAAC3... user@host
# Multiple keys
cat >> ~/.ssh/authorized_keys << 'EOF'
ssh-ed25519 AAAAC3NzaC1... workstation
ssh-ed25519 AAAAC3NzaC2... laptop
EOF
# RESTRICT KEY OPTIONS
# Prefix key with options for restrictions:
# command="/path/to/script" - Only run this command
# from="10.50.1.*" - Only from these IPs
# no-pty - No terminal
# no-port-forwarding - No tunnels
# no-X11-forwarding - No X forwarding
# EXAMPLES
# Backup user - rsync only from specific IP
command="/usr/local/bin/rrsync -ro /backup",from="10.50.1.70",no-pty ssh-ed25519 AAAA... backup@nas
# Monitoring - specific command only
command="/usr/local/bin/check-services.sh",no-pty ssh-ed25519 AAAA... monitoring@nagios
# SSHD CONFIG OPTIONS (/etc/ssh/sshd_config)
PermitRootLogin no # Disable root login
PasswordAuthentication no # Keys only
PubkeyAuthentication yes # Enable pubkey
AuthorizedKeysFile .ssh/authorized_keys # Key file location
AllowUsers evanusmodestus admin # Whitelist users
AllowGroups wheel ssh-users # Whitelist groups
DenyUsers guest # Blacklist users
# MATCH BLOCKS (specific rules per user/group)
Match User deploy
PasswordAuthentication no
AllowTcpForwarding no
X11Forwarding no
ForceCommand /usr/local/bin/deploy-only.sh
Match Group sftp-only
ChrootDirectory /sftp/%u
ForceCommand internal-sftp
AllowTcpForwarding no
# VAULT SSH CA
# TrustedUserCAKeys points to Vault SSH CA public key
TrustedUserCAKeys /etc/ssh/vault-ca.pub
# Now Vault-signed certs are trusted automatically
# PERMISSIONS (CRITICAL)
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
chmod 600 ~/.ssh/id_*
chmod 644 ~/.ssh/*.pub
chmod 600 ~/.ssh/config
chown -R $USER:$USER ~/.ssh
# INFRASTRUCTURE: Deploy SSH key across hosts
pubkey="ssh-ed25519 AAAA... admin@workstation"
for host in vault-01 bind-01 ise-01; do
ssh "$host" "mkdir -p ~/.ssh && chmod 700 ~/.ssh && echo '$pubkey' >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"
done
# Verify SSH access
for host in vault-01 bind-01 ise-01; do
ssh -o BatchMode=yes "$host" "hostname" 2>/dev/null && echo "$host: OK" || echo "$host: FAILED"
done
PAM Configuration
# PAM - Pluggable Authentication Modules
# Controls: auth, account, password, session
# PAM FILE LOCATIONS
# /etc/pam.d/ - Service-specific files
# /etc/pam.d/sshd - SSH login
# /etc/pam.d/su - su command
# /etc/pam.d/sudo - sudo
# /etc/pam.d/system-auth - Shared (RHEL)
# /etc/pam.d/common-auth - Shared (Debian)
# PAM LINE FORMAT
# type control module [args]
#
# type: auth, account, password, session
# control: required, requisite, sufficient, optional
#
# required - Must pass, but continue checking
# requisite - Must pass, fail immediately if not
# sufficient - Pass here = done, fail = continue
# optional - Ignore result
# EXAMPLE: /etc/pam.d/sshd (RHEL)
cat <<'EOF'
auth required pam_sepermit.so
auth substack password-auth
auth include postlogin
account required pam_nologin.so
account include password-auth
password include password-auth
session required pam_selinux.so close
session required pam_loginuid.so
session required pam_selinux.so open env_params
session required pam_namespace.so
session optional pam_keyinit.so force revoke
session include password-auth
session include postlogin
EOF
# COMMON PAM MODULES
# pam_unix.so - Standard Unix authentication
# pam_wheel.so - Restrict to wheel group
# pam_faillock.so - Lock after failed attempts (RHEL 8+)
# pam_pwquality.so - Password complexity
# pam_limits.so - Resource limits
# pam_mkhomedir.so - Create home on first login
# pam_sss.so - SSSD (AD/LDAP)
# pam_google_authenticator.so - TOTP 2FA
# RESTRICT SU TO WHEEL GROUP
# /etc/pam.d/su
auth required pam_wheel.so use_uid
# ACCOUNT LOCKOUT (faillock)
# /etc/pam.d/system-auth
auth required pam_faillock.so preauth silent deny=5 unlock_time=300
auth required pam_unix.so
auth required pam_faillock.so authfail deny=5 unlock_time=300
# Check/reset lockout
faillock --user username # View status
faillock --user username --reset # Unlock
# PASSWORD QUALITY
# /etc/security/pwquality.conf
minlen = 12
minclass = 3
dcredit = -1
ucredit = -1
lcredit = -1
ocredit = -1
# 2FA WITH GOOGLE AUTHENTICATOR
# 1. Install
pacman -S libpam-google-authenticator
# 2. User setup
google-authenticator
# 3. Add to PAM (/etc/pam.d/sshd)
auth required pam_google_authenticator.so
# 4. Enable in sshd_config
ChallengeResponseAuthentication yes
# INFRASTRUCTURE: AD/SSSD auth
# After SSSD configured, add to PAM:
auth sufficient pam_sss.so
account [default=bad success=ok user_unknown=ignore] pam_sss.so
password sufficient pam_sss.so
session optional pam_sss.so
LDAP/AD Integration
# SSSD - System Security Services Daemon
# Handles AD/LDAP/Kerberos authentication
# INSTALL (RHEL/Rocky)
dnf install sssd sssd-ad sssd-ldap oddjob-mkhomedir
# JOIN AD DOMAIN
realm discover inside.domusdigitalis.dev
realm join inside.domusdigitalis.dev -U Administrator
# VERIFY JOIN
realm list
klist # Kerberos tickets
# SSSD CONFIG (/etc/sssd/sssd.conf)
cat <<'EOF'
[sssd]
domains = inside.domusdigitalis.dev
services = nss, pam, ssh
[domain/inside.domusdigitalis.dev]
ad_domain = inside.domusdigitalis.dev
krb5_realm = INSIDE.DOMUSDIGITALIS.DEV
realmd_tags = manages-system joined-with-adcli
cache_credentials = True
id_provider = ad
access_provider = ad
fallback_homedir = /home/%u
default_shell = /bin/bash
# Only allow specific groups
ad_access_filter = (memberOf=CN=Linux-Users,OU=Groups,DC=inside,DC=domusdigitalis,DC=dev)
# Username format
use_fully_qualified_names = False
ldap_id_mapping = True
EOF
chmod 600 /etc/sssd/sssd.conf
systemctl enable --now sssd
# NSS CONFIG (/etc/nsswitch.conf)
# Add sss to passwd, group, shadow
passwd: files sss
group: files sss
shadow: files sss
# ENABLE HOME DIRECTORY CREATION
authselect select sssd with-mkhomedir --force
systemctl enable --now oddjobd
# TEST AD USER
id adusername # Should show AD info
getent passwd adusername # Should resolve
# SUDO FOR AD GROUPS
cat > /etc/sudoers.d/10-ad-admins <<'EOF'
%Linux-Admins ALL=(ALL) ALL
EOF
# SSH ACCESS FOR AD GROUPS
# /etc/ssh/sshd_config
AllowGroups wheel Linux-Users
# COMMON ISSUES
# Cache: sssd stores data in /var/lib/sss/db/
sss_cache -E # Clear all cache
systemctl restart sssd
# Debug
sssd -i -d 5 # Interactive debug mode
journalctl -u sssd -f # Watch logs
# INFRASTRUCTURE: Check AD connectivity
realm list
klist -l # List Kerberos tickets
getent passwd "INSIDE\\Administrator" # Test AD lookup
User Auditing
# LAST LOGINS
last # Recent logins
last -n 20 # Last 20
last -a # Show hostname
last username # Specific user
lastlog # All users' last login
lastlog -u username # Specific user
# FAILED LOGINS
lastb # Failed login attempts (root only)
lastb -n 20 # Last 20 failures
# WTMP/BTMP FILES
# /var/log/wtmp - Successful logins (last reads this)
# /var/log/btmp - Failed logins (lastb reads this)
# /var/log/lastlog - Last login per user
# CURRENTLY LOGGED IN
who # Basic info
who -aH # Detailed with header
w # With activity
users # Just usernames
# AUDITD - Advanced auditing
# Install: dnf install audit
# Log all user commands
auditctl -a exit,always -F arch=b64 -S execve -F euid=0
# Log sudo usage
auditctl -w /etc/sudoers -p wa -k sudo_changes
auditctl -w /etc/sudoers.d/ -p wa -k sudo_changes
# Log SSH key changes
auditctl -w /home -p wa -k ssh_keys
# Search audit logs
ausearch -k sudo_changes # By key
ausearch -ua 1000 # By UID
ausearch -x /usr/bin/passwd # By executable
ausearch -ts today # Today only
# Generate report
aureport --auth # Authentication report
aureport --failed # Failed events
aureport --login # Login report
# PROCESS ACCOUNTING
# Track all processes per user
# Install: pacman -S acct / dnf install psacct
systemctl enable --now psacct # Start accounting
lastcomm # Recent commands
lastcomm --user username # Specific user
sa # Summary by command
sa -u # Summary by user
# INFRASTRUCTURE: Security audit
echo "=== Users with shell ==="
awk -F: '$7 !~ /nologin|false/ {print $1}' /etc/passwd
echo "=== Users with sudo ==="
grep -h "^[^#]*ALL" /etc/sudoers /etc/sudoers.d/* 2>/dev/null | grep -v "^#"
echo "=== Recent sudo usage ==="
journalctl -u sudo --since "7 days ago" | grep -E "COMMAND|USER" | tail -20
echo "=== Failed SSH attempts ==="
journalctl -u sshd --since "1 day ago" | grep -i "failed\|invalid" | tail -10
echo "=== Users logged in now ==="
who -aH
User Gotchas
# WRONG: usermod -G (replaces all groups!)
usermod -G docker username # REMOVES from wheel, audio, etc!
# CORRECT: usermod -aG (append)
usermod -aG docker username # Adds docker, keeps others
# WRONG: Editing /etc/sudoers directly
vim /etc/sudoers # No syntax checking!
# CORRECT: Use visudo
visudo # Validates syntax
visudo -f /etc/sudoers.d/myfile # For drop-in files
# WRONG: Wrong permissions on .ssh
chmod 777 ~/.ssh # SSH refuses to work
chmod 644 ~/.ssh/id_rsa # Private key exposed!
# CORRECT: Strict permissions
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
chmod 600 ~/.ssh/id_*
chmod 644 ~/.ssh/*.pub
# WRONG: Expecting group changes immediately
usermod -aG docker $USER
docker ps # Permission denied!
# CORRECT: Re-login or newgrp
usermod -aG docker $USER
newgrp docker # Start new shell with group
# OR
exec su - $USER # Full re-login
# WRONG: Deleting user without checking processes
userdel username # Orphaned processes still running!
# CORRECT: Kill processes first
pkill -u username
userdel -r username
# WRONG: Assuming sudo = root everywhere
sudo command # Works
ssh root@server # Different authentication!
# CORRECT: Understand they're separate
# sudo = local privilege escalation
# root SSH = remote authentication
# WRONG: NOPASSWD for everything
evanusmodestus ALL=(ALL) NOPASSWD: ALL # Security risk!
# CORRECT: NOPASSWD for specific commands only
evanusmodestus ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart nginx
# WRONG: Assuming AD username = local username
ssh evan@server # Might be different!
# CORRECT: Check SSSD config
# use_fully_qualified_names = True → DOMAIN\\user or user@domain
# use_fully_qualified_names = False → just user
Quick Reference
# USER MANAGEMENT
useradd -m -s /bin/bash user # Create
userdel -r user # Delete with home
usermod -aG group user # Add to group (APPEND!)
passwd user # Set password
chage -l user # Password aging info
# GROUP MANAGEMENT
groupadd group # Create
groupdel group # Delete
groups user # Show user's groups
gpasswd -d user group # Remove from group
# SUDO
visudo # Edit sudoers
visudo -f /etc/sudoers.d/myfile # Edit drop-in
sudo -l # List my permissions
# SSH ACCESS
ssh-keygen -t ed25519 # Generate key
ssh-copy-id user@host # Deploy key
chmod 700 ~/.ssh # Fix permissions
chmod 600 ~/.ssh/authorized_keys
# AUDITING
last # Recent logins
lastb # Failed logins
who -aH # Current sessions
faillock --user user # Check lockout
# SSSD/AD
realm join domain.local # Join domain
id aduser # Test lookup
sss_cache -E # Clear cache
# COMMON SUDOERS
user ALL=(ALL) ALL # Full access
user ALL=(ALL) NOPASSWD: /path/to/cmd # No password for cmd
%group ALL=(ALL) ALL # Group access