Rotate the Tailscale OAuth client
Tailscale cannot change the scopes of a client that already exists, so widening the fleet’s client — from auth_keys alone to auth_keys + devices:core, or onto policy_file so hermetic can keep the tailnet policy current — is always a new client plus a rotation.
-
In the admin console, create a new OAuth client with every scope: Auth Keys → Write and Devices → Core → Read, Write, each tagged
tag:hermetic, plus Policy File → Write (tailnet-wide; Tailscale attaches Devices → Posture Attributes and Devices → Core → Read to it). Copy the secret; Tailscale shows it once. -
Push it. The value comes over stdin or a prompt, never a flag — a flag value lands in your shell history and in hermetic’s
runstable.printf '%s' "$SECRET" | hermetic secrets push _fleet --tailscale-oauthThe secret is proved before it is stored: hermetic exchanges it for a token, mints a
tag:hermetickey, revokes it, and lists the tailnet’s devices. A client that cannot mint is refused withTAILSCALE_UNAVAILABLEand nothing is written — the old secret stays in place and the fleet keeps working. A client that mints but cannot list devices is stored, with a warning naming what is missing. -
Confirm:
hermetic secrets verify _fleet # /hermetic/<fleet_id>/tailscale/oauth-secret and oauth-client-id both "set"hermetic doctor # the device list is read rather than skippedhermetic policy # scope `read + write`, and what hermetic's entries say nowThe client id is parsed out of the secret and recorded on
_fleetand in its own slot, sohermetic configand the app’s Settings view name the client that is actually in use. -
Delete the old client in the admin console.
Existing agents are unaffected. Auth keys are single-use with a one-hour expiry and are consumed at first boot, so the rotation changes only which client mints future keys — no agent needs a recreate.