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 40 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
1 change: 1 addition & 0 deletions docs/book/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
- [Workload Identity](./concepts/login-modes/workloadidentity.md)
- [Resource Owner Password Credential](./concepts/login-modes/ropc.md)
- [Using kubelogin with AKS](./concepts/aks.md)
- [Using kubelogin to get Proof-of-Possession (PoP) tokens for Azure Arc](./concepts/azure-arc.md)
- [Command-Line Tool](./cli-reference.md)
- [convert-kubeconfig](./cli/convert-kubeconfig.md)
- [get-token](./cli/get-token.md)
Expand Down
2 changes: 2 additions & 0 deletions docs/book/src/cli/convert-kubeconfig.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ Flags:
--legacy set to true to get token with 'spn:' prefix in audience claim
-l, --login string Login method. Supported methods: devicecode, interactive, spn, ropc, msi, azurecli, workloadidentity. It may be specified in AAD_LOGIN_METHOD environment variable (default "devicecode")
--password string password for ropc login flow. It may be specified in AAD_USER_PRINCIPAL_PASSWORD or AZURE_PASSWORD environment variable
--pop-enabled set to true to request a proof-of-possession/PoP token, or false to request a regular bearer token. Only works with interactive and spn login modes. --pop-claims must be provided if --pop-enabled is true
--pop-claims claims to include when requesting a PoP token, formatted as a comma-separated string of key=value pairs. Must include the u-claim, `u=ARM_ID` containing the ARM ID of the cluster (host). --pop-enabled must be set to true if --pop-claims are provided
--server-id string AAD server application ID
-t, --tenant-id string AAD tenant ID. It may be specified in AZURE_TENANT_ID environment variable
--token-cache-dir string directory to cache token (default "${HOME}/.kube/cache/kubelogin/")
Expand Down
2 changes: 2 additions & 0 deletions docs/book/src/cli/get-token.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ ECRET environment variable
-l, --login string Login method. Supported methods: devicecode, interactive, spn, ropc, msi, azurecli, workloadidentity. It may be specified in A
AD_LOGIN_METHOD environment variable (default "devicecode")
--password string password for ropc login flow. It may be specified in AAD_USER_PRINCIPAL_PASSWORD or AZURE_PASSWORD environment variable
--pop-enabled set to true to request a proof-of-possession/PoP token, or false to request a regular bearer token. Only works with interactive and spn login modes. --pop-claims must be provided if --pop-enabled is true
--pop-claims claims to include when requesting a PoP token, formatted as a comma-separated string of key=value pairs. Must include the u-claim, `u=ARM_ID` containing the ARM ID of the cluster (host). --pop-enabled must be set to true if --pop-claims are provided
--server-id string AAD server application ID
-t, --tenant-id string AAD tenant ID. It may be specified in AZURE_TENANT_ID environment variable
--token-cache-dir string directory to cache token (default "${HOME}/.kube/cache/kubelogin/")
Expand Down
10 changes: 10 additions & 0 deletions docs/book/src/concepts/azure-arc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Using kubelogin with Azure Arc

kubelogin can be used to authenticate with Azure Arc-enabled clusters by requesting a [proof-of-possession (PoP) token](https://learn.microsoft.com/en-us/entra/msal/dotnet/advanced/proof-of-possession-tokens). This can be done by providing both of the following flags together:

1. `--pop-enabled`: indicates that `kubelogin` should request a PoP token instead of a regular bearer token
2. `--pop-claims`: is a comma-separated list of `key=value` claims to include in the PoP token. At minimum, this must include the u-claim as `u=ARM_ID_OF_CLUSTER`, which specifies the host that the requested token should allow access on.

These flags can be provided to either `kubelogin get-token` directly to get a PoP token, or to `kubelogin convert-kubeconfig` for `kubectl` to request the token internally.

PoP token requests only work with `interactive` and `spn` login modes; these flags will be ignored if provided for other login modes.
10 changes: 10 additions & 0 deletions docs/book/src/concepts/login-modes/interactive.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ In this login mode, the access token will be cached at `${HOME}/.kube/cache/kube

## Usage Examples

### Bearer token with interactive flow
```sh
export KUBECONFIG=/path/to/kubeconfig

Expand All @@ -16,6 +17,15 @@ kubelogin convert-kubeconfig -l interactive
kubectl get nodes
```

### Proof-of-possession (PoP) token with interactive flow
```sh
export KUBECONFIG=/path/to/kubeconfig

kubelogin convert-kubeconfig -l interactive --pop-enabled --pop-claims "u=/ARM/ID/OF/CLUSTER"

kubectl get nodes
```

## References

- https://learn.microsoft.com/en-us/python/api/azure-identity/azure.identity.interactivebrowsercredential?view=azure-python
12 changes: 12 additions & 0 deletions docs/book/src/concepts/login-modes/sp.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,18 @@ export AZURE_CLIENT_CERTIFICATE_PASSWORD=<pfx password>
kubectl get nodes
```

### Proof-of-possession (PoP) token with client secret from environment variables
```sh
export KUBECONFIG=/path/to/kubeconfig

kubelogin convert-kubeconfig -l spn --pop-enabled --pop-claims "u=/ARM/ID/OF/CLUSTER"

export AAD_SERVICE_PRINCIPAL_CLIENT_ID=<spn client id>
export AAD_SERVICE_PRINCIPAL_CLIENT_SECRET=<spn secret>

kubectl get nodes
```

## Restrictions

- on AKS, it will only work with managed AAD
Expand Down
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
}
Loading