Skip to content

Auto-approve Tailscale exit-node routes from tag:homelab devices - #9

Merged
nickvigilante merged 1 commit into
mainfrom
tailscale-autoapprove-exit
May 16, 2026
Merged

Auto-approve Tailscale exit-node routes from tag:homelab devices#9
nickvigilante merged 1 commit into
mainfrom
tailscale-autoapprove-exit

Conversation

@nickvigilante

Copy link
Copy Markdown
Owner

Summary

  • Adds autoApprovers.exitNode = ["tag:homelab"] to the tailnet policy.
  • Effect: any homelab-tagged device that advertises itself as an exit node (e.g., pi-zero-exit re-running sudo tailscale up --advertise-exit-node) gets the 0.0.0.0/0 + ::/0 routes auto-approved. No admin-UI click required.
  • Tag-gated: only devices we've already vouched for via the auth-key + ansible flow can self-approve.

Why now

pi-zero-exit lost its exit-node advertisement at some point post-2026-05-12 (ExitNodeOption=False, Tags=[] per tailscale status --json on gandalf). Symptom on the user side: iOS Tailscale app's Exit Node selector is empty, blocking http://pi.hole/admin access from cellular. Re-running tailscale up on the device restores advertising; with this ACL change merged + applied, the restored route auto-approves instead of waiting for a human in the admin UI.

Test plan

  • tofu apply — Tailscale provider patches the tailnet policy.
  • On pi-zero-exit: sudo tailscale up --advertise-exit-node --advertise-tags=tag:homelab --accept-dns=false.
  • On gandalf: tailscale exit-node list lists pi-zero-exit as available with route accepted.
  • iOS Tailscale app shows pi-zero-exit in the Exit Node selector; selecting + browsing to http://pi.hole/admin loads Pi-hole admin.

Adds `autoApprovers.exitNode = ["tag:homelab"]` so any tagged
homelab device that re-runs `sudo tailscale up --advertise-exit-node`
is auto-approved as an exit node with no admin-UI click. Tag-gated
by design — only nodes we've already vouched for via the auth-key
+ ansible flow can self-approve exit routing.

Surfaces: pi-zero-exit lost its exit-node advertisement at some
point post-2026-05-12 (ExitNodeOption=False, Tags=[]). Re-running
the up command on the device will restore advertising; with this
ACL change in place, the 0.0.0.0/0 + ::/0 routes auto-approve
instead of waiting for a human in the admin UI.
@github-actions

Copy link
Copy Markdown

homelab tofu plan

Plan output
tailscale_dns_nameservers.global: Refreshing state... [id=34619e51-87ec-b5df-c078-fd2e3181d9c5]
tailscale_acl.main: Refreshing state... [id=acl]
tailscale_dns_preferences.main: Refreshing state... [id=ad9f64d5-f380-ae42-4811-7604bfdb5bcd]
data.github_user.self: Reading...
github_repository.managed["styleguide"]: Refreshing state... [id=styleguide]
github_repository.managed["homelab"]: Refreshing state... [id=homelab]
github_repository.managed["tools"]: Refreshing state... [id=tools]
github_repository.managed["passgen"]: Refreshing state... [id=passgen]
github_repository.managed["infrastructure"]: Refreshing state... [id=infrastructure]
github_repository.managed["puzzles"]: Refreshing state... [id=puzzles]
github_repository.managed["tuile"]: Refreshing state... [id=tuile]
github_repository.managed["docs"]: Refreshing state... [id=docs]
github_repository.managed["vale-languagetool"]: Refreshing state... [id=vale-languagetool]
github_repository.managed["dotfiles"]: Refreshing state... [id=dotfiles]
data.github_user.self: Read complete after 2s [id=13631471]
github_repository_environment.homelab_apply: Refreshing state... [id=infrastructure:homelab-apply]
github_repository_vulnerability_alerts.alerts["dotfiles"]: Refreshing state... [id=692319991]
github_repository_vulnerability_alerts.alerts["puzzles"]: Refreshing state... [id=1221351905]
github_repository_vulnerability_alerts.alerts["docs"]: Refreshing state... [id=1213739034]
github_actions_secret.github_app_installation_id: Refreshing state... [id=infrastructure:TF_GITHUB_APP_INSTALLATION_ID]
github_repository_vulnerability_alerts.alerts["styleguide"]: Refreshing state... [id=877056782]
github_actions_secret.aws_access_key_id: Refreshing state... [id=infrastructure:AWS_ACCESS_KEY_ID]
github_actions_secret.tailscale_oauth_client_secret: Refreshing state... [id=infrastructure:TAILSCALE_OAUTH_CLIENT_SECRET]
github_repository_vulnerability_alerts.alerts["infrastructure"]: Refreshing state... [id=1234973546]
github_repository_vulnerability_alerts.alerts["passgen"]: Refreshing state... [id=1007162400]
github_repository_vulnerability_alerts.alerts["tuile"]: Refreshing state... [id=1209980965]
github_repository_vulnerability_alerts.alerts["tools"]: Refreshing state... [id=177344790]
github_repository_vulnerability_alerts.alerts["homelab"]: Refreshing state... [id=1235102549]
github_actions_secret.github_app_id: Refreshing state... [id=infrastructure:TF_GITHUB_APP_ID]
github_actions_secret.tailscale_oauth_client_id: Refreshing state... [id=infrastructure:TAILSCALE_OAUTH_CLIENT_ID]
github_actions_secret.aws_secret_access_key: Refreshing state... [id=infrastructure:AWS_SECRET_ACCESS_KEY]
github_actions_secret.github_app_private_key: Refreshing state... [id=infrastructure:TF_GITHUB_APP_PRIVATE_KEY]
github_repository_vulnerability_alerts.alerts["vale-languagetool"]: Refreshing state... [id=874539266]
github_branch_protection.main["homelab"]: Refreshing state... [id=BPR_kwDOSZ4rVc4Ek1bm]
github_branch_protection.main["vale-languagetool"]: Refreshing state... [id=BPR_kwDONCBpAs4Ek1LO]
github_branch_protection.main["puzzles"]: Refreshing state... [id=BPR_kwDOSMxZ4c4Ek1Lo]
github_branch_protection.main["passgen"]: Refreshing state... [id=BPR_kwDOPAgUIM4Ek1Lp]
github_branch_protection.main["styleguide"]: Refreshing state... [id=BPR_kwDONEbTDs4Ek1Lj]
github_branch_protection.main["tools"]: Refreshing state... [id=BPR_kwDOCpIRFs4CjUwo]
github_branch_protection.main["docs"]: Refreshing state... [id=BPR_kwDOSFgwGs4Ek1Ll]
github_branch_protection.main["dotfiles"]: Refreshing state... [id=BPR_kwDOKUP2984EclBF]
github_branch_protection.main["infrastructure"]: Refreshing state... [id=BPR_kwDOSZwzas4Ek1LQ]
github_branch_protection.main["tuile"]: Refreshing state... [id=BPR_kwDOSB7YJc4Ek1LN]

OpenTofu used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
  ~ update in-place

OpenTofu will perform the following actions:

  # tailscale_acl.main will be updated in-place
  ~ resource "tailscale_acl" "main" {
      ~ acl = jsonencode(
          ~ {
              + autoApprovers = {
                  + exitNode = [
                      + "tag:homelab",
                    ]
                }
                # (4 unchanged attributes hidden)
            }
        )
        id  = "acl"
    }

Plan: 0 to add, 1 to change, 0 to destroy.

Warning: Deprecated attribute

  on github.tf line 32, in resource "github_repository" "managed":
  32:     ignore_changes = [vulnerability_alerts]

The attribute "vulnerability_alerts" is deprecated. Refer to the provider
documentation for details.

Warning: Argument is deprecated

  with github_actions_secret.tailscale_oauth_client_id,
  on github.tf line 85, in resource "github_actions_secret" "tailscale_oauth_client_id":
  85:   plaintext_value = var.tailscale_oauth_client_id

Use value.

(and 6 more similar warnings elsewhere)

─────────────────────────────────────────────────────────────────────────────

Saved the plan to: tfplan

To perform exactly these actions, run the following command to apply:
    tofu apply "tfplan"

@nickvigilante
nickvigilante merged commit 49545b2 into main May 16, 2026
1 check passed
@nickvigilante
nickvigilante deleted the tailscale-autoapprove-exit branch May 16, 2026 17:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant