Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add PoP token support to interactive+spn get-token/convert-kubeconfig flows #319

Merged
merged 41 commits into from
Sep 6, 2023
Merged
Show file tree
Hide file tree
Changes from 38 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
d3a19d7
Add --pop-enabled flag for get-token
Aug 8, 2023
92af2da
Add dependency on authnscheme branch of msal
Aug 8, 2023
3e0ee76
Add pop token support for interactive flow
Aug 14, 2023
6bb2f5c
Add pop token support to devicecode flow
Aug 14, 2023
50d877e
Clean up pop claim parsing
rharpavat Aug 15, 2023
247f286
Update unit test, fix some linting errors
rharpavat Aug 16, 2023
6afca46
Fix lint errors
rharpavat Aug 16, 2023
a2a4816
Remove pop token support from device code flow
rharpavat Aug 22, 2023
1430489
Tidy go mod files
rharpavat Aug 22, 2023
4a49b2c
Support pop flags in convert-kubeconfig
rharpavat Aug 22, 2023
cd2ce0b
Add PoP token support to SPN client secret flow
rharpavat Aug 22, 2023
7a66aa7
Fix SPN pop token flow
rharpavat Aug 22, 2023
ebca51f
Move PoP token request code to utils file
rharpavat Aug 23, 2023
dfaed5f
Add PoP token support for spn client cert flow
rharpavat Aug 23, 2023
5a536f2
Add options unit tests for token flow
rharpavat Aug 23, 2023
6d679d2
Fix linter errors
rharpavat Aug 24, 2023
91f1098
Refactor code + tests for cleanup, fix lint errors
rharpavat Aug 25, 2023
e5923a5
Fix authority host format
rharpavat Aug 25, 2023
95aff91
Add yaml file for new VCR test
rharpavat Aug 25, 2023
5de302e
Uts VCR httpclient + reorder token function param
julienstroheker Aug 25, 2023
eacca9d
Fix PoP token secret VCR test
rharpavat Aug 28, 2023
05c011f
Merge branch 'Azure:master' into rharpavat/add-pop-token-support
rharpavat Aug 28, 2023
07dfd46
Add VCR tests for SPN PoP token flow
rharpavat Aug 29, 2023
276da61
Refactor authnscheme, add tests
rharpavat Aug 29, 2023
c8f8745
Add func descriptions, clean up code
rharpavat Aug 29, 2023
80919c6
Update to latest msal version
rharpavat Aug 29, 2023
f066d61
Add new convert-kubeconfig tests
rharpavat Aug 29, 2023
938776c
Fix minor formatting issues
rharpavat Aug 29, 2023
4495a5d
Fix tests and casing
rharpavat Aug 29, 2023
3089efd
Refactor pop token code to return errors
rharpavat Aug 30, 2023
5eeb10a
Refactor SPN token flow into multiple files
rharpavat Aug 30, 2023
35b5235
Add client opts to interactive flow + UTs
rharpavat Aug 30, 2023
cc6e41f
Re-record VCR cassettes for SPN flow tests
rharpavat Aug 31, 2023
fcc20da
Add nil check for options
rharpavat Aug 31, 2023
2f2ccd9
Add tests for pop token code
rharpavat Aug 31, 2023
15b4104
Move vcr code to common utils package
rharpavat Aug 31, 2023
1905dec
Add VCR tests for msal.go
rharpavat Sep 1, 2023
dec69fa
Add additional tests, refactor code
rharpavat Sep 5, 2023
95dc0cd
Add tests, remove pop token mutex/global vars
rharpavat Sep 5, 2023
bbfa43d
Update documentation
rharpavat Sep 5, 2023
e776bbe
Remove copyright header from code files
rharpavat Sep 5, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@ require (
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0
github.com/Azure/go-autorest/autorest v0.11.29
github.com/Azure/go-autorest/autorest/adal v0.9.23
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.0
github.com/golang-jwt/jwt/v4 v4.5.0
github.com/golang-jwt/jwt/v5 v5.0.0
github.com/golang/mock v1.6.0
github.com/google/go-cmp v0.5.9
github.com/google/uuid v1.3.0
github.com/spf13/cobra v1.7.0
github.com/spf13/pflag v1.0.5
Expand Down Expand Up @@ -36,11 +39,9 @@ require (
github.com/go-openapi/jsonreference v0.20.1 // indirect
github.com/go-openapi/swag v0.22.3 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/btree v1.1.2 // indirect
github.com/google/gnostic v0.6.9 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
Expand Down
6 changes: 4 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+Z
github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo=
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 h1:OBhqkivkhkMqLPymWEppkm7vgPQY2XsHoEkaMQ0AdZY=
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0/go.mod h1:kgDmCTgBzIEPFElEF+FK0SdjAor06dRq2Go927dnQ6o=
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.0 h1:hVeq+yCyUi+MsoO/CU95yqCIcdzra5ovzk8Q2BBpV2M=
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.0/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
Expand Down Expand Up @@ -74,6 +74,8 @@ github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69
github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE=
github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
Expand Down
65 changes: 63 additions & 2 deletions pkg/converter/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ const (
argAuthorityHost = "--authority-host"
argFederatedTokenFile = "--federated-token-file"
argTokenCacheDir = "--token-cache-dir"
argIsPoPTokenEnabled = "--pop-enabled"
argPoPTokenClaims = "--pop-claims"

flagAzureConfigDir = "azure-config-dir"
flagClientID = "client-id"
Expand All @@ -51,6 +53,8 @@ const (
flagAuthorityHost = "authority-host"
flagFederatedTokenFile = "federated-token-file"
flagTokenCacheDir = "token-cache-dir"
flagIsPoPTokenEnabled = "pop-enabled"
flagPoPTokenClaims = "pop-claims"

execName = "kubelogin"
getTokenCommand = "get-token"
Expand All @@ -64,7 +68,16 @@ To learn more, please go to https://azure.github.io/kubelogin/
azureConfigDir = "AZURE_CONFIG_DIR"
)

func getArgValues(o Options, authInfo *api.AuthInfo) (argServerIDVal, argClientIDVal, argEnvironmentVal, argTenantIDVal, argTokenCacheDirVal string, argIsLegacyConfigModeVal bool) {
func getArgValues(o Options, authInfo *api.AuthInfo) (
argServerIDVal,
argClientIDVal,
argEnvironmentVal,
argTenantIDVal,
argTokenCacheDirVal,
argPoPTokenClaimsVal string,
argIsLegacyConfigModeVal,
argIsPoPTokenEnabledVal bool,
) {
if authInfo == nil {
return
}
Expand Down Expand Up @@ -129,6 +142,20 @@ func getArgValues(o Options, authInfo *api.AuthInfo) (argServerIDVal, argClientI
argTokenCacheDirVal = getExecArg(authInfo, argTokenCacheDir)
}

if o.isSet(flagIsPoPTokenEnabled) {
argIsPoPTokenEnabledVal = o.TokenOptions.IsPoPTokenEnabled
} else {
if found := getExecBoolArg(authInfo, argIsPoPTokenEnabled); found {
argIsPoPTokenEnabledVal = true
}
}

if o.isSet(flagPoPTokenClaims) {
argPoPTokenClaimsVal = o.TokenOptions.PoPTokenClaims
} else {
argPoPTokenClaimsVal = getExecArg(authInfo, argPoPTokenClaims)
}

return
}

Expand Down Expand Up @@ -198,7 +225,7 @@ func Convert(o Options, pathOptions *clientcmd.PathOptions) error {

klog.V(5).Info("converting...")

argServerIDVal, argClientIDVal, argEnvironmentVal, argTenantIDVal, argTokenCacheDirVal, isLegacyConfigMode := getArgValues(o, authInfo)
argServerIDVal, argClientIDVal, argEnvironmentVal, argTenantIDVal, argTokenCacheDirVal, argPoPTokenClaimsVal, isLegacyConfigMode, isPoPTokenEnabled := getArgValues(o, authInfo)
exec := &api.ExecConfig{
Command: execName,
Args: []string{
Expand Down Expand Up @@ -282,6 +309,12 @@ func Convert(o Options, pathOptions *clientcmd.PathOptions) error {
exec.Args = append(exec.Args, argEnvironment, argEnvironmentVal)
}

// PoP token flags are optional but must be provided together
exec.Args, err = validatePoPClaims(exec.Args, isPoPTokenEnabled, argPoPTokenClaims, argPoPTokenClaimsVal)
if err != nil {
return err
}

case token.ServicePrincipalLogin:

if argClientIDVal == "" {
Expand Down Expand Up @@ -317,6 +350,12 @@ func Convert(o Options, pathOptions *clientcmd.PathOptions) error {
exec.Args = append(exec.Args, argIsLegacy)
}

// PoP token flags are optional but must be provided together
exec.Args, err = validatePoPClaims(exec.Args, isPoPTokenEnabled, argPoPTokenClaims, argPoPTokenClaimsVal)
if err != nil {
return err
}

case token.MSILogin:

if o.isSet(flagClientID) {
Expand Down Expand Up @@ -420,3 +459,25 @@ func getExecBoolArg(authInfoPtr *api.AuthInfo, someArg string) bool {
}
return false
}

// If enabling PoP token support, users must provide both "--pop-enabled" and "--pop-claims" flags together.
// If either is provided without the other, validation should throw an error, otherwise the get-token command
// will fail under the hood.
func validatePoPClaims(args []string, isPopTokenEnabled bool, popTokenClaimsFlag, popTokenClaimsVal string) ([]string, error) {
if isPopTokenEnabled && popTokenClaimsVal == "" {
// pop-enabled and pop-claims must be provided together
return args, fmt.Errorf("%s is required when specifying %s", argPoPTokenClaims, argIsPoPTokenEnabled)
}

if popTokenClaimsVal != "" && !isPopTokenEnabled {
// pop-enabled and pop-claims must be provided together
return args, fmt.Errorf("%s is required when specifying %s", argIsPoPTokenEnabled, argPoPTokenClaims)
}

if isPopTokenEnabled && popTokenClaimsVal != "" {
args = append(args, argIsPoPTokenEnabled)
args = append(args, popTokenClaimsFlag, popTokenClaimsVal)
}

return args, nil
}
142 changes: 128 additions & 14 deletions pkg/converter/convert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1186,6 +1186,117 @@ func TestConvert(t *testing.T) {
},
},
},
{
name: "with exec format kubeconfig, convert from devicecode to interactive with only pop-enabled specified, Convert should return error",
execArgItems: []string{
getTokenCommand,
argServerID, serverID,
argClientID, clientID,
argTenantID, tenantID,
argEnvironment, envName,
argLoginMethod, token.DeviceCodeLogin,
argIsPoPTokenEnabled,
},
overrideFlags: map[string]string{
flagLoginMethod: token.InteractiveLogin,
},
command: execName,
expectedError: "--pop-claims is required when specifying --pop-enabled",
},
{
name: "with exec format kubeconfig, convert from devicecode to interactive with only pop-claims specified, Convert should return error",
execArgItems: []string{
getTokenCommand,
argServerID, serverID,
argClientID, clientID,
argTenantID, tenantID,
argEnvironment, envName,
argLoginMethod, token.DeviceCodeLogin,
argPoPTokenClaims, "u=testhost",
},
overrideFlags: map[string]string{
flagLoginMethod: token.InteractiveLogin,
},
command: execName,
expectedError: "--pop-enabled is required when specifying --pop-claims",
},
{
name: "with exec format kubeconfig, convert from devicecode to interactive with pop-enabled and pop-claims",
execArgItems: []string{
getTokenCommand,
argServerID, serverID,
argClientID, clientID,
argTenantID, tenantID,
argEnvironment, envName,
argLoginMethod, token.DeviceCodeLogin,
argIsPoPTokenEnabled,
argPoPTokenClaims, "u=testhost, 1=2",
},
overrideFlags: map[string]string{
flagLoginMethod: token.InteractiveLogin,
},
expectedArgs: []string{
getTokenCommand,
argServerID, serverID,
argClientID, clientID,
argTenantID, tenantID,
argEnvironment, envName,
argLoginMethod, token.InteractiveLogin,
argIsPoPTokenEnabled,
argPoPTokenClaims, "u=testhost, 1=2",
},
command: execName,
},
{
name: "with exec format kubeconfig, convert from devicecode to spn with pop-enabled and pop-claims as flags",
execArgItems: []string{
getTokenCommand,
argServerID, serverID,
argClientID, clientID,
argTenantID, tenantID,
argEnvironment, envName,
argLoginMethod, token.DeviceCodeLogin,
},
overrideFlags: map[string]string{
flagLoginMethod: token.ServicePrincipalLogin,
flagIsPoPTokenEnabled: "true",
flagPoPTokenClaims: "u=testhost, 1=2",
},
expectedArgs: []string{
getTokenCommand,
argEnvironment, envName,
argServerID, serverID,
argTenantID, tenantID,
argClientID, clientID,
argLoginMethod, token.ServicePrincipalLogin,
argIsPoPTokenEnabled,
argPoPTokenClaims, "u=testhost, 1=2",
},
command: execName,
},
{
name: "with exec format kubeconfig, convert from azurecli to devicecode with pop-enabled and pop-claims, expect pop args to be ignored",
execArgItems: []string{
getTokenCommand,
argServerID, serverID,
argLoginMethod, token.AzureCLILogin,
argIsPoPTokenEnabled,
argPoPTokenClaims, "u=testhost, 1=2",
},
overrideFlags: map[string]string{
flagClientID: clientID,
flagTenantID: tenantID,
flagLoginMethod: token.DeviceCodeLogin,
},
expectedArgs: []string{
getTokenCommand,
argServerID, serverID,
argClientID, clientID,
argTenantID, tenantID,
argLoginMethod, token.DeviceCodeLogin,
},
command: execName,
},
}
rootTmpDir, err := os.MkdirTemp("", "kubelogin-test")
if err != nil {
Expand Down Expand Up @@ -1236,20 +1347,23 @@ func TestConvert(t *testing.T) {
err = Convert(o, &pathOptions)
if data.expectedError == "" && err != nil {
t.Fatalf("Unexpected error from Convert: %v", err)
} else if data.expectedError != "" && (err == nil || err.Error() != data.expectedError) {
t.Fatalf("Expected error: %q, but got: %q", data.expectedError, err)
}

if o.context != "" {
// when --context is specified, convert-kubeconfig will convert only the targeted context
// hence, we expect the second auth info not to change
validate(t, clusterName1, config.AuthInfos[clusterName1], data.authProviderConfig, data.expectedArgs, data.expectedExecName, data.expectedInstallHint, data.expectedEnv)
validateAuthInfoThatShouldNotChange(t, clusterName2, config.AuthInfos[clusterName2], data.authProviderConfig)
} else if data.expectedError != "" {
if err == nil || err.Error() != data.expectedError {
t.Fatalf("Expected error: %q, but got: %q", data.expectedError, err)
}
} else {
// when --context is not specified, convert-kubeconfig will convert every auth info in the kubeconfig
// hence, we expect the second auth info to be converted in the same way as the first one
validate(t, clusterName1, config.AuthInfos[clusterName1], data.authProviderConfig, data.expectedArgs, data.expectedExecName, data.expectedInstallHint, data.expectedEnv)
validate(t, clusterName2, config.AuthInfos[clusterName2], data.authProviderConfig, data.expectedArgs, data.expectedExecName, data.expectedInstallHint, data.expectedEnv)
// only need to validate fields if we're not expecting an error
if o.context != "" {
// when --context is specified, convert-kubeconfig will convert only the targeted context
// hence, we expect the second auth info not to change
validate(t, clusterName1, config.AuthInfos[clusterName1], data.authProviderConfig, data.expectedArgs, data.expectedExecName, data.expectedInstallHint, data.expectedEnv)
validateAuthInfoThatShouldNotChange(t, clusterName2, config.AuthInfos[clusterName2], data.authProviderConfig)
} else {
// when --context is not specified, convert-kubeconfig will convert every auth info in the kubeconfig
// hence, we expect the second auth info to be converted in the same way as the first one
validate(t, clusterName1, config.AuthInfos[clusterName1], data.authProviderConfig, data.expectedArgs, data.expectedExecName, data.expectedInstallHint, data.expectedEnv)
validate(t, clusterName2, config.AuthInfos[clusterName2], data.authProviderConfig, data.expectedArgs, data.expectedExecName, data.expectedInstallHint, data.expectedEnv)
}
}
})
}
Expand Down Expand Up @@ -1333,7 +1447,7 @@ func validate(
t.Fatalf("[context:%s]: expected exec command: %s, actual: %s", clusterName, expectedExecName, exec.Command)
}

// defautl to the kubelogin install hint
// default to the kubelogin install hint
if expectedInstallHint == "" {
expectedInstallHint = execInstallHint
}
Expand Down
Loading