# Server Configuration Security Checklist

Security checks for OS-level server hardening. Extracted from the Gemma server
security research report (§03 Network Security, §04 SSH Hardening, §08 Server
Hardening).

---

## Check: SSH password authentication disabled

- **Priority**: Critical
- **What to look for**: `/etc/ssh/sshd_config` — check the value of `PasswordAuthentication` and `AuthenticationMethods`. Default Debian/Ubuntu images often ship with password auth enabled.
- **Pass condition**: `PasswordAuthentication no` and `AuthenticationMethods publickey` are set in `sshd_config`, and `sudo sshd -T | grep passwordauthentication` returns `no`.
- **Fail example**:
  ```
  # /etc/ssh/sshd_config
  PasswordAuthentication yes
  ```
- **Remediation**: Edit `/etc/ssh/sshd_config`, then test and reload. Always verify from a second terminal before closing the current session.
  ```
  # /etc/ssh/sshd_config
  PasswordAuthentication no
  AuthenticationMethods publickey
  PubkeyAuthentication yes
  PermitEmptyPasswords no
  ```
  ```bash
  sudo sshd -t && sudo systemctl restart sshd
  ```
- **Source**: Security report §04

---

## Check: PermitRootLogin no

- **Priority**: Critical
- **What to look for**: `/etc/ssh/sshd_config` — value of `PermitRootLogin`. Default cloud images often set this to `prohibit-password` (key-only root login), which is still a risk.
- **Pass condition**: `PermitRootLogin no` is set. The root account should not be directly reachable over SSH under any circumstances.
- **Fail example**:
  ```
  # /etc/ssh/sshd_config
  PermitRootLogin yes
  # or:
  PermitRootLogin prohibit-password
  ```
- **Remediation**: Ensure a non-root admin user with `sudo` exists first, then disable root login.
  ```
  # /etc/ssh/sshd_config
  PermitRootLogin no
  ```
  ```bash
  sudo sshd -t && sudo systemctl restart sshd
  ```
- **Source**: Security report §04

---

## Check: AllowGroups restricts SSH access

- **Priority**: Important
- **What to look for**: `/etc/ssh/sshd_config` — presence of `AllowGroups` directive. Without it, any OS user with a valid key can SSH in, including service accounts.
- **Pass condition**: `AllowGroups sshusers` (or equivalent) is set. Human users are members of that group. Service accounts (`appuser`, `deploy`) are not members.
- **Fail example**:
  ```
  # /etc/ssh/sshd_config — no AllowGroups directive
  PasswordAuthentication no
  PermitRootLogin no
  ```
- **Remediation**: Add the `AllowGroups` directive and create the group.
  ```bash
  sudo groupadd sshusers
  sudo usermod -aG sshusers alice
  sudo usermod -aG sshusers bob
  ```
  ```
  # /etc/ssh/sshd_config
  AllowGroups sshusers
  ```
  ```bash
  sudo sshd -t && sudo systemctl restart sshd
  ```
- **Source**: Security report §04

---

## Check: UFW default-deny incoming

- **Priority**: Critical
- **What to look for**: Run `sudo ufw status verbose` on the server. Check both `Default:` policy lines. Confirm UFW is active (`Status: active`).
- **Pass condition**: UFW is active, `Default: deny (incoming)` and `Default: allow (outgoing)` are shown.
- **Fail example**:
  ```
  Status: inactive
  # or:
  Status: active
  Default: allow (incoming), allow (outgoing)
  ```
- **Remediation**: Set defaults and enable UFW. Configure all required allow rules *before* enabling to avoid lockout.
  ```bash
  sudo ufw default deny incoming
  sudo ufw default allow outgoing
  sudo ufw allow 443/tcp
  sudo ufw allow 80/tcp
  # SSH from Tailscale interface only (configure before enabling)
  sudo ufw allow in on tailscale0 to any port 22 proto tcp
  sudo ufw deny 22/tcp
  sudo ufw enable
  ```
- **Source**: Security report §03

---

## Check: Only ports 80, 443, and SSH from restricted CIDRs allowed

- **Priority**: Critical
- **What to look for**: `sudo ufw status numbered` — all allowed inbound rules. Also check cloud-provider firewall/security-group rules. Look for any rule allowing port 22 from `0.0.0.0/0` or `::/0`, or any unexpected open ports.
- **Pass condition**: Only ports 80 and 443 are open to the public internet. Port 22 is allowed only from the Tailscale interface (`tailscale0`) or a specific CIDR (office/VPN IPs). All other ports are denied.
- **Fail example**:
  ```
  # sudo ufw status
  22/tcp    ALLOW IN    Anywhere
  5432/tcp  ALLOW IN    Anywhere    # database exposed!
  ```
- **Remediation**: Remove broad allow rules for SSH and close any unintended ports.
  ```bash
  sudo ufw delete allow 22/tcp
  sudo ufw delete allow 5432/tcp
  sudo ufw allow in on tailscale0 to any port 22 proto tcp
  ```
- **Source**: Security report §03

---

## Check: fail2ban installed and configured

- **Priority**: Recommended
- **What to look for**: `sudo systemctl status fail2ban` — confirm it is installed, enabled, and running. `sudo fail2ban-client status sshd` — confirm the SSH jail is active.
- **Pass condition**: `fail2ban` is installed, the `sshd` jail is enabled, and `sudo fail2ban-client status sshd` shows `Currently banned: N` (not necessarily 0, just active).
- **Fail example**:
  ```bash
  $ sudo systemctl status fail2ban
  Unit fail2ban.service could not be found.
  ```
- **Remediation**: Install and enable fail2ban with SSH jail.
  ```bash
  sudo apt install fail2ban
  sudo systemctl enable --now fail2ban
  ```
  ```ini
  # /etc/fail2ban/jail.d/sshd.conf
  [sshd]
  enabled = true
  port    = ssh
  filter  = sshd
  maxretry = 5
  bantime  = 3600
  findtime = 600
  ```
  ```bash
  sudo systemctl restart fail2ban
  sudo fail2ban-client status sshd
  ```
- **Source**: Security report §08

---

## Check: unattended-upgrades active for security patches

- **Priority**: Important
- **What to look for**: `sudo systemctl status unattended-upgrades` — confirm active and enabled. `cat /etc/apt/apt.conf.d/50unattended-upgrades` — confirm `security` origin is included and not commented out.
- **Pass condition**: `unattended-upgrades` is installed and running, and the configuration includes the `${distro_id}:${distro_codename}-security` origin at minimum.
- **Fail example**:
  ```bash
  $ sudo systemctl status unattended-upgrades
  Unit unattended-upgrades.service could not be found.
  ```
- **Remediation**: Install and configure unattended-upgrades.
  ```bash
  sudo apt install unattended-upgrades
  sudo dpkg-reconfigure --priority=low unattended-upgrades
  ```
  ```
  # /etc/apt/apt.conf.d/50unattended-upgrades
  Unattended-Upgrade::Allowed-Origins {
      "${distro_id}:${distro_codename}-security";
  };
  Unattended-Upgrade::Mail "admin@yourteam.com";
  Unattended-Upgrade::MailReport "on-change";
  Unattended-Upgrade::Automatic-Reboot "false";
  ```
- **Source**: Security report §08

---

## Check: sudo scoped to specific commands, no NOPASSWD:ALL

- **Priority**: Important
- **What to look for**: `sudo grep -r "NOPASSWD" /etc/sudoers /etc/sudoers.d/` — any result with `NOPASSWD: ALL` is a finding. Service accounts (deploy, appuser) should not have sudo at all.
- **Pass condition**: No user or group has `NOPASSWD: ALL`. The deploy user has either no sudo access or tightly scoped rules (e.g., `NOPASSWD: /usr/bin/docker`). Human admins authenticate with a password for sudo.
- **Fail example**:
  ```
  # /etc/sudoers.d/deploy
  deploy ALL=(ALL) NOPASSWD: ALL
  ```
- **Remediation**: Replace broad NOPASSWD grants with command-scoped rules.
  ```
  # /etc/sudoers.d/deploy  — before
  deploy ALL=(ALL) NOPASSWD: ALL

  # /etc/sudoers.d/deploy  — after
  deploy ALL=(ALL) NOPASSWD: /usr/bin/docker, /usr/bin/docker compose
  ```
  Always edit via `sudo visudo -f /etc/sudoers.d/deploy` to catch syntax errors before saving.
- **Source**: Security report §04

---

## Check: Per-person OS accounts, no shared logins

- **Priority**: Important
- **What to look for**: `getent passwd` — look for shared accounts named `app`, `devops`, `team`, or similar multi-person accounts. Check whether multiple engineers SSH in as the same user (e.g., both use the `ubuntu` or `admin` account).
- **Pass condition**: Each engineer has their own OS account (`alice`, `bob`, etc.) with their own SSH key. Shared accounts exist only for service purposes (no SSH login allowed for those).
- **Fail example**:
  ```bash
  # All engineers SSH in as the same user
  ssh ubuntu@server  # everyone uses this account
  ```
- **Remediation**: Create individual accounts and add personal SSH keys.
  ```bash
  sudo adduser alice
  sudo usermod -aG sudo alice
  sudo usermod -aG sshusers alice
  # Then add alice's public key to /home/alice/.ssh/authorized_keys
  ```
  Offboard properly: remove SSH key, lock account, remove from sudo groups, kill active sessions.
- **Source**: Security report §04

---

## Check: SSH LogLevel VERBOSE

- **Priority**: Recommended
- **What to look for**: `/etc/ssh/sshd_config` — value of `LogLevel`. Default is `INFO` which does not capture key fingerprints used during authentication.
- **Pass condition**: `LogLevel VERBOSE` is set. This causes sshd to log the fingerprint of the key used for each login, enabling audit trails.
- **Fail example**:
  ```
  # /etc/ssh/sshd_config
  # LogLevel INFO  (default — key fingerprints not logged)
  ```
- **Remediation**:
  ```
  # /etc/ssh/sshd_config
  LogLevel VERBOSE
  ```
  ```bash
  sudo sshd -t && sudo systemctl restart sshd
  # Verify: sudo journalctl -u ssh | grep Accepted
  ```
- **Source**: Security report §04

---

## Check: MaxAuthTries 3, LoginGraceTime 30

- **Priority**: Recommended
- **What to look for**: `/etc/ssh/sshd_config` — values of `MaxAuthTries` and `LoginGraceTime`. Defaults are 6 and 120 respectively, which give attackers more attempts and time.
- **Pass condition**: `MaxAuthTries 3` and `LoginGraceTime 30` are set, reducing the window for brute-force and slow-connect attacks.
- **Fail example**:
  ```
  # /etc/ssh/sshd_config — using defaults
  # MaxAuthTries 6
  # LoginGraceTime 120
  ```
- **Remediation**:
  ```
  # /etc/ssh/sshd_config
  MaxAuthTries 3
  LoginGraceTime 30
  ```
  ```bash
  sudo sshd -t && sudo systemctl restart sshd
  ```
- **Source**: Security report §04

---

## Check: X11Forwarding no, AllowTcpForwarding no

- **Priority**: Recommended
- **What to look for**: `/etc/ssh/sshd_config` — values of `X11Forwarding` and `AllowTcpForwarding`. Default for `AllowTcpForwarding` is `yes` on many distributions, enabling SSH tunneling.
- **Pass condition**: Both `X11Forwarding no` and `AllowTcpForwarding no` are set. Server-side processes do not use X11 or rely on TCP tunneling through SSH.
- **Fail example**:
  ```
  # /etc/ssh/sshd_config
  X11Forwarding yes
  # AllowTcpForwarding yes  (default)
  ```
- **Remediation**:
  ```
  # /etc/ssh/sshd_config
  X11Forwarding no
  AllowTcpForwarding no
  PermitUserEnvironment no
  ```
  ```bash
  sudo sshd -t && sudo systemctl restart sshd
  ```
- **Source**: Security report §04
