diff --git a/client/tailscale/tailscale.go b/client/tailscale/tailscale.go index 65610e708..56703a63b 100644 --- a/client/tailscale/tailscale.go +++ b/client/tailscale/tailscale.go @@ -275,6 +275,21 @@ func status(ctx context.Context, queryString string) (*ipnstate.Status, error) { return st, nil } +// IDToken is a request to get an OIDC ID token for an audience. +// The token can be presented to any resource provider which offers OIDC +// Federation. +func IDToken(ctx context.Context, aud string) (*tailcfg.TokenResponse, error) { + body, err := get200(ctx, "/localapi/v0/id-token?aud="+url.QueryEscape(aud)) + if err != nil { + return nil, err + } + tr := new(tailcfg.TokenResponse) + if err := json.Unmarshal(body, tr); err != nil { + return nil, err + } + return tr, nil +} + func WaitingFiles(ctx context.Context) ([]apitype.WaitingFile, error) { body, err := get200(ctx, "/localapi/v0/files/") if err != nil { diff --git a/cmd/tailscale/cli/cli.go b/cmd/tailscale/cli/cli.go index 59faee7c7..a98c25158 100644 --- a/cmd/tailscale/cli/cli.go +++ b/cmd/tailscale/cli/cli.go @@ -25,6 +25,7 @@ import ( "github.com/peterbourgon/ff/v3/ffcli" "tailscale.com/client/tailscale" + "tailscale.com/envknob" "tailscale.com/ipn" "tailscale.com/paths" "tailscale.com/safesocket" @@ -173,6 +174,9 @@ change in the future. for _, c := range rootCmd.Subcommands { c.UsageFunc = usageFunc } + if envknob.UseWIPCode() { + rootCmd.Subcommands = append(rootCmd.Subcommands, idTokenCmd) + } // Don't advertise the debug command, but it exists. if strSliceContains(args, "debug") { diff --git a/cmd/tailscale/cli/id-token.go b/cmd/tailscale/cli/id-token.go new file mode 100644 index 000000000..84bb1df20 --- /dev/null +++ b/cmd/tailscale/cli/id-token.go @@ -0,0 +1,35 @@ +// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package cli + +import ( + "context" + "errors" + "fmt" + + "github.com/peterbourgon/ff/v3/ffcli" + "tailscale.com/client/tailscale" +) + +var idTokenCmd = &ffcli.Command{ + Name: "id-token", + ShortUsage: "id-token ", + ShortHelp: "fetch an OIDC id-token for the Tailscale machine", + Exec: runIDToken, +} + +func runIDToken(ctx context.Context, args []string) error { + if len(args) != 1 { + return errors.New("usage: id-token ") + } + + tr, err := tailscale.IDToken(ctx, args[0]) + if err != nil { + return err + } + + fmt.Println(tr.IDToken) + return nil +} diff --git a/ipn/localapi/localapi.go b/ipn/localapi/localapi.go index 9fdcaedcb..2c18a80ac 100644 --- a/ipn/localapi/localapi.go +++ b/ipn/localapi/localapi.go @@ -25,7 +25,6 @@ import ( "inet.af/netaddr" "tailscale.com/client/tailscale/apitype" - "tailscale.com/envknob" "tailscale.com/ipn" "tailscale.com/ipn/ipnlocal" "tailscale.com/ipn/ipnstate" @@ -145,10 +144,6 @@ func (h *Handler) serveIDToken(w http.ResponseWriter, r *http.Request) { http.Error(w, "id-token access denied", http.StatusForbidden) return } - if !envknob.UseWIPCode() { - http.Error(w, "id-token access denied", http.StatusServiceUnavailable) - return - } nm := h.b.NetMap() if nm == nil { http.Error(w, "no netmap", http.StatusServiceUnavailable)