cmd,feature: add identity token auto generation for workload identity (#18373)
Adds the ability to detect what provider the client is running on and tries fetch the ID token to use with Workload Identity. Updates https://github.com/tailscale/corp/issues/33316 Signed-off-by: Danni Popova <danni@tailscale.com>
This commit is contained in:
@@ -19,6 +19,7 @@ import (
|
||||
"tailscale.com/feature"
|
||||
"tailscale.com/internal/client/tailscale"
|
||||
"tailscale.com/ipn"
|
||||
"tailscale.com/wif"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -28,13 +29,20 @@ func init() {
|
||||
}
|
||||
|
||||
// resolveAuthKey uses OIDC identity federation to exchange the provided ID token and client ID for an authkey.
|
||||
func resolveAuthKey(ctx context.Context, baseURL, clientID, idToken string, tags []string) (string, error) {
|
||||
func resolveAuthKey(ctx context.Context, baseURL, clientID, idToken, audience string, tags []string) (string, error) {
|
||||
if clientID == "" {
|
||||
return "", nil // Short-circuit, no client ID means not using identity federation
|
||||
}
|
||||
|
||||
if idToken == "" {
|
||||
return "", errors.New("federated identity authkeys require --id-token")
|
||||
if audience == "" {
|
||||
return "", errors.New("federated identity requires either an ID token or an audience")
|
||||
}
|
||||
providerIdToken, err := wif.ObtainProviderToken(ctx, audience)
|
||||
if err != nil {
|
||||
return "", errors.New("federated identity authkeys require --id-token")
|
||||
}
|
||||
idToken = providerIdToken
|
||||
}
|
||||
if len(tags) == 0 {
|
||||
return "", errors.New("federated identity authkeys require --advertise-tags")
|
||||
|
||||
@@ -16,6 +16,7 @@ func TestResolveAuthKey(t *testing.T) {
|
||||
name string
|
||||
clientID string
|
||||
idToken string
|
||||
audience string
|
||||
tags []string
|
||||
wantAuthKey string
|
||||
wantErr string
|
||||
@@ -24,6 +25,7 @@ func TestResolveAuthKey(t *testing.T) {
|
||||
name: "success",
|
||||
clientID: "client-123",
|
||||
idToken: "token",
|
||||
audience: "api://tailscale-wif",
|
||||
tags: []string{"tag:test"},
|
||||
wantAuthKey: "tskey-auth-xyz",
|
||||
wantErr: "",
|
||||
@@ -32,21 +34,24 @@ func TestResolveAuthKey(t *testing.T) {
|
||||
name: "missing client id short-circuits without error",
|
||||
clientID: "",
|
||||
idToken: "token",
|
||||
audience: "api://tailscale-wif",
|
||||
tags: []string{"tag:test"},
|
||||
wantAuthKey: "",
|
||||
wantErr: "",
|
||||
},
|
||||
{
|
||||
name: "missing id token",
|
||||
name: "missing id token and audience",
|
||||
clientID: "client-123",
|
||||
idToken: "",
|
||||
audience: "",
|
||||
tags: []string{"tag:test"},
|
||||
wantErr: "federated identity authkeys require --id-token",
|
||||
wantErr: "federated identity requires either an ID token or an audience",
|
||||
},
|
||||
{
|
||||
name: "missing tags",
|
||||
clientID: "client-123",
|
||||
idToken: "token",
|
||||
audience: "api://tailscale-wif",
|
||||
tags: []string{},
|
||||
wantErr: "federated identity authkeys require --advertise-tags",
|
||||
},
|
||||
@@ -54,6 +59,7 @@ func TestResolveAuthKey(t *testing.T) {
|
||||
name: "invalid client id attributes",
|
||||
clientID: "client-123?invalid=value",
|
||||
idToken: "token",
|
||||
audience: "api://tailscale-wif",
|
||||
tags: []string{"tag:test"},
|
||||
wantErr: `failed to parse optional config attributes: unknown optional config attribute "invalid"`,
|
||||
},
|
||||
@@ -64,7 +70,7 @@ func TestResolveAuthKey(t *testing.T) {
|
||||
srv := mockedControlServer(t)
|
||||
defer srv.Close()
|
||||
|
||||
authKey, err := resolveAuthKey(context.Background(), srv.URL, tt.clientID, tt.idToken, tt.tags)
|
||||
authKey, err := resolveAuthKey(context.Background(), srv.URL, tt.clientID, tt.idToken, tt.audience, tt.tags)
|
||||
if tt.wantErr != "" {
|
||||
if err == nil {
|
||||
t.Errorf("resolveAuthKey() error = nil, want %q", tt.wantErr)
|
||||
|
||||
Reference in New Issue
Block a user