Skip to content
ALB Wiki

Diff — Linux Security

Comparing rev 5 → rev 6

{{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 capability grammar — root's powers unbundled
+
+
```sh
+
getcap /usr/bin/ping               # what does it carry?
+
# /usr/bin/ping cap_net_raw=ep    — raw sockets, no setuid needed
+
setcap cap_net_bind_service=+ep /usr/bin/myd   # bind low ports, not root
+
setcap -r /path                    # strip them
+
getcap -r / 2>/dev/null            # every capability file on the system
+
```
+
+
Capabilities split root into ~40 named powers — `cap_net_bind_service` (bind <1024), `cap_net_raw` (raw sockets), `cap_sys_admin` (the dangerous one — mounts, namespaces), `cap_chown`, `cap_dac_override`, `cap_kill`, `cap_setuid`, `cap_sys_time`. Modern daemons carry the one they need instead of full setuid — `ping` carrying `cap_net_raw` alone is the proof.
+
+
## The PAM layer — where auth actually happens
+
+
```text
+
/etc/pam.d/sshd — sshd's auth stack:
+
auth     required   pam_env.so
+
auth     required   pam_unix.so        — the password check
+
auth     required   pam_nologin.so
+
account  required   pam_unix.so        — expiry, policy
+
password required   pam_unix.so        — password changes
+
session  optional   pam_motd.so
+
session  required   pam_limits.so
+
session  optional   pam_systemd.so
+
```
+
+
Four phases — `auth`/`account`/`password`/`session`; four controls — `required`/`requisite`/`sufficient`/`optional`. PAM stacks modules — add `pam_google_authenticator` for TOTP, `pam_faillock` for lockout — without touching sshd's code.
+
## 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]]