Skip to content

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.

  1. 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.

  2. 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 runs table.

    printf '%s' "$SECRET" | hermetic secrets push _fleet --tailscale-oauth

    The secret is proved before it is stored: hermetic exchanges it for a token, mints a tag:hermetic key, revokes it, and lists the tailnet’s devices. A client that cannot mint is refused with TAILSCALE_UNAVAILABLE and 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.

  3. 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 skipped
    hermetic policy # scope `read + write`, and what hermetic's entries say now

    The client id is parsed out of the secret and recorded on _fleet and in its own slot, so hermetic config and the app’s Settings view name the client that is actually in use.

  4. 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.