Chapter 10 · Operate
Threat model & key custody
Security advice given without a threat model is just superstition with a command line. This chapter states plainly who you are defending against, which controls actually stop them, and — the part usually left out — what this design does not protect you from.
Who is actually attacking you
| Adversary | Likelihood | What stops them |
|---|---|---|
| Automated internet scanners botnets sweeping for open SSH |
Constant. Minutes after a VPS gets an IP. | Fully solved. Tailnet-only binding means there is no port to find. This is the single highest-value control in the guide. |
| Hostile local network café, hotel, conference Wi-Fi |
Every time you travel. | Solved. WireGuard encrypts everything; default-deny inbound means your laptop offers nothing to the segment it joined. |
| Lost or stolen device | Plausible. Phones and laptops go missing. | Strongly mitigated. Full-disk encryption at rest, Secure Enclave keys that cannot be extracted, and one-click revocation in the tailnet. |
| Malware on a device you own | Possible. A bad dependency, a malicious VS Code extension. | Partially. Secure Enclave keys cannot be stolen — but malware can still use the key while you are unlocked. Assume a compromised endpoint means compromised access. |
| Compromise of Tailscale itself | Low, high impact. | Solvable. Tailnet lock removes the coordination server from your trust model — it can no longer insert a node into your network. |
| Targeted attacker with resources | Only you know. | Not solved, and no consumer setup solves it. Raise the cost; do not pretend to eliminate the risk. |
Layers, and what each one actually stops
The value of defence in depth is that each layer fails differently. A misconfigured
sshd_config does not matter if the packet never arrives; a tailnet ACL mistake does
not matter if the key is not in authorized_keys.
The sandbox has a hostile machine you can add to the topology, and each attack it runs tells you which layer stopped it rather than merely that something did. A stolen node key dies at tailnet lock; a legitimately-joined machine dies at the ACL; a port scan dies at the firewall. Those are three different defences, and knowing which one is carrying you is the whole point of this chapter.
- An internet scannerConstant, indiscriminate, and arriving within minutes of any VPS getting an address. It has never heard of you; it is trying everyone. This is the most common adversary by several orders of magnitude, and the one the outermost ring exists for.
- A neighbour on the café Wi-FiYou joined a hotel network and so did they. They can reach your machine's local address directly — no tailnet, no coordination server, nothing for ring 1 to have an opinion about. This is why ring 1 alone is not enough.
- A bad ACL editYou widened one rule late at night and a device you never meant to allow now matches it. Nothing is broken yet and nothing warns you. Ring 1 is the ring that eventually breaks — the question is what happens next.
- Your phone, in someone else's pocketIt is still a member of the tailnet, because you have not revoked it yet. It holds an SSH key that every one of your machines trusts. And it is locked. Watch how far it gets.
- You, with your keyThe one case every ring is built to let through. Worth stepping once, because it is the only run where you see all five rings answer yes — and it shows the cost of the design is a single
ssh. - 1 · Stopped at the tailnet ACLThere is no port to find. sshd is bound to the tailnet address and nothing else, so the scan hits a filtered port and moves on. This is the single highest-value control in the guide — it removes an entire adversary class instead of hardening against it.
- 1 · Bypassed, not defeatedThe rings are not literally concentric for every attacker. This packet is addressed to your Wi-Fi interface, so the tailnet ACL is never consulted at all. A control that is not on the path cannot help you — which is the whole argument for the ring below.
- 1 · Passed — and it should not haveThe grant was too broad, so a device you never intended to allow now reaches port 22. Ring 1 has failed silently, exactly the way misconfiguration always does. If it were your only layer, this would already be an incident.
- 1 · On the tailnet, port grantedThe device is a member and the ACL permits it to reach port 22, so the packet is delivered. Correct in both cases — the phone is yours, and you have not revoked it yet.
- 2 · Stopped at the bind addresssshd listens on the
100.xaddress only, so on the hotel segment there is simply nothing to answer. Ring 1 never applied here; ring 2 did the work alone. Default-deny inbound means your laptop offers that network nothing. - 2 · Reached the right interfaceThe packet arrived over the tailnet interface, which is precisely the one sshd is bound to. Nothing here looks suspicious yet — a device that slipped past ring 1 is indistinguishable from a legitimate one at this layer.
- 2 · Not runRing 1 already ended it. Nothing inside was ever tested, which is the point: a control that stops traffic before it arrives makes every control behind it irrelevant to that attacker.
- 3 · The daemon says no — and this is the payoffPassword authentication is off, root login is off, and this account is not in
AllowUsers. Ring 1 failed, and the result was an inconvenience rather than an incident. This is why you harden sshd even behind a private network. - 3 · Policy permits this loginKey authentication only, no root, and the account is on the allow list. The daemon has no objection to this attempt — it is a legitimate user account presenting itself the approved way.
- 3 · Not runThe packet never got this far, so
sshd_configplayed no part in the outcome. Worth remembering when you are tempted to debug a connection problem by editing it. - 4 · The enclave will not signThe key is hardware-bound and every use is gated by Face ID or the passcode. A thief has the device, not the key — and cannot copy it out to try elsewhere. This is what makes losing a phone a chore rather than a crisis.
- 4 · Your key signs the challengeThe private key never leaves the enclave; the enclave returns a signature over the session ID. Note the honest limit: if malware were running on your unlocked machine, it could ask for that same signature. Possession of the device is the boundary.
- 4 · Not runNo key was ever offered here, because an outer ring answered first. Key custody is a strong layer, but it only gets a turn if the packet reaches it.
- 5 · On the SSH access list, and into the shellPAM checks
com.apple.access_sshand lets you through. Five rings, five yeses, one shell. Each of them fails differently, which is the entire value of the arrangement — the failure of any one is survivable. - 5 · Not runAn outer ring already stopped this attempt, so the shell was never in reach. Switch the attacker above and watch where the run ends — no two of them stop at the same ring.
Where every key lives
Knowing this table cold is what makes an incident boring instead of frightening.
| Key | Lives | Extractable? | If the device is lost |
|---|---|---|---|
| iPhone SSH key | Secure Enclave | No — hardware-bound | Remove one authorized_keys line per host; revoke the node. Nothing to rotate elsewhere. |
| Mac / Ubuntu SSH key | ~/.ssh/id_ed25519, passphrase-encrypted | Yes, with the passphrase | Assume compromised. Rotate it everywhere, promptly. |
| Tailscale node key | Per-device, generated locally | No | Revoke the device in the admin console. Immediate and global. |
| SSH host keys | /etc/ssh/ssh_host_* on each server | Root can read them | Server compromise means regenerating them, and every client will warn on next connect. |
| FileVault / LUKS key | Derived from your passphrase | No | This is what makes a stolen disk useless. Store the recovery key off the machine. |
- Day to day, they are indistinguishableBoth keys log you in. The difference only shows up on the worst day of the year, which is exactly why it is worth understanding before then. The file key hands its bytes to whatever opens the file; the enclave hands back a signature and keeps the key.
- 1 · The device is goneThe laptop leaves with the key file on its disk. FileVault protects that file while the machine is powered off — and only then; a sleeping machine has already derived the volume key. The phone leaves with its key too, but sealed in a coprocessor that has no read operation.
- 1 · Something is running that should not beA bad dependency or a malicious extension. On the file side, one
open()is the entire attack — no prompt, no trace. On the enclave side it cannot read the key, but here is the honest limit: it can ask the enclave to sign while you are unlocked. Hardware-bound is not the same as safe from a compromised endpoint. - 1 · A backup ran, unencryptedTime Machine to an unencrypted disk, a
tarof your home directory, a sync tool with generous defaults. The file key is now inside that archive. The enclave key was never a file, so there was nothing for the backup to pick up. - 2 · What they end up holdingThe disk holds the key file, so you have to assume it becomes readable eventually — a stolen laptop is not a one-hour problem. The enclave holds the phone's key, and every use of it is gated by Face ID or the passcode. It cannot be copied off the chip at any price.
- 2 · What they end up holdingOn the file side, the key itself: usable from any machine, at any time, and you cannot tell it happened. On the enclave side, no key at all — only access, bounded to that one device and to the time it stays unlocked. That bound is the difference between an incident and a permanent one.
- 2 · What they end up holdingThe key file is in the archive, protected only by its passphrase — and a passphrase in someone else's hands brute-forces offline, patiently, without rate limits. The enclave key is simply not in there. Nothing to crack now, and nothing to crack in five years.
- 3 · What it costs youFile key: rotate everywhere, promptly, on every host and service that trusts it — under time pressure, on a bad day. Enclave key: revoke the node in the admin console, then delete one
authorized_keysline per host. Nothing else to rotate, because the key existed nowhere else. - 3 · What it costs youFile key: rotate everywhere, then audit what was done with it in the meantime. Enclave key: revoke the node and audit the same window — but you do not rotate keys on your other machines, because this one never left the phone. A compromised endpoint is still a compromised endpoint; the blast radius is what changes.
- 3 · What it costs youFile key: rotate everywhere, because you do not know who read that archive or how many copies of it exist. Enclave key: nothing at all. The backup was always safe to store, and that is a property you get for free rather than by remembering to be careful.
Blast radius
For each thing that could go wrong, what does the attacker actually get?
| Compromised | They get | They do not get |
|---|---|---|
| iPhone, locked | Nothing. The enclave requires Face ID or passcode. | Any access at all. |
| iPhone, unlocked | Shell on every machine, until you revoke. | The key itself — so revocation is complete and final. |
| Mac, powered off | Nothing. FileVault. | Keys, sessions, history. |
| Mac, unlocked and malware-infected | Everything that machine can reach, plus its SSH key. | Your phone's key, your VPS disk at rest. |
| VPS root | The VPS, its data, its host keys, its tailnet membership. | Your laptops — unless you forwarded an agent, which is why forwarding is off. |
| Tailscale account | Ability to add a device — unless tailnet lock is on. | Traffic, which is end-to-end encrypted. |
If you SSH into a compromised server with -A, root on that box can use your
forwarded agent to authenticate as you, anywhere your key is trusted, silently, for as long
as you stay connected. That converts "one server was compromised" into "everything was."
Use ProxyJump instead — the keys stay on your machine.
What this design does not protect against
Stated plainly, because a threat model that claims to cover everything is not a threat model:
- A compromised endpoint you are actively using. Malware on your unlocked Mac acts as you. No network design fixes this.
- Malicious code you run yourself. A backdoored dependency executes with your privileges. This is the most probable serious compromise for most developers, and nothing in this guide addresses it.
- Coercion. Encryption yields to a court order or a threat.
- Your cloud provider. They control the hypervisor. Disk encryption on a VPS protects against decommissioned-disk recovery, not against the host.
- Metadata. Your ISP cannot read WireGuard traffic but sees that you connect and how much.
Incident response
Phone lost or stolen
- Tailscale admin console → remove the device. It loses network access immediately; this is the fastest and broadest action, so do it first.
- Find My → Mark as Lost, and Erase if you are confident it is gone.
- Remove its line from
~/.ssh/authorized_keyson every machine. - No further rotation needed — the Secure Enclave key never existed anywhere else.
A machine's SSH key may have leaked
- Remove the public key from every
authorized_keysimmediately. Revocation first, replacement second. - Generate a fresh key on that device and distribute the new public half.
- Check
~/.ssh/authorized_keyson every host for entries you do not recognise — adding a key is the classic persistence move. - Review authentication logs for the fingerprints that actually logged in:
grep 'Accepted publickey' /var/log/auth.log. This is whatLogLevel VERBOSEwas for.
A server may be compromised
- Isolate it:
sudo tailscale down, and remove the node from the tailnet. - Do not trust anything it tells you. A rootkit edits the logs and the tools that read them.
- Snapshot the disk for later analysis if it matters.
- Rebuild rather than clean. Restore data, not binaries. Regenerate host keys; clients will warn, and that warning is correct.
- Rotate every credential that machine held.
Review periodically
- Every entry in every
authorized_keysis one you recognise and still want. - Tailnet device list contains no machines you have retired.
- Key expiry is on where appropriate; you know where you disabled it and why.
- Tailnet lock is enabled and recovery keys are reachable from another device.
- FileVault and LUKS recovery keys are stored off the machines they unlock.
- ACL policy still reflects reality after any new device joined.
- Auth logs reviewed for unfamiliar key fingerprints.
The commands in this guide change firewall and login settings, and can lock you out of a machine. Practise on something disposable first. Everything here is provided as is, with no warranty — you accept the risk of running it. Read the disclaimer.