Legendary Arena Lab

SSH Hardening

wiki

SSH Hardening

Migration in progress. api.legendary-arena.com and its PostgreSQL are moving off Render onto a self-hosted DigitalOcean Ubuntu droplet fronted by Cloudflare, per docs/PLAN.md . Until decommission, Render stays warm as the rollback target.

Summary

SSH Hardening locks remote access to the droplet down to key-only login for a non-root operator account, disabling root SSH and password authentication. It is the first hardening step after first-boot provisioning and gates every later phase — nothing else is safe to run until remote access is both locked and verified.

Mechanics

The entity is owned by infra/scripts/05-user-and-ssh.ps1 , an idempotent PowerShell provisioner that must run as root. It does three things:

  1. Ensures the operator account. Creates the user named by OPERATOR_USER (default operator) if absent (useradd -m -s /bin/bash) and adds it to the sudo group. Re-running is safe — an existing user is left in place and re-added to sudo idempotently.

  2. Writes a managed drop-in, not an inline edit of the distro default. The file /etc/ssh/sshd_config.d/99-la-hardening.conf holds the full hardened directive set:

    PermitRootLogin no
    PasswordAuthentication no
    ChallengeResponseAuthentication no
    KbdInteractiveAuthentication no
    UsePAM yes
    PubkeyAuthentication yes
    

    Using a numbered drop-in (99-) keeps the change explicit, auditable, and re-appliable without touching /etc/ssh/sshd_config.

  3. Validates and reloads. Runs sshd -t to reject a broken config before reloading, then reloads whichever unit exists (ssh.service or sshd.service). The script’s final log line is the safety instruction: open a new SSH session and verify login before closing existing ones.

The operator’s public key arrives earlier, at first boot, via infra/cloud-init.yaml (the ssh_authorized_keys block on the operator user) — see Provision Droplet . This page assumes that key is already present.

Interactions

  • Provision Droplet . Cloud-init creates the operator user and installs its authorized key at first boot; this script hardens the daemon that key authenticates against. The operator account is the shared identity both steps configure.
  • UFW Firewall . Runs next. UFW’s allow OpenSSH / allow 22/tcp rule must be in place before default-deny is enabled, or the hardened SSH path is firewalled off. The two steps are a pair: harden the daemon, then fence the port.
  • Render-to-DigitalOcean Migration . Access hardening is Phase 1 of the migration sequence; its exit criterion is “operator access verified from a clean shell.”

Edge Cases

  • Lockout is the primary hazard. Disabling PasswordAuthentication and PermitRootLogin while your only session is a password/root session can strand you. The runbook’s safety sequence — keep two sessions open, run in one, verify a third fresh login before closing any — exists to prevent exactly this. See PLAN.md §5.1 D .
  • Missing authorized key. If cloud-init’s key placeholder was never replaced, key auth cannot succeed and disabling passwords locks everyone out. Verify ~operator/.ssh/authorized_keys is populated before running this script.
  • Service unit name varies. Ubuntu ships the SSH unit as ssh.service; some images use sshd.service. The script probes for both and throws if neither is found rather than silently skipping the reload.
  • Non-standard SSH port. This script hardens auth but does not change the port. If you move SSH off 22, the matching allow rule lives in UFW Firewall via its SSH_PORT override — keep the two in sync.

Execute

Run as root on the droplet:

sudo pwsh -File infra/scripts/05-user-and-ssh.ps1

Optional operator-username override:

sudo OPERATOR_USER=operator pwsh -File infra/scripts/05-user-and-ssh.ps1

The copy-paste operator runbook is content/foundations/ssh-hardening.md .

Verify

# confirm the hardened directives are in place
sudo grep -E '^(PermitRootLogin|PasswordAuthentication)' \
  /etc/ssh/sshd_config.d/99-la-hardening.conf

# config must parse clean
sudo sshd -t

# service must be healthy
sudo systemctl status ssh --no-pager

Expected: PermitRootLogin no, PasswordAuthentication no, sshd -t exits zero, and a new SSH session as operator logs in by key.

References