Write the Tailscale policy in grants, not acls (#46)

The console ships a grants-based default policy, so an acls example forces the
operator to translate before they can use it - and the two differ in exactly
the place that matters: grants carry ports in `ip` (`tcp:9222`) rather than
suffixed onto `dst`.

Adds the three things dropping the blanket grant silently takes away: an ssh
block, since tagged devices leave `autogroup:self` and Tailscale SSH stops
resolving them; a commented `autogroup:internet` grant for exit-node users;
and `tcp:22` to the browser machine, without which tagging locks you out.
This commit is contained in:
2026-08-09 16:05:13 +07:00
parent 35a254a86e
commit 240edfb677
+33 -12
View File
@@ -314,12 +314,13 @@ tailnet's policy is allow-all, so this is the step that makes "Tailscale
identity is the access control" true rather than aspirational.
Tailscale has no `deny`, so a restriction is expressed by removing the blanket
`accept` and enumerating what is left. That only works if the browser machine
can be *excluded* from a selector that still covers your own devices — which is
grant and enumerating what is left. That only works if the browser machine can
be *excluded* from a selector that still covers your own devices — which is
what tagging buys: a tagged device has no user, so `autogroup:member` and
`autogroup:self` stop matching it. Tagging is the mechanism, not decoration.
In the admin console, under **Access controls**, replace the default rule:
In the admin console, under **Access controls**, the shipped policy grants
`{"src": ["*"], "dst": ["*"], "ip": ["*"]}`. Replace it:
```jsonc
{
@@ -329,24 +330,44 @@ In the admin console, under **Access controls**, replace the default rule:
"tag:bookmark-browser": [],
},
"acls": [
"grants": [
// The only thing on the tailnet that may drive the browser.
{
"action": "accept",
"src": ["tag:bookmark-api"],
"proto": "tcp",
"dst": ["tag:bookmark-browser:9222"],
"dst": ["tag:bookmark-browser"],
"ip": ["tcp:9222"],
},
// Your own devices: everything of yours, both servers — but not CDP.
// Drop the `:22` and you have locked yourself out of the browser machine.
// Your own devices reach your own devices, and the VPS, in full.
{
"action": "accept",
"src": ["autogroup:member"],
"dst": ["autogroup:self:*", "tag:bookmark-api:*", "tag:bookmark-browser:22"],
"dst": ["autogroup:self", "tag:bookmark-api"],
"ip": ["*"],
},
// On the browser machine you get SSH and nothing else. Widen this to `*`
// and the restriction above is void; delete it and you are locked out.
{
"src": ["autogroup:member"],
"dst": ["tag:bookmark-browser"],
"ip": ["tcp:22"],
},
// Uncomment if you route traffic through an exit node — dropping the
// blanket grant takes exit-node access with it.
// {"src": ["autogroup:member"], "dst": ["autogroup:internet"], "ip": ["*"]},
],
// Tagged devices left `autogroup:self`, so Tailscale SSH needs them named.
// Irrelevant if you reach these boxes with ordinary sshd over the tailnet —
// that is the `tcp:22` grant above.
"ssh": [
{
"action": "check",
"src": ["autogroup:member"],
"dst": ["autogroup:self", "tag:bookmark-api", "tag:bookmark-browser"],
"users": ["autogroup:nonroot", "root"],
},
],
// Saved policies are rejected if these fail, so the rule cannot rot silently.
// Run on every save, so a later edit that reopens 9222 is rejected outright.
"tests": [
{ "src": "tag:bookmark-api", "accept": ["tag:bookmark-browser:9222"] },
{