# TAILSCALE ACL: ONE RULE, FOR JOSEPH TO CLICK Written 28 July 2026. This is the only open item in Track H item 1 that cannot be done from a shell, because granting a tailnet policy requires the account owner. ## WHAT IS BROKEN, precisely The control plane backup runs on megamind-agi, because Bubbles has pg_dump 17.10 and the control plane is PostgreSQL 18.4, and pg_dump refuses a server newer than itself. So megamind-agi dumps, encrypts, and pushes the artifact to Bubbles. That push is supposed to go over the tailnet. It does not. Tailscale SSH is enabled on Bubbles and the policy refuses megamind-agi: tailnet policy does not permit you to SSH to this node So `/usr/local/sbin/mm-cp-backup.sh` on megamind-agi uses the LAN address instead: DEST_HOST="user@192.168.1.132" It reaches the same host, the same restricted key, and the same spool directory, so **the backups are working today**. This is not an outage. It is a dependency on both machines sitting on one physical LAN, which is exactly the property the off machine backup was supposed to stop relying on. ## THE FACTS YOU WILL NEED IN THE CONSOLE tailnet taildf8f6b.ts.net source node megamind-agi-wsl 100.77.114.40 (the WSL2 instance, NOT the Windows host at 100.69.120.61, which is a separate tailnet node) destination node bubbles 100.90.97.104 destination user user what it needs ssh, and nothing else **The source is the WSL node, not the Windows one.** megamind-agi appears twice in your tailnet. The backup job runs inside WSL2, which has its own tailscaled and its own address. An ACL written against the Windows host would look correct in the console and change nothing. ## THE RULE Add ONE entry to the `ssh` block of the tailnet policy file. This is the whole change: ```json { "action": "accept", "src": ["tag:mm-backup-source"], "dst": ["tag:mm-backup-dest"], "users": ["user"] } ``` with the two tags declared and assigned: ```json "tagOwners": { "tag:mm-backup-source": ["autogroup:admin"], "tag:mm-backup-dest": ["autogroup:admin"] } ``` Tags rather than raw addresses on purpose: an address in a policy file rots the moment a machine is rebuilt, and this tailnet has already produced one such trap in the two identities of megamind-agi. If you would rather not deal with tags, the equivalent by user is: ```json { "action": "accept", "src": ["joseph.w.anady@"], "dst": ["joseph.w.anady@"], "users": ["user"] } ``` That is broader than needed. It permits any of your machines to SSH to any other as `user`. On a five node personal tailnet that is a reasonable trade; on a tailnet that later carries a client machine it is not. The tagged version is the one to prefer. ## CLICK BY CLICK 1. Go to **https://login.tailscale.com/admin/acls** and sign in as joseph.w.anady@. 2. You land on the **Access controls** tab with the policy file in an editor. 3. If a `"tagOwners"` block already exists, add the two `tag:mm-backup-*` lines inside it. If it does not, paste the whole `tagOwners` block above at the top level, beside `"acls"`. 4. Find the `"ssh"` block. If there is none, add `"ssh": [ ... ]` at the top level and put the rule inside it. If there is one, add the rule as another entry in its list. 5. Click **Preview** first. It shows which nodes the change affects. Confirm it names bubbles and megamind-agi-wsl and nothing you did not expect. 6. Click **Save**. 7. Go to the **Machines** tab. Find **megamind-agi-wsl** (address 100.77.114.40, not the Windows one). Open its row menu, choose **Edit ACL tags**, tick **tag:mm-backup-source**, save. 8. Do the same for **bubbles**, ticking **tag:mm-backup-dest**. A machine that gets a tag loses its user ownership for ACL purposes. That is normal and is why the tags are assigned last, after the rule that uses them exists. ## HOW TO PROVE IT WORKED, and how to prove the test is real From megamind-agi inside WSL: tailscale ssh user@bubbles true ; echo "exit=$?" Expect `exit=0`. Before the change it prints the policy refusal and a nonzero exit. **Then the positive control, which matters more than the success:** the rule must permit exactly this and not everything. From the same shell: tailscale ssh user@batman true ; echo "exit=$?" Expect a REFUSAL. If that also succeeds, the rule is broader than intended and the tags were not applied, because a rule scoped to two tags cannot reach a third machine. A grant that permits everything looks identical to a grant that works until the day it matters. ## AFTER IT IS GRANTED Tell me, and I will change one line in `/usr/local/sbin/mm-cp-backup.sh` on megamind-agi: DEST_HOST="user@192.168.1.132" -> DEST_HOST="user@bubbles" then run the job and confirm today's control plane dump lands in the set. That is a one line change to a backup script on a node outside the production API path, and I will still bring it to you before running it, because Track H deploys are approved individually. ## IF YOU WOULD RATHER NOT Reply **skip Tailscale** and I will record KEEP LAN as the decision and close the item. It is a legitimate choice: the LAN path works, both machines live in one building anyway, and the honest statement of the residual risk is one sentence. The risk does not change by being written in a policy file; only the coupling does.