// headscale ACL policy (HuJSON: JSON with // comments and trailing commas). // // headscale uses Tailscale's legacy "acls" format (NOT the "grants" syntax // from the Tailscale admin console) and is stricter: a "tagOwner" must be a // user (has "@"), a "group:*", or a "tag:*" -- never an autogroup. // // DEFAULT = allow everything (the first acl below). To lock down, comment out // that one line and the tighter rules (your Tailscale default, translated) // take over. ACLs are additive, so while allow-all is present it wins and the // other rules are inert -- which is why they're safe to leave in place. // // After ANY edit, validate before applying: // headscale policy check // cd /srv/headscale && docker compose restart headscale { // Admin users (OIDC emails from pocket-id). Required only if you enable the // tag:shared owner below. Empty is fine while allow-all is active. "groups": { // "group:admins": ["alice@example.com", "bob@example.com"], }, // Tag owners MUST be a user / "group:*" / "tag:*" (not an autogroup). // Uncomment together with group:admins above when you lock down. "tagOwners": { // "tag:shared": ["group:admins"], }, "acls": [ // ===== DEFAULT: allow everything. Comment out this line to lock down. ===== { "action": "accept", "src": ["*"], "dst": ["*:*"] }, // ===== Your Tailscale default, translated (active once allow-all is off) ===== // Each user can reach their own devices (autogroup:self is experimental // in headscale -- validate with `headscale policy check`): // { "action": "accept", "src": ["autogroup:member"], "dst": ["autogroup:self:*"] }, // Anyone can reach devices tagged tag:shared (needs the tagOwner above): // { "action": "accept", "src": ["*"], "dst": ["tag:shared:*"] }, ], // Tailscale SSH (nodes must run `tailscale up --ssh`). Uncomment to allow // SSH into your own devices. "check" = periodic browser re-auth. "ssh": [ // { "action": "check", // "src": ["autogroup:member"], // "dst": ["autogroup:self"], // "users": ["autogroup:nonroot", "root"] }, ], }