Files
imladris/docs/tailscale-acl-proposed.hujson
kyle ad7e5b6f42 docs: proposed tagged tailnet ACL (Phase 2 draft, review only)
server/cloud/mobile/iot tags, funnel scoped to servers+cloud, mobile
limited to HA/Mattermost/Syncthing/DSM, pre-allowed agent+metrics flows,
policy tests included.
2026-11-01 14:51:10 -08:00

131 lines
5.0 KiB
Plaintext

// PROPOSED tailnet policy — Phase 2 of docs/security-plan.md. DRAFT for
// Kyle's review, 2026-11-01. NOT LIVE. Baseline it replaces:
// docs/tailscale-acl.hujson.
//
// Design: single-user tailnet. Kyle's interactive machines stay UNTAGGED
// (they speak as isomk@ → full access via autogroup:members). Tags exist to
// constrain the devices that are NOT him-at-a-keyboard: phones/TV, IoT, and
// the server fleet. Tagging a device removes its user identity, which is
// exactly the point.
//
// Tag assignments (apply on the Machines page before saving this policy):
// tag:server (home) : rift, orion, dixie, butler, sk, talos
// tag:cloud (public-facing remote): phobos, deimos (Hetzner colo, web),
// fs (devlogs/primers, remote dev), svc
// tag:mobile : iphone-15-pro, ipad-pro-11-gen-3, pixel-8a, apple-tv
// tag:iot : homeassistant, synology
// UNtagged (full access, by design): straylight, titan, callisto, io,
// wntrmute (agent host).
//
// Known flows pre-allowed (expect a first-week breakage pass anyway):
// - agents on tagged servers → Mattermost on straylight (serve 443)
// - orion VictoriaMetrics → node exporters (*:9100, *:8420)
// - server agents → wntrmute Hermes peer API (:8642)
// - Syncthing mesh on :22000 for every device incl. tagged
//
// Unresolved: fs also ADVERTISES exit node — deliberate? (dst
// autogroup:internet below covers use; drop the advertisement if unwanted.)
{
"tagOwners": {
"tag:server": ["autogroup:admin"],
"tag:cloud": ["autogroup:admin"],
"tag:mobile": ["autogroup:admin"],
"tag:iot": ["autogroup:admin"],
},
"acls": [
// Kyle's own (untagged) devices: full access. NOTE: autogroup:members
// = every tailnet member; safe only while this stays a single-user
// tailnet. Revisit if anyone is ever invited.
{"action": "accept", "src": ["autogroup:members"], "dst": ["*:*"]},
// Exit-node egress for user devices (fs currently advertises).
{"action": "accept", "src": ["autogroup:members"], "dst": ["autogroup:internet:*"]},
// Syncthing mesh: every device, both directions, one port.
{"action": "accept", "src": ["*"], "dst": ["*:22000"]},
// Server ↔ server ↔ cloud: agents, metrics, compose, DBs.
{"action": "accept", "src": ["tag:server", "tag:cloud"],
"dst": ["tag:server:*", "tag:cloud:*"]},
// Tagged servers reach the untagged hub hosts only on specific flows:
// Mattermost (straylight serve 443 + direct 8065), exporters anywhere.
{"action": "accept", "src": ["tag:server", "tag:cloud"],
"dst": [
"straylight.scylla-hammerhead.ts.net:443",
"straylight.scylla-hammerhead.ts.net:8065",
]},
{"action": "accept", "src": ["tag:server"], "dst": ["*:9100", "*:8420"]},
// Server agents → wntrmute Hermes peer API only (Kyle: outbound +
// maybe inbound peer API).
{"action": "accept", "src": ["tag:server"],
"dst": ["wntrmute.scylla-hammerhead.ts.net:8642"]},
// Phones / tablet / TV: HA is a must; Mattermost, Syncthing, and DSM
// nice-to-have. Nothing else — no SSH, no DBs, no exporters.
{"action": "accept", "src": ["tag:mobile"],
"dst": [
"homeassistant.scylla-hammerhead.ts.net:8123",
"straylight.scylla-hammerhead.ts.net:443",
"synology.scylla-hammerhead.ts.net:5000",
"synology.scylla-hammerhead.ts.net:5001",
]},
// (Syncthing for mobile arrives via the *:22000 mesh rule above.)
// tag:iot initiates nothing except the Syncthing mesh above.
],
// Tailscale SSH: unchanged default (members → own devices). Once P1
// adopts --ssh fleet-wide, consider narrowing dst to tag:server and
// adding a checkPeriod so sessions re-auth instead of persisting.
"ssh": [
{
"action": "accept",
"src": ["autogroup:members"],
"dst": ["autogroup:self"],
"users": ["autogroup:nonroot", "root"],
},
],
"nodeAttrs": [
// Funnel: servers and cloud boxes may publish; user desktops,
// phones, and IoT may not. (straylight is untagged by design and so
// loses Funnel eligibility — it runs serve, tailnet-only, which
// needs no attr.)
{
"target": ["tag:server", "tag:cloud"],
"attr": ["funnel"],
},
],
// Run on every save; the console executes these before accepting.
"tests": [
{
"src": "tag:mobile",
"accept": ["homeassistant.scylla-hammerhead.ts.net:8123",
"straylight.scylla-hammerhead.ts.net:443",
"100.95.252.120:22000"],
"deny": ["rift.scylla-hammerhead.ts.net:22",
"rift.scylla-hammerhead.ts.net:8000",
"dixie.scylla-hammerhead.ts.net:5432",
"orion.scylla-hammerhead.ts.net:9100"],
},
{
"src": "tag:server",
"accept": ["dixie.scylla-hammerhead.ts.net:11434",
"rift.scylla-hammerhead.ts.net:8000",
"straylight.scylla-hammerhead.ts.net:443",
"wntrmute.scylla-hammerhead.ts.net:8642"],
"deny": ["titan.scylla-hammerhead.ts.net:22"],
},
{
"src": "tag:iot",
"accept": ["synology.scylla-hammerhead.ts.net:22000"],
"deny": ["rift.scylla-hammerhead.ts.net:22"],
},
],
}