Hardening a public world

A world that anybody can reach needs more than the defaults of a development machine. Work through this list before you announce your server.

Network and TLS

  • Put the server behind a reverse proxy that terminates TLS (HAProxy, nginx, Caddy). Never offer accounts over plain HTTP to the internet: passwords and session tokens would travel in the clear. The client refuses remote credentials over HTTP (only http://127.0.0.1, localhost and [::1] are allowed without TLS).

  • Bind the server so only the proxy can reach it (listen = "127.0.0.1:7400" on the same host, or a private address the proxy uses). Open only 443 (and 80 for the certificate) toward the public internet; PostgreSQL stays on its local socket.

  • Set public_url to the HTTPS URL players and peers use (the name on the certificate), not the internal host:7400 address.

  • Under [limits], set trust_proxy = true only behind a proxy you run, so limits and the access log see the real client from X-Forwarded-For. A top-level trust_proxy key is ignored.

HAProxy

HAProxy terminates TLS and forwards plain HTTP to the world process. Prefer a TCP or a light HTTP health check: GET / returns 404 on this server, so a check that expects 200 on / marks the backend down. GET /world is a small JSON metadata reply (a few SQL queries, not the heightmap) and is fine as a check every few seconds.

Example backend (TLS certificates and the frontend that selects by Host are outside this snippet; Spoje.NET operators keep the live list in the haproxy-ansible-role inventory):

backend world_example_org
    mode http
    balance roundrobin
    option forwardfor
    option httpchk GET /world
    http-check send meth GET uri /world ver HTTP/1.1 hdr Host world.example.org
    http-check expect status 200
    server world 10.0.0.10:7400 check

Matching /etc/ishtaria/server.toml on the world host:

listen = "0.0.0.0:7400"   # or the private address HAProxy uses
public_url = "https://world.example.org"

[limits]
trust_proxy = true

Players enter https://world.example.org in the client (no :7400). Federation share links and public_url must use the same HTTPS name.

nginx

A minimal nginx site (the package ishtaria-web ships a fuller example for the portal):

server {
    listen 443 ssl http2;
    server_name world.example.org;
    # ssl_certificate ... ssl_certificate_key ...  (for example from certbot)
    client_max_body_size 64k;                       # the server refuses more anyway
    location / {
        proxy_pass http://127.0.0.1:7400;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_read_timeout 35s;                      # the server times requests out at 30 s
    }
}

Accounts and limits

  • Keep the default auth_per_minute (10 per address). Registration and login share it, which slows guessing and the making of accounts alike.

  • Passwords are stored as Argon2id hashes and session tokens only as hashes; tokens expire and can be revoked (log out). Nothing in the logs contains a password, a token or a query string.

  • Consider also rate limiting in the proxy (limit_req) and a ban tool such as fail2ban on the access log lines with status=429.

  • Bans of abusive players and scheduled shutdowns are done with ishtaria-admin.

The system service

The package runs the server as a systemd unit under its own user. Keep it that way: no root, the configuration file readable only by that user, and DATABASE_URL (if you use it) in the environment of the unit, not in a world-readable file. Federation signing keys live in the database; back it up (Backup, restore and upgrade) and treat the backup as a secret.

Checklist

☐

TLS in front, server bound to loopback, firewall closed except 80/443

☐

trust_proxy set exactly when a proxy of yours is in front

☐

[limits] reviewed for the number of players per address you expect

☐

/metrics readable by your monitoring only (Monitoring)

☐

backups run and a restore was tried on another machine (Backup, restore and upgrade)

☐

the operator policy of federation is approve or closed unless you know why not

☐

packages are updated (apt upgrade); read the changelog before a server upgrade