diff --git a/cmd/tailscale/cli/up.go b/cmd/tailscale/cli/up.go index 1863957d3..d32fddee4 100644 --- a/cmd/tailscale/cli/up.go +++ b/cmd/tailscale/cli/up.go @@ -1100,21 +1100,32 @@ func exitNodeIP(p *ipn.Prefs, st *ipnstate.Status) (ip netip.Addr) { } // resolveAuthKey either returns v unchanged (in the common case) or, if it -// starts with "tskey-client-" (as Tailscale OAuth secrets do) parses it like +// starts with "tskey-client-" (as Tailscale OAuth secrets do) or jwt: +// (a prefix to indicate an OIDC JWT), parses it like // -// tskey-client-xxxx[?ephemeral=false&bar&preauthorized=BOOL&baseURL=...] +// tskey-client-xxxx[?ephemeral=false&bar&preauthorized=BOOL&baseURL=...] +// or +// jwt:eyJ...[?ephemeral=false&preauthorized=BOOL&baseURL=...] // -// and does the OAuth2 dance to get and return an authkey. The "ephemeral" +// and does the OAuth or OIDC dance to get and return an authkey. The "ephemeral" // property defaults to true if unspecified. The "preauthorized" defaults to // false. The "baseURL" defaults to https://api.tailscale.com. // The passed in tags are required, and must be non-empty. These will be -// set on the authkey generated by the OAuth2 dance. +// set on the authkey generated by the OAuth2/OIDC dance. func resolveAuthKey(ctx context.Context, v, tags string) (string, error) { - if !strings.HasPrefix(v, "tskey-client-") { + var authType string + + if strings.HasPrefix(v, "tskey-client-") { + authType = "OAuth" + } else if strings.HasPrefix(v, "jwt:") { + authType = "OIDC JWT" + v = strings.TrimPrefix(v, "jwt:") + } else { + // Return unchanged for all other key types (including tskey-auth-*) return v, nil } if tags == "" { - return "", errors.New("oauth authkeys require --advertise-tags") + return "", fmt.Errorf("%s authkeys require --advertise-tags", authType) } clientSecret, named, _ := strings.Cut(v, "?")