Tailscale ACL owned by Terraform
Date: 2026-09-21 Status: Accepted Supersedes: hl-swfp Deciders: Sean Brandt
Context
Section titled “Context”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.
Decision
Section titled “Decision”Terraform owns the tailnet policy content. The gitops-acl-action path is
retired, its credential is revoked, and fzymgc-house/tailscale-config is
archived.
What hl-swfp got wrong
Section titled “What hl-swfp got wrong”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.
Consequences
Section titled “Consequences”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.