| | {{infobox |
| | | title = Linux Security |
| | | chapter = Linux Handbook, chapter 22 — the defensive layer: least privilege, hardening, auditing |
| | | core | Defense in depth — permissions, minimal surface, verified everything |
| | | tools | chmod/umask · sudo · firewall · ssh hardening · audit tools |
| | | mindset | Every daemon is an attack surface; every setuid a privilege |
| | | next | Eyes-free Linux — the accessible path |
| | }} |
| | |
| | **Linux security is a discipline of subtraction**: every running service is an attack surface, every world-writable file a liability, every setuid binary a privilege. This chapter covers the layers — permissions and least privilege, SSH hardening, the firewall, update hygiene, the audit commands that show you your own exposure, and the SELinux/AppArmor machinery underneath. |
| | |
| | ## The foundation — least privilege |
| | |
| | ```sh |
| | # every service runs as its own user — never root |
| | id nginx # nginx's uid — not root |
| | ps -eo user,comm | grep -v ' root ' | sort -u # who runs what |
| | ``` |
| | |
| | Principles: run daemons as dedicated users, grant only the rights they need, `NOPASSWD:` only for specific commands, sudo over su, key-only ssh. The model from chapter 12 — owner/group/other — is the wall; this chapter is keeping the wall maintained. |
| | |
| | ## SSH — the exposed door |
| | |
| | ```sh |
| | # /etc/ssh/sshd_config — the three lines that matter |
| | PermitRootLogin no |
| | PasswordAuthentication no |
| | Port 2222 # obscurity tax, not security — but reduces noise |
| | ``` |
| | |
| | `sshd -t` validates before reload. Key-only auth (`PasswordAuthentication no`) is the single largest hardening win — brute force becomes impossible. `fail2ban` rate-limits attempts; `AllowUsers` whitelists. |
| | |
| | ## The firewall — default deny |
| | |
| | ```sh |
| | nft list ruleset # the current policy |
| | # the canonical minimal policy: |
| | iptables -P INPUT DROP |
| | iptables -A INPUT -i lo -j ACCEPT |
| | iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT |
| | iptables -A INPUT -p tcp --dport 22 -j ACCEPT |
| | iptables -A INPUT -p tcp --dport 80 -j ACCEPT |
| | iptables -A INPUT -p tcp --dport 443 -j ACCEPT |
| | ``` |
| | |
| | Default-deny is the posture; allow what's needed. `ss -tlnp` tells you what's *actually* listening — the firewall audit always starts there. |
| | |
| | ## The audit commands — your own exposure |
| | |
| | ```sh |
| | ss -tlnp # every listener — attack surface inventory |
| | find / -perm -4000 -type f # every setuid binary |
| | find / -perm -0002 -type d # world-writable dirs |
| | find / -nouser -o -nogroup # orphaned files |
| | ls -la /etc/sudoers.d/ # the delegation surface |
| | cat /etc/passwd | awk -F: '$3==0' # everyone with uid 0 — only root |
| | last -20 # login history |
| | grep 'Failed password' /var/log/auth.log | tail # brute-force traffic |
| | ``` |
| | |
| | `ss -tlnp`, the setuid inventory, `last`, and the auth log — the four commands that see a system's real exposure. |
| | |
| | ## Updates — the unglamorous hardening |
| | |
| | ```sh |
| | apt update && apt upgrade # Debian |
| | dnf upgrade # Fedora |
| | slackpkg update && slackpkg upgrade-all # Slackware |
| | unattended-upgrades # auto security updates — Debian |
| | needs-restarting -r # does it need a reboot? |
| | ``` |
| | |
| | Most real-world compromises are *unpatched software*, not clever attacks — the update habit is the highest-value security control. |
| | |
| | ## The hardening checklist — the working sequence |
| | |
| | ```sh |
| | # 1. Accounts |
| | passwd -l root # or keep root-key-only |
| | awk -F: '$2 ~ /^[!*]/ {print $1}' /etc/shadow # locked/no-login accounts |
| | find / -nouser -o -nogroup 2>/dev/null # orphans |
| | |
| | # 2. SSH — the door |
| | PermitRootLogin no · PasswordAuthentication no · AllowUsers me |
| | sshd -t && systemctl reload sshd |
| | fail2ban or sshguard # rate-limit attempts |
| | ssh-keygen -t ed25519; ssh-copy-id |
| | |
| | # 3. Firewall — default deny |
| | iptables -P INPUT DROP; -P FORWARD DROP |
| | -A INPUT -i lo -j ACCEPT |
| | -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT |
| | -A INPUT -p tcp --dport 22 -j ACCEPT |
| | -A INPUT -j LOG --log-prefix "DROP: " # watch what hits the wall |
| | |
| | # 4. Services — kill the surface |
| | ss -tlnp # inventory |
| | systemctl disable unused-svc # off |
| | apt remove unused # gone |
| | |
| | # 5. Updates — the unglamorous wall |
| | apt update && apt upgrade # the habit |
| | unattended-upgrades # auto security |
| | |
| | # 6. The audit |
| | find / -perm -4000 -type f # setuid inventory |
| | find / -perm -0002 -type d # world-writable |
| | last -20; faillog -a # who's tried in |
| | ``` |
| | |
| | ## The service hardening — the per-daemon grammar |
| | |
| | ```ini |
| | # systemd's sandboxing — the modern confinement: |
| | [Service] |
| | ProtectSystem=strict # / read-only — services can't touch it |
| | ProtectHome=true # /home invisible |
| | PrivateTmp=true # own /tmp |
| | NoNewPrivileges=true # no privilege escalation |
| | PrivateDevices=true # no /dev |
| | ProtectKernelTunables/Modules/Logs=true |
| | CapabilityBoundingSet=CAP_NET_BIND_SERVICE # only this cap |
| | RestrictAddressFamilies=AF_INET AF_INET6 # only these sockets |
| | SystemCallFilter=@system-service # syscall allowlist |
| | ReadWritePaths=/var/lib/myapp # the one writable dir |
| | MemoryMax=512M; CPUQuota=50% # resource caps |
| | ``` |
| | |
| | ## The kernel-level machinery |
| | |
| | - **SELinux** — mandatory access control — policies that confine processes *beyond* file permissions (`sestatus`, `setenforce`, `ausearch`) — Fedora's default |
| | - **AppArmor** — the simpler MAC — profiles per program (`aa-status`) — Debian/Ubuntu's |
| | - **seccomp** — syscall filtering — restricts which syscalls a process may make |
| | - **capabilities** — root unbundled (chapter 12) |
| | - **cgroups + namespaces** — the isolation primitives containers ride |
| | |
| | ## Crypto at rest — LUKS + the secrets files |
| | |
| | ```sh |
| | cryptsetup luksFormat /dev/sda3 # encrypt the partition |
| | cryptsetup luksOpen /dev/sda3 crypt |
| | mkfs.ext4 /dev/mapper/crypt |
| | ``` |
| | |
| | Full-disk encryption (LUKS) protects at rest — a stolen disk yields nothing without the passphrase. `/etc/shadow`'s `600` perms, `~/.ssh`'s `700`, private keys' `600` — the permission layer *is* the crypto's keeper. |
| | |
| | ## The operational hygiene |
| | |
| | ```sh |
| | umask 027 # default-private for a server |
| | passwd -l dormantacct # lock unused accounts |
| | chmod 600 ~/.ssh/id_* # private keys private |
| | sudo -l # YOUR sudo rights — know them |
| | find . -name '*.key' -perm -o+r # world-readable private keys — fix now |
| | ``` |
| | |
| | ## Where security goes wrong |
| | |
| | | Symptom | Cause | |
| | | --- | --- | |
| | | Password auth still on after "hardening" | `sshd -t` passed, `systemctl reload` didn't happen | |
| | | `Permission denied` on a legit file | SELinux context — `restorecon` or `chcon` | |
| | | Root login still open | `PermitRootLogin` not actually `no` | |
| | | A daemon runs as root "for simplicity" | The classic — dedicated user now | |
| | | `sudo -l` shows (ALL) NOPASSWD: ALL | The sudoers gift that defeats sudo | |
| | | A world-writable setuid binary | `find` it — the audit commands above | |
| | |
| | ## Exercises |
| | |
| | 1. `ss -tlnp` your box — justify every listener. |
| | 2. `find / -perm -4000` — inventory setuid; research one you don't recognize. |
| | 3. `sshd -t` a hardened sshd_config — verify before reload. |
| | 4. `ausearch -m avc` or `aa-status` — see your MAC layer. |
| | 5. Write the minimal iptables policy from memory. |
| | |
| | ## See also |
| | |
| | - [[Users and permissions]] · [[Networking]] · [[Linux]] · [[Inside a Linux System]] |
| | |
| | [[Category:Linux]] |
| | [[Category:Handbook]] |
| | [[Category:Security]] |
| | |