Harden a router that faces the internet#
You will end up having made ten decisions about a router that has the internet on one side of it. Each item names a mechanism, says what it costs, and says where in the web interface it is made.
Do them in this order. It is roughly the order of value.
Before you start#
- The admin role in the web interface.
- A shell on the router, only for the break-glass token near the end and, if you choose to, for the web interface's certificate in item 3.
- A backup, taken before you begin.
- An hour. Several of these can end your session: apply them with Apply with auto-revert, and press Keep changes once the page still responds.
1. Confirm you have a firewall, and that it covers IPv6#
Open Protect → Firewall and look at both IPv4 and IPv6.
A router with an internet uplink and no firewall rules needs them before anything else on this list. A router installed without the firewall and plugged into a modem has its management interface on the internet, protected by one password.
Check both families, not one. A router filtered on IPv4 and open on IPv6 is not filtered, and a provider can bring IPv6 up without asking. When one family has rules and the other has none, the page says so: Nothing filters IPv6 here. The other address family has 12 rules — a dual-stack router needs both. A default-drop ruleset that admits what you meant has the ruleset the installer writes and how to extend it.
2. Take the management interface off the internet#
The agent listens on every address by default. The shipped service passes
--addr 0.0.0.0:8443, so out of the box nothing but the firewall keeps the web interface off the
internet.
The clean answer is the firewall: the ruleset for traffic addressed to the router drops by default and admits the local network, so with a normal installer-written ruleset the web interface is already reachable from the local network only. Confirm that from outside:
nc -vz 203.0.113.2 8443 # should not connectFor a second layer, pin the address the agent listens on: Admin → Agent settings →
Management address → Listen address, for example 192.0.2.1:8443, then Save changes. The
page warns when the address is on an internet uplink or is the router's loopback address. The
address is checked against the ones the router really has, and refused, with the list quoted, if
it is none of them. It is stored with the agent's data, so an image upgrade does not widen it
again.
The saved address is used after the agent restarts: press Restart agent, then Restart the agent? to go ahead. The web interface goes away for a few seconds, and the page follows it back — or, when the new address is one this page cannot reach, links to it. Listening on then shows the address in use. The restart is refused while changes are pending (Apply or discard the pending changes first.) or while an auto-revert window is open. The change and the restart are both in the audit log.
3. Replace the self-signed certificate#
The web interface is served with the agent's own certificate, a self-signed one by default. It encrypts the session; a browser cannot tell it from an impostor's, which is why it warns. The Certificates page does not change it: that page manages the certificates the router's services use. Serving the web interface with a trusted certificate is a start-up option of the agent, set at the console — The web interface's certificate. If you keep the self-signed one, add a browser exception for this router once, so that a warning means something again.
4. Give every person their own account, with the lowest role that works#
Nobody should share admin. The audit log records who did what, and it can only do that if "who"
is a person.
Admin → Users → Add user: Username, Initial password (they change it at their first sign-in) and Role. Four roles, enforced by the agent on every request:
| Role | As the Users page puts it |
|---|---|
| Viewer | Sees everything. Changes nothing. |
| Approver | Applies, keeps and reverts changes others have prepared, and restores earlier configurations. Cannot prepare changes, and does not see the secrets in them. |
| Operator | Prepares and applies changes, and restores earlier configurations. |
| Admin | Everything an operator can do, and manages users, API tokens and settings. |
Below the admin role, configuration reads are redacted: private keys, pre-shared secrets and
password hashes come back as [redacted]. Add an operator.
5. Turn on two-factor — after reading this#
Codes from an authenticator app, set up per account and checked with a live code before they are switched on, so a mis-scanned QR code cannot lock anyone out. Admin → Agent settings → Sign-in → Require two-factor authentication makes it mandatory: Accounts without it are asked to set it up at their next sign-in.
Requiring two-factor is refused if no admin account could sign in to set it up. Clearing somebody's factor does not turn the requirement off: they are asked to set it up at their next sign-in, which is the requirement working.
6. Use API tokens for automation#
One per consumer, with an expiry, revoked when the consumer goes away. Admin → API
tokens → Issue token: a Label (Name the system that will use this token.), a Role, and
Expires: never, or in 30 days, 90 days or a year. Copy the token now. It is not shown again.
Only its SHA-256 is stored. Tokens begin wh_, which makes a leaked one findable by a secret
scanner and in a log. Revoke ends one.
Use a token rather than a browser session for anything scripted: a token request needs no CSRF header, which a browser session's changes do.
7. Leave SSH off, or restrict it and use keys#
A freshly installed router has no SSH at all: the shipped configuration and the installer leave it off. That is the right default.
If you need it, turn it on listening on the local network only, with keys and no passwords:
- System → Settings → Router accounts: select the account you will sign in as, paste your public key under SSH keys → Public key, press Add key, then Save changes.
- System → Settings → SSH → Edit: turn on SSH server, add the local network address under Listen addresses (Every address when none is set.), turn off Password sign-in (Off allows sign-in with SSH keys only.), and Save changes.
- Apply. Changes to SSH and to router accounts are marked as able to cut you off, so use Apply with auto-revert, check you can still sign in over SSH, and press Keep changes.
8. Shorten the session lifetime#
Twelve hours by default: Admin → Agent settings → Sign-in → Session lifetime, from 5 minutes to 30 days. Shorten it if the machine is somewhere people walk past. Changing a password signs out that account's other sessions, and the Sessions section of your Account page lists your own and signs any of them out.
9. Take backups off the router#
A backup holds the whole router: password hashes, two-factor secrets, the router's API key, every pre-shared key and private key in the configuration, and your licence. The Backup page encrypts it with a passphrase that is stored nowhere; keep the file and the passphrase apart.
Take a backup that is actually complete.
10. Watch the audit log#
The Audit log tab of the System page records who did what, when, from where, with which role, and whether it worked; each entry opens to the details, the changes in words, and the router's reason when it failed. It also records refused requests, with who was refused and what they asked for — which is how you find out that somebody's token is being used for something it should not be.
Two things to do with it, in this order.
Send it somewhere else. Every entry carries a sequence number and the hash of the entry before it, so a rewrite is detectable — but only to somebody holding an earlier copy of the hashes. Root on the router can rewrite the file and re-chain it, so the chain alone proves nothing to the person whose router it is. Configure a collector: Admin → Agent settings → Audit log export → Send audit entries to a syslog server, with its Server, Port, Protocol and Facility, then Save changes.
It is off by default, so a router nobody has done this to has no witness at all. Ship logs off the box has the details. Failed and refused actions are sent at syslog warning and successful ones at informational, so alert on the difference.
Check the copy is arriving, and the file still adds up. No page shows this yet. For
automation, GET /api/audit/verify, with an admin token, answers both in one call: the chain's
first break if there is one, and the exporter's own count of what it sent and what it dropped. It
is not a spooler — an entry that cannot reach the collector is dropped and counted, not kept to
send later — so a non-zero drop count is a hole in the record.
Two more, if the router matters#
Create a break-glass token. Nothing creates one, so a default install has none, and its absence is what turns a forgotten password into a trip to the console. It takes a shell:
sudo sh -c 'umask 077; openssl rand -hex 24 > /config/wheelhouse/admin-token'The agent reads the file when it starts: press Restart agent under Admin → Agent settings afterwards. It is a full admin credential in a file. Treat it as one, or leave it absent on purpose. Locked out of the UI.
Turn on the IDS, if you have the processor for it. Turn on Suricata IDS.
The gaps you cannot close today#
None of these has a workaround inside the product.
| Gap | What it means for you |
|---|---|
| The agent listens on every address by default | Out of the box the firewall is the only thing keeping 8443 off the internet. Management address narrows it to one address and keeps it across an upgrade — narrower, not safe. Item 2. |
| Nothing signs the audit log | The chain and the copy on a collector make a rewrite detectable; neither makes the file provably the agent's. Item 10. |
| A fleet router's direct configuration route skips pending changes | POST /api/fleet/{id}/configure applies operations to a fleet router at once, with no pending changes and no review. It is recorded in the audit log. The Fleet page itself uses each router's pending changes. Point one agent at several routers. |
| Redaction hides what somebody named | Redaction matches where a value sits in the configuration, so a value secret because of where it sits — a container's environment variable, an SNMP community — is matched. What stays readable is deliberate: a container's command, arguments and labels, which can carry a token. A setting a future release adds under a name no rule mentions is covered only by its name. |
| A factory reset is not a secure erase | A factory reset, from the System page or at the console, wipes the agent's data and runs first boot again, keeping the licence key by default. It unlinks files rather than overwriting them, and it revokes nothing another system already holds — an audit log on a syslog collector, a deploy key on a git remote. Locked out of the UI. |
| Releases are not signed | A checksum detects a corrupt download, not a hostile one. Learn that a release exists. |
| No IPv6 in the v4 pages | The IPv6 page shows IPv6 addresses, neighbours, routes and counters; the Routes, Diagnostics and Interfaces pages and the uplink model are still IPv4. |
| A pinned catalogue goes stale between releases | Images are pinned by digest, so an install is reviewable — but an upstream security fix does not reach a router until someone pins it again and ships it. Install an app. |
Check it worked#
Work down your own list from outside the network:
# The management port
nc -vz 203.0.113.2 8443
# SSH
nc -vz 203.0.113.2 22
# Anything you did not mean to publish
nmap -Pn -p- 203.0.113.2Then from the local network, confirm you have not broken your own access. And sign in and read the Audit log tab of the System page: everything you just did should be in it, attributed to you.
Undoing it#
Each item is independent, and each is undone where it was made. The riskiest to reverse is item 2.
A management address that will not bind makes the agent fall back to the service's address, so the
web interface stays reachable. A management address that binds and cannot be reached from your
network — the loopback, or a network you are no longer on — shuts the web interface off for you,
and the fix is wheelhouse-rescue listen --reset at the console.