Standardize Tailscale node auth on TF-minted OAuth clients (not static keys)
Date: 2026-07-05 Status: Accepted Decision: hl-imbj Deciders: Sean Brandt
Context
Section titled “Context”Tailscale nodes (the Firewalla router now, the NAS in the sibling spec) previously authenticated via a SOPS-encrypted static auth key (router) or a catalog-app auth key (NAS). Adopting Vault-native, Terraform-minted credentials required choosing the specific credential mechanism the containers use — a choice that forks the container packaging model itself.
Decision
Section titled “Decision”All Tailscale node/app auth moves to TF-minted, Vault-stored OAuth clients (tailscale_oauth_client, scoped auth_keys + the node’s tags), consumed by the containers via TS_CLIENT_ID/TS_CLIENT_SECRET (not TS_AUTHKEY). Because those env vars are mutually exclusive with TS_AUTHKEY and require full container-env control, the NAS Tailscale app converts from a TrueNAS catalog app to a custom (compose-owned) app; the router is already a custom compose. Each per-node OAuth client MUST carry every tag the node holds (nas: tag:nas+tag:core; router: tag:router+tag:ns+tag:security).
Rationale
Section titled “Rationale”- Corrects an initial design error: an OAuth client secret cannot be placed in
TS_AUTHKEY;containerboottreatsTS_AUTHKEYandTS_CLIENT_ID/TS_CLIENT_SECRETas mutually exclusive and performs the key exchange internally (verified via deepwikitailscale/tailscale). - Unifies every node credential onto the Vault-sourced pattern, eliminating the last hand-managed/SOPS tailscale secret.
- OAuth client secrets do not expire, giving a no-rotation credential for headless/rebuild scenarios.
Alternatives Considered
Section titled “Alternatives Considered”- TF-minted OAuth client via
TS_CLIENT_ID/TS_CLIENT_SECRET(chosen): non-expiring, narrow per-purpose scope, Vault-stored. Cost: no rotation backstop; forces catalog->custom conversion for the NAS. - Reusable Tailscale tailnet key (
tailscale_tailnet_key->TS_AUTHKEY) (rejected): works with the catalog app unmodified, but expires (<=90d) requiring rotation, and the NAS catalog app only cleanly exposesauth_key. - SOPS static auth key (status quo, rejected): hand-managed outside Vault, no per-purpose scoping — the last non-GitOps tailscale credential.
Consequences
Section titled “Consequences”- Positive: no static keys/SOPS remain for tailscale auth; consistent per-purpose scoping; non-expiring credential.
- Negative: non-expiring secrets have no rotation backstop (mitigated by narrow scope + per-client revocation); forces catalog->custom app conversion; per-node clients must enumerate all node tags or a rebuild silently drops them (
tag:nsis a load-bearing DNS:53ACL dst). - Neutral: the NAS node re-registers with a new identity during cutover;
autoApprovers+ router route-redundancy absorb the gap.