# Host Hardening Baseline — SNS Networking

**Entity:** SNS Networking · **Status:** Buildable now (Ansible playbook)

## What it is

The minimum security posture applied to every Linux host — on-prem VM, LXC, or
cloud VPS — before it does anything else. This is the Ansible "base" role that
runs first on every new machine.

## Stack

| Component | Software | FOSS | Role |
|-----------|----------|------|------|
| Firewall | UFW (iptables frontend) | Yes | Default-deny inbound, explicit allow |
| Brute-force protection | Fail2ban | Yes | SSH + web login jail |
| SSH hardening | OpenSSH (config only) | Yes | Key-only, no root, no password |
| Auto-updates | unattended-upgrades (Debian) | Yes | Security patches only |
| Audit logging | auditd | Yes | File access, privilege escalation events |
| Rootkit detection | rkhunter | Yes | Weekly scan, email on findings |

## The Playbook (what it does)

```yaml
# roles/base-hardening/tasks/main.yml (conceptual)
- name: SSH hardening
  # PermitRootLogin no
  # PasswordAuthentication no
  # PubkeyAuthentication yes
  # MaxAuthTries 3
  # AllowUsers sam

- name: UFW default deny
  # Default incoming: deny
  # Default outgoing: allow
  # Allow: SSH (22/tcp) from Tailscale/WireGuard subnet only

- name: Fail2ban SSH jail
  # maxretry: 3
  # bantime: 3600
  # findtime: 600

- name: Unattended upgrades (security only)
  # Automatic-Reboot: false (manual kernel reboots)

- name: Auditd rules
  # Watch /etc/passwd, /etc/shadow, /etc/sudoers
  # Log all sudo usage
  # Log failed file access attempts

- name: Disable unused services
  # Stop & mask: cups, avahi-daemon, bluetooth (if present)

- name: Sysctl hardening
  # net.ipv4.conf.all.rp_filter = 1
  # net.ipv4.icmp_echo_ignore_broadcasts = 1
  # net.ipv4.conf.all.accept_redirects = 0
  # net.ipv4.conf.all.send_redirects = 0
  # kernel.randomize_va_space = 2
```

## Per-Host Firewall Rules

| Host type | Inbound allowed | Notes |
|-----------|----------------|-------|
| All hosts | SSH from 10.10.0.0/24 (WireGuard) | Management only over VPN |
| Web-facing (Caddy) | 80, 443 from 0.0.0.0/0 | Public web traffic |
| Bastion (Linode) | 22, 51820/udp from 0.0.0.0/0 | SSH + WireGuard |
| Internal services | App port from service VLAN only | No direct internet access |
| Proxmox host | 8006 from Tailscale only | Web UI never on public IP |

## Build Steps

1. Write Ansible role `roles/base-hardening/` with the above tasks.
2. Add to `site.yml` as the first role for all host groups.
3. Run against all existing hosts: `ansible-playbook site.yml --tags hardening`
4. Verify: `ssh root@host` should fail. `ssh sam@host` with key should work.
5. Verify: `nmap -sS host` from outside WireGuard should show only allowed ports.

## Security Posture

- **Default deny.** Nothing gets in unless explicitly allowed.
- **No passwords anywhere.** SSH key-only. Service accounts use tokens or certs.
- **Audit trail.** Every sudo, every sensitive file access logged.
- **Automatic patching.** Security updates apply daily without manual intervention.
  Kernel updates require manual reboot (scheduled maintenance window).

## Upgrade Path

- **CIS Benchmark compliance:** Run `cis-cat` scanner and close gaps for client
  environments that require formal compliance.
- **OSSEC/Wazuh:** When you need centralized security event management across 10+ hosts.
- **SELinux/AppArmor:** Enforce MAC policies on high-value services (currently not
  enabled — adds complexity without proportional gain at this scale).

<!-- ponytail: No SELinux/AppArmor yet. Ceiling: DAC-only can't contain a compromised
     service from lateral movement on the same host. Upgrade: AppArmor profiles for
     Caddy, WordPress, Gitea when client compliance requires it. -->
