# Harden a VM

The platform enforces one rule on your machine — anti-spoof, which lets it
emit only from its own IP and MAC — and filters nothing else. There is no
firewall API; the policy is yours. **A rule that drops your own SSH is
permanent**: there is no console and no password
([access](../vm.md#access-is-ssh-and-only-ssh)).

## Never apply a rule you cannot undo

Apply firewall and sshd changes behind a dead-man switch: schedule the revert
*first*, confirm you still have access, then cancel the revert.

```
# 1. schedule the undo before you change anything
systemd-run --on-active=10min --unit=fw-revert nft flush ruleset

# 2. apply your ruleset
nft -f /etc/nftables.conf

# 3. from somewhere else, open a SECOND ssh session and confirm it works

# 4. only then, cancel the undo
systemctl stop fw-revert.timer
```

If step 3 fails, do nothing: the timer fires and the machine lets you back in.
The same shape works for `ufw` (`systemd-run --on-active=10min ufw disable`)
and for sshd — restart it, then prove a *new* session connects while the old
one is still open, because the session you are holding survives a restart that
broke the config.

Take a [snapshot](../vm.md#snapshots) before anything structural: the only
other undo. It bills for as long as you keep it, and rolling back needs the VM
stopped and leaves it stopped.

## A policy that fits this platform

- **Inbound: default deny, allow what you serve.** Nothing of ours filters
  ahead of you, so every port you leave open is open to the Internet.
- **Keep 22 reachable** from wherever you actually connect from. If that is a
  fixed address, scope the rule to it; if it is not, leave 22 open to the
  world and rely on keys — an unreachable machine is a worse outcome than an
  exposed SSH port that takes no passwords.
- **Outbound: allow.** Filtering your own egress breaks package updates in
  ways that are tedious to find.
- **Do not filter loopback**, and allow established/related before anything
  else. Most self-inflicted lockouts are a missing conntrack rule, not a
  missing SSH rule.

## sshd

Key-only is already the case, so the useful changes are small:

- `PermitRootLogin no`. Log in as `ubuntu` and use `sudo`.
- `PasswordAuthentication no` and `KbdInteractiveAuthentication no`, so a
  future image change cannot quietly turn one on.
- Leave the port at 22 unless you have a reason. Moving it hides you from
  your own logs and from nobody else.

Run `sshd -t` before restarting: it refuses to start on a config it cannot
parse, and an sshd that will not start is a machine you cannot enter.

## Two things the platform already decided

- **Outbound TCP `:25` is blocked** upstream of your VM, so no in-guest
  configuration reopens it. Mail goes through the
  [relay](../email.md).
- **No rDNS/PTR management.** Ask by ticket if you need one.

Details of both: [Firewall](../vm.md#firewall-one-platform-rule-the-rest-is-yours).

Already locked out: [Debug a VM that will not come
up](debug-vm-boot.md#5-if-it-is-the-guest-it-is-unrecoverable).
