Cheat sheet — Device Trust & Posture (WireGuard / Tailscale / headscale)¶
Companion to Module 03 — Device Trust & Posture · CC BY 4.0 — print it, pin it, share it.
Last reviewed: 2026-07
WireGuard basics¶
wg genkey | tee privatekey | wg pubkey > publickey # keypair (the pubkey IS the device identity)
wg show # peers, latest handshake, transfer, endpoints
wg show wg0 latest-handshakes # last handshake per peer (recent = alive)
wg-quick up wg0 # bring interface up from /etc/wireguard/wg0.conf
wg-quick down wg0 # tear it down
ip -brief addr show wg0 # confirm the tunnel address is assigned
WireGuard crypto is fixed by design (Noise, ChaCha20-Poly1305, Curve25519) — no algorithm to negotiate, nothing to downgrade. A peer is only a 256-bit public key.
headscale — self-hosted control server¶
headscale users create corp # a namespace/user to own nodes
headscale users list
# enroll a node: on the client, tailscale up points at headscale
tailscale up --login-server http://headscale.local:8080
headscale nodes register --user corp --key <nodekey> # approve the node from the server side
headscale nodes list # enrolled devices, tags, IPs, online state
headscale nodes tag -i <id> -t tag:corp-managed # tag a node so ACLs can target it
headscale routes list # advertised subnet routes
Tailscale client¶
tailscale up --login-server http://headscale.local:8080 --advertise-tags=tag:corp-managed
tailscale status # peers, tags, connection type (direct vs relay)
tailscale ping <peer> # test reachability + path
tailscale netcheck # NAT / DERP diagnostics
tailscale ip -4 # this node's tailnet IP
ACL policy (HuJSON — HCL/JSON with comments + trailing commas)¶
{
// DEFAULT-DENY: with tagOwners + acls present, anything NOT listed is denied.
"tagOwners": {
"tag:corp-managed": ["corp"],
"tag:target": ["corp"],
},
"acls": [
// only corp-managed devices may reach the target service on 443
{ "action": "accept", "src": ["tag:corp-managed"], "dst": ["tag:target:443"] },
],
// NO wildcard rule like {"src":["*"],"dst":["*:*"]} — that is the implicit default-allow trap.
}
Prove the deny (the only honest test)¶
# from an ENROLLED, tagged device — should SUCCEED
curl -sS --max-time 5 http://target.tailnet:443/ && echo ALLOWED
# from a container with NO registered keypair — should FAIL (no route, no reach)
curl -sS --max-time 5 http://target.tailnet:443/ ; echo "exit=$?" # non-200 / timeout = denied
Gotchas worth remembering¶
- WireGuard is not identity-aware by itself. It authenticates a key, not a user or a device's health. A mesh where every enrolled device reaches everything is just a flatter network with better crypto — you moved the perimeter, you didn't remove it. The ACL is what makes it Zero Trust.
- Prove the deny, not just the allow. A syntax check passes an ACL with an implicit default-allow.
The only honest verification is confirming an unenrolled/untagged device is refused — construct a
curlfrom a container with no keypair. - Bind app AND scope, not a bare tag everywhere. A rule that allows a tag with no destination
restriction is a hole; scope
dstto the specific service and port. - Posture is assessed, not demonstrated, when self-hosted. Patch level, EDR state, and disk encryption come from an MDM/EDR stack you can't run for free — the lab maps a structured policy to production controls and labels it honestly. Don't claim you proved posture when you only asserted it.
- Control plane ≠ data plane. headscale distributes keys and ACLs; once keys are exchanged, traffic flows peer-to-peer. headscale being down doesn't kill existing tunnels, but it does stop new enrollments and ACL updates.
- Store the private key in a TPM/Secure Enclave in production so it can't be exported. A key sitting in a world-readable file is a device identity anyone can steal.
Comments
Sign in with GitHub to comment. Choose the type: Feedback (errors or suggestions on this page) · Hints (help for fellow learners — no spoilers) · General (anything else).