Give the Tailscale ACL step a working policy file #53

Merged
sulthan merged 2 commits from docs/tailscale-acl into main 2026-08-09 16:12:03 +07:00
Showing only changes of commit 35a254a86e - Show all commits
+74 -7
View File
@@ -309,18 +309,74 @@ refuses to start rather than guess.
**Narrow it to the one device that needs it.** The bind address keeps CDP off **Narrow it to the one device that needs it.** The bind address keeps CDP off
your LAN; it still leaves port 9222 open to every device on the tailnet, and your LAN; it still leaves port 9222 open to every device on the tailnet, and
CDP has no login. Add a rule in the Tailscale admin console's access controls CDP has no login — a compromised phone is enough to drive this host. A new
so only the VPS can reach it — tag the two machines, then: 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
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:
```jsonc ```jsonc
// tailnet policy file {
"tagOwners": {
// Empty list: implicitly owned by the tailnet Owner/Admins, which is you.
"tag:bookmark-api": [],
"tag:bookmark-browser": [],
},
"acls": [ "acls": [
{ "action": "accept", "src": ["tag:bookmark-api"], "dst": ["tag:bookmark-browser:9222"] }, // The only thing on the tailnet that may drive the browser.
] {
"action": "accept",
"src": ["tag:bookmark-api"],
"proto": "tcp",
"dst": ["tag:bookmark-browser: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.
{
"action": "accept",
"src": ["autogroup:member"],
"dst": ["autogroup:self:*", "tag:bookmark-api:*", "tag:bookmark-browser:22"],
},
],
// Saved policies are rejected if these fail, so the rule cannot rot silently.
"tests": [
{ "src": "tag:bookmark-api", "accept": ["tag:bookmark-browser:9222"] },
{
"src": "you@example.com",
"accept": ["tag:bookmark-browser:22"],
"deny": ["tag:bookmark-browser:9222"],
},
],
}
``` ```
Without a rule the tailnet default is allow-all, so this step is what makes Then apply the tags — on the VPS and the home machine respectively:
"Tailscale identity is the access control" true rather than aspirational.
```bash
sudo tailscale up --advertise-tags=tag:bookmark-api
sudo tailscale up --advertise-tags=tag:bookmark-browser
```
Each re-authenticates in a browser and issues a new node key; the tailnet IP is
unchanged, so `BROWSER_WS_URL` and `BROWSER_BIND_ADDR` still hold. Key expiry is
disabled once a device is tagged, which is what you want for a server — an
expired key would otherwise take the poller down every few months.
**Tagging replaces the device's user identity**, so do this only to machines
that exist to run these services. If your "home machine" is also your daily
driver, tag it anyway and reach it through the `:22` rule above, or skip the
tag and accept that any device of yours can reach CDP.
Enforcement is by the destination's packet filter, so the check below is real,
not advisory.
Prove the bind is tight, from the home machine itself: Prove the bind is tight, from the home machine itself:
@@ -334,6 +390,17 @@ The first call is also what wakes Chrome: it is not running until something
connects, and it is reaped again after five idle minutes. A cold first response connects, and it is reaped again after five idle minutes. A cold first response
takes a few seconds; that is the browser starting, not a fault. takes a few seconds; that is the browser starting, not a fault.
That check proves the *bind*, not the ACL — traffic that starts on the node is
not filtered. Prove the ACL from somewhere else: on your laptop or phone the
same URL must now time out, and from the VPS it must answer.
```bash
# on any other device of yours -> hangs until timeout
curl -s -m 5 http://<home machine tailnet IP>:9222/json/version
# on the VPS -> JSON
curl -s -m 20 http://<home machine tailnet IP>:9222/json/version
```
**On the VPS:** **On the VPS:**
```bash ```bash