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