Skip to content

Tailscale ACL owned by Terraform

Date: 2026-09-21 Status: Accepted Supersedes: hl-swfp Deciders: Sean Brandt

hl-swfp split tailnet ownership. The ACL content stayed with the gitops-acl-action workflow in fzymgc-house/tailscale-config, and tf/tailscale took everything else. The relocation stalled with two authoritative-looking copies of the policy.

Milestone v2026.09.20.01 finished it. tf/tailscale/acl.tf now owns the policy through one tailscale_acl resource reading tailscale/policy.hujson.

Terraform owns the tailnet policy content. The gitops-acl-action path is retired, its credential is revoked, and fzymgc-house/tailscale-config is archived.

Three claims in its rationale were measured during this milestone and all three are false.

tailscale_acl loses native sshTests execution.” The control plane runs tests and sshTests on every policy write, whoever writes it. Choosing the action retained nothing.

“and HuJSON ergonomics.” tailscale_acl carries the policy as one opaque types.String through PolicyFile().Raw() and Set(string). Nineteen comments survive in the live policy today. The ETag the control plane returns is the SHA-256 of the canonical bytes, so a post-apply value is computable offline before the write.

“the two systems never race to apply the same tailnet.” They raced. fzymgc-house/tailscale-config automerges Renovate digest bumps. Its pull request 38 merged unattended on 2026-07-24 and produced a push run seven seconds later that re-applied the policy. tailscale_acl Update sends an empty ETag and replaces the policy unconditionally, so whichever writer ran last won.

One writer. tf/tailscale/acl.tf decides what the tailnet runs, and any other holder of a policy_file credential is a second writer by the same mechanism that caused the race.

The admin console policy editor stays locked. acls_externally_managed_on is true and tf/tailscale/settings.tf owns it. Reopening it needs a merged pull request against tf/tailscale and a main-cluster-tailscale run. That procedure is not yet written down and has its own issue.

main-cluster-tailscale carries auto_apply = true and its trigger prefixes name both tf/tailscale and tailscale, so a change to the policy file alone starts a run and merging the pull request is the apply.