From eef15ac9b7fe33300f9f64d2452eb26b8da4c7b5 Mon Sep 17 00:00:00 2001 From: hbc Date: Mon, 18 Dec 2023 14:46:00 -0800 Subject: [PATCH 1/7] feat: define types --- pkg/token/options.go | 41 +++++++++++++++++++++++++++++++++++++++++ pkg/token/types.go | 16 ++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 pkg/token/options.go create mode 100644 pkg/token/types.go diff --git a/pkg/token/options.go b/pkg/token/options.go new file mode 100644 index 00000000..9989cc3f --- /dev/null +++ b/pkg/token/options.go @@ -0,0 +1,41 @@ +package token + +import "github.com/Azure/kubelogin/pkg/internal/token" + +// list of supported login methods for library consumers + +const ( + ServicePrincipalLogin = token.ServicePrincipalLogin + ROPCLogin = token.ROPCLogin + MSILogin = token.MSILogin + WorkloadIdentityLogin = token.WorkloadIdentityLogin +) + +// Options defines the options for getting token. +type Options struct { + LoginMethod string + + // shared login settings + + Environment string + TenantID string + ServerID string + ClientID string + + // for ServicePrincipalLogin & ROPCLogin + + ClientSecret string + ClientCert string + ClientCertPassword string + IsPopTokenEnabled bool + PoPTokenClaims string + + // for MSILogin + + IdentityResourceID string + + // for WorkloadIdentityLogin + + AuthorityHost string + FederatedTokenFile string +} diff --git a/pkg/token/types.go b/pkg/token/types.go new file mode 100644 index 00000000..e14d666e --- /dev/null +++ b/pkg/token/types.go @@ -0,0 +1,16 @@ +package token + +import ( + "context" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore" +) + +// AccessToken represents an Azure service bearer access token with expiry information. +type AccessToken = azcore.AccessToken + +// TokenProvider provides access to tokens. +type TokenProvider interface { + // GetAccessToken returns an access token from giving settings. + GetAccessToken(ctx context.Context) (AccessToken, error) +} From 1264aef9b60a56478eb13a4f88f8cb8d6c0a012d Mon Sep 17 00:00:00 2001 From: hbc Date: Mon, 18 Dec 2023 16:01:16 -0800 Subject: [PATCH 2/7] refactor: move environment variables to `internal/env` --- pkg/internal/env/variables.go | 30 ++++++++++ pkg/internal/token/options.go | 92 +++++++++++------------------- pkg/internal/token/options_test.go | 49 ++++++++-------- 3 files changed, 88 insertions(+), 83 deletions(-) create mode 100644 pkg/internal/env/variables.go diff --git a/pkg/internal/env/variables.go b/pkg/internal/env/variables.go new file mode 100644 index 00000000..59e71011 --- /dev/null +++ b/pkg/internal/env/variables.go @@ -0,0 +1,30 @@ +package env + +const ( + // env vars + LoginMethod = "AAD_LOGIN_METHOD" + KubeloginROPCUsername = "AAD_USER_PRINCIPAL_NAME" + KubeloginROPCPassword = "AAD_USER_PRINCIPAL_PASSWORD" + KubeloginClientID = "AAD_SERVICE_PRINCIPAL_CLIENT_ID" + KubeloginClientSecret = "AAD_SERVICE_PRINCIPAL_CLIENT_SECRET" + KubeloginClientCertificatePath = "AAD_SERVICE_PRINCIPAL_CLIENT_CERTIFICATE" + KubeloginClientCertificatePassword = "AAD_SERVICE_PRINCIPAL_CLIENT_CERTIFICATE_PASSWORD" + + // env vars used by Terraform + TerraformClientID = "ARM_CLIENT_ID" + TerraformClientSecret = "ARM_CLIENT_SECRET" + TerraformClientCertificatePath = "ARM_CLIENT_CERTIFICATE_PATH" + TerraformClientCertificatePassword = "ARM_CLIENT_CERTIFICATE_PASSWORD" + TerraformTenantID = "ARM_TENANT_ID" + + // env vars following azure sdk naming convention + AzureAuthorityHost = "AZURE_AUTHORITY_HOST" + AzureClientCertificatePassword = "AZURE_CLIENT_CERTIFICATE_PASSWORD" + AzureClientCertificatePath = "AZURE_CLIENT_CERTIFICATE_PATH" + AzureClientID = "AZURE_CLIENT_ID" + AzureClientSecret = "AZURE_CLIENT_SECRET" + AzureFederatedTokenFile = "AZURE_FEDERATED_TOKEN_FILE" + AzureTenantID = "AZURE_TENANT_ID" + AzureUsername = "AZURE_USERNAME" + AzurePassword = "AZURE_PASSWORD" +) diff --git a/pkg/internal/token/options.go b/pkg/internal/token/options.go index f2fab32c..ccc02a86 100644 --- a/pkg/internal/token/options.go +++ b/pkg/internal/token/options.go @@ -7,6 +7,7 @@ import ( "strings" "time" + "github.com/Azure/kubelogin/pkg/internal/env" "github.com/spf13/pflag" "k8s.io/client-go/util/homedir" ) @@ -45,33 +46,6 @@ const ( AzureCLILogin = "azurecli" WorkloadIdentityLogin = "workloadidentity" manualTokenLogin = "manual_token" - - // env vars - loginMethod = "AAD_LOGIN_METHOD" - kubeloginROPCUsername = "AAD_USER_PRINCIPAL_NAME" - kubeloginROPCPassword = "AAD_USER_PRINCIPAL_PASSWORD" - kubeloginClientID = "AAD_SERVICE_PRINCIPAL_CLIENT_ID" - kubeloginClientSecret = "AAD_SERVICE_PRINCIPAL_CLIENT_SECRET" - kubeloginClientCertificatePath = "AAD_SERVICE_PRINCIPAL_CLIENT_CERTIFICATE" - kubeloginClientCertificatePassword = "AAD_SERVICE_PRINCIPAL_CLIENT_CERTIFICATE_PASSWORD" - - // env vars used by Terraform - terraformClientID = "ARM_CLIENT_ID" - terraformClientSecret = "ARM_CLIENT_SECRET" - terraformClientCertificatePath = "ARM_CLIENT_CERTIFICATE_PATH" - terraformClientCertificatePassword = "ARM_CLIENT_CERTIFICATE_PASSWORD" - terraformTenantID = "ARM_TENANT_ID" - - // env vars following azure sdk naming convention - azureAuthorityHost = "AZURE_AUTHORITY_HOST" - azureClientCertificatePassword = "AZURE_CLIENT_CERTIFICATE_PASSWORD" - azureClientCertificatePath = "AZURE_CLIENT_CERTIFICATE_PATH" - azureClientID = "AZURE_CLIENT_ID" - azureClientSecret = "AZURE_CLIENT_SECRET" - azureFederatedTokenFile = "AZURE_FEDERATED_TOKEN_FILE" - azureTenantID = "AZURE_TENANT_ID" - azureUsername = "AZURE_USERNAME" - azurePassword = "AZURE_PASSWORD" ) var ( @@ -97,27 +71,27 @@ func NewOptions() Options { func (o *Options) AddFlags(fs *pflag.FlagSet) { fs.StringVarP(&o.LoginMethod, "login", "l", o.LoginMethod, - fmt.Sprintf("Login method. Supported methods: %s. It may be specified in %s environment variable", GetSupportedLogins(), loginMethod)) + fmt.Sprintf("Login method. Supported methods: %s. It may be specified in %s environment variable", GetSupportedLogins(), env.LoginMethod)) fs.StringVar(&o.ClientID, "client-id", o.ClientID, - fmt.Sprintf("AAD client application ID. It may be specified in %s or %s environment variable", kubeloginClientID, azureClientID)) + fmt.Sprintf("AAD client application ID. It may be specified in %s or %s environment variable", env.KubeloginClientID, env.AzureClientID)) fs.StringVar(&o.ClientSecret, "client-secret", o.ClientSecret, - fmt.Sprintf("AAD client application secret. Used in spn login. It may be specified in %s or %s environment variable", kubeloginClientSecret, azureClientSecret)) + fmt.Sprintf("AAD client application secret. Used in spn login. It may be specified in %s or %s environment variable", env.KubeloginClientSecret, env.AzureClientSecret)) fs.StringVar(&o.ClientCert, "client-certificate", o.ClientCert, - fmt.Sprintf("AAD client cert in pfx. Used in spn login. It may be specified in %s or %s environment variable", kubeloginClientCertificatePath, azureClientCertificatePath)) + fmt.Sprintf("AAD client cert in pfx. Used in spn login. It may be specified in %s or %s environment variable", env.KubeloginClientCertificatePath, env.AzureClientCertificatePath)) fs.StringVar(&o.ClientCertPassword, "client-certificate-password", o.ClientCertPassword, - fmt.Sprintf("Password for AAD client cert. Used in spn login. It may be specified in %s or %s environment variable", kubeloginClientCertificatePassword, azureClientCertificatePassword)) + fmt.Sprintf("Password for AAD client cert. Used in spn login. It may be specified in %s or %s environment variable", env.KubeloginClientCertificatePassword, env.AzureClientCertificatePassword)) fs.StringVar(&o.Username, "username", o.Username, - fmt.Sprintf("user name for ropc login flow. It may be specified in %s or %s environment variable", kubeloginROPCUsername, azureUsername)) + fmt.Sprintf("user name for ropc login flow. It may be specified in %s or %s environment variable", env.KubeloginROPCUsername, env.AzureUsername)) fs.StringVar(&o.Password, "password", o.Password, - fmt.Sprintf("password for ropc login flow. It may be specified in %s or %s environment variable", kubeloginROPCPassword, azurePassword)) + fmt.Sprintf("password for ropc login flow. It may be specified in %s or %s environment variable", env.KubeloginROPCPassword, env.AzurePassword)) fs.StringVar(&o.IdentityResourceID, "identity-resource-id", o.IdentityResourceID, "Managed Identity resource id.") fs.StringVar(&o.ServerID, "server-id", o.ServerID, "AAD server application ID") fs.StringVar(&o.FederatedTokenFile, "federated-token-file", o.FederatedTokenFile, - fmt.Sprintf("Workload Identity federated token file. It may be specified in %s environment variable", azureFederatedTokenFile)) + fmt.Sprintf("Workload Identity federated token file. It may be specified in %s environment variable", env.AzureFederatedTokenFile)) fs.StringVar(&o.AuthorityHost, "authority-host", o.AuthorityHost, - fmt.Sprintf("Workload Identity authority host. It may be specified in %s environment variable", azureAuthorityHost)) + fmt.Sprintf("Workload Identity authority host. It may be specified in %s environment variable", env.AzureAuthorityHost)) fs.StringVar(&o.TokenCacheDir, "token-cache-dir", o.TokenCacheDir, "directory to cache token") - fs.StringVarP(&o.TenantID, "tenant-id", "t", o.TenantID, fmt.Sprintf("AAD tenant ID. It may be specified in %s environment variable", azureTenantID)) + fs.StringVarP(&o.TenantID, "tenant-id", "t", o.TenantID, fmt.Sprintf("AAD tenant ID. It may be specified in %s environment variable", env.AzureTenantID)) fs.StringVarP(&o.Environment, "environment", "e", o.Environment, "Azure environment name") fs.BoolVar(&o.IsLegacy, "legacy", o.IsLegacy, "set to true to get token with 'spn:' prefix in audience claim") fs.BoolVar(&o.UseAzureRMTerraformEnv, "use-azurerm-env-vars", o.UseAzureRMTerraformEnv, @@ -160,75 +134,75 @@ func (o *Options) UpdateFromEnv() { o.tokenCacheFile = getCacheFileName(o) if o.UseAzureRMTerraformEnv { - if v, ok := os.LookupEnv(terraformClientID); ok { + if v, ok := os.LookupEnv(env.TerraformClientID); ok { o.ClientID = v } - if v, ok := os.LookupEnv(terraformClientSecret); ok { + if v, ok := os.LookupEnv(env.TerraformClientSecret); ok { o.ClientSecret = v } - if v, ok := os.LookupEnv(terraformClientCertificatePath); ok { + if v, ok := os.LookupEnv(env.TerraformClientCertificatePath); ok { o.ClientCert = v } - if v, ok := os.LookupEnv(terraformClientCertificatePassword); ok { + if v, ok := os.LookupEnv(env.TerraformClientCertificatePassword); ok { o.ClientCertPassword = v } - if v, ok := os.LookupEnv(terraformTenantID); ok { + if v, ok := os.LookupEnv(env.TerraformTenantID); ok { o.TenantID = v } } else { - if v, ok := os.LookupEnv(kubeloginClientID); ok { + if v, ok := os.LookupEnv(env.KubeloginClientID); ok { o.ClientID = v } - if v, ok := os.LookupEnv(azureClientID); ok { + if v, ok := os.LookupEnv(env.AzureClientID); ok { o.ClientID = v } - if v, ok := os.LookupEnv(kubeloginClientSecret); ok { + if v, ok := os.LookupEnv(env.KubeloginClientSecret); ok { o.ClientSecret = v } - if v, ok := os.LookupEnv(azureClientSecret); ok { + if v, ok := os.LookupEnv(env.AzureClientSecret); ok { o.ClientSecret = v } - if v, ok := os.LookupEnv(kubeloginClientCertificatePath); ok { + if v, ok := os.LookupEnv(env.KubeloginClientCertificatePath); ok { o.ClientCert = v } - if v, ok := os.LookupEnv(azureClientCertificatePath); ok { + if v, ok := os.LookupEnv(env.AzureClientCertificatePath); ok { o.ClientCert = v } - if v, ok := os.LookupEnv(kubeloginClientCertificatePassword); ok { + if v, ok := os.LookupEnv(env.KubeloginClientCertificatePassword); ok { o.ClientCertPassword = v } - if v, ok := os.LookupEnv(azureClientCertificatePassword); ok { + if v, ok := os.LookupEnv(env.AzureClientCertificatePassword); ok { o.ClientCertPassword = v } - if v, ok := os.LookupEnv(azureTenantID); ok { + if v, ok := os.LookupEnv(env.AzureTenantID); ok { o.TenantID = v } } - if v, ok := os.LookupEnv(kubeloginROPCUsername); ok { + if v, ok := os.LookupEnv(env.KubeloginROPCUsername); ok { o.Username = v } - if v, ok := os.LookupEnv(azureUsername); ok { + if v, ok := os.LookupEnv(env.AzureUsername); ok { o.Username = v } - if v, ok := os.LookupEnv(kubeloginROPCPassword); ok { + if v, ok := os.LookupEnv(env.KubeloginROPCPassword); ok { o.Password = v } - if v, ok := os.LookupEnv(azurePassword); ok { + if v, ok := os.LookupEnv(env.AzurePassword); ok { o.Password = v } - if v, ok := os.LookupEnv(loginMethod); ok { + if v, ok := os.LookupEnv(env.LoginMethod); ok { o.LoginMethod = v } if o.LoginMethod == WorkloadIdentityLogin { - if v, ok := os.LookupEnv(azureClientID); ok { + if v, ok := os.LookupEnv(env.AzureClientID); ok { o.ClientID = v } - if v, ok := os.LookupEnv(azureFederatedTokenFile); ok { + if v, ok := os.LookupEnv(env.AzureFederatedTokenFile); ok { o.FederatedTokenFile = v } - if v, ok := os.LookupEnv(azureAuthorityHost); ok { + if v, ok := os.LookupEnv(env.AzureAuthorityHost); ok { o.AuthorityHost = v } } diff --git a/pkg/internal/token/options_test.go b/pkg/internal/token/options_test.go index bc6c127b..27e771c2 100644 --- a/pkg/internal/token/options_test.go +++ b/pkg/internal/token/options_test.go @@ -7,6 +7,7 @@ import ( "testing" "time" + "github.com/Azure/kubelogin/pkg/internal/env" "github.com/Azure/kubelogin/pkg/internal/testutils" "github.com/google/go-cmp/cmp" "github.com/spf13/pflag" @@ -86,14 +87,14 @@ func TestOptionsWithEnvVars(t *testing.T) { { name: "setting env var using legacy env var format", envVarMap: map[string]string{ - kubeloginClientID: clientID, - kubeloginClientSecret: clientSecret, - kubeloginClientCertificatePath: certPath, - kubeloginClientCertificatePassword: certPassword, - kubeloginROPCUsername: username, - kubeloginROPCPassword: password, - azureTenantID: tenantID, - loginMethod: DeviceCodeLogin, + env.KubeloginClientID: clientID, + env.KubeloginClientSecret: clientSecret, + env.KubeloginClientCertificatePath: certPath, + env.KubeloginClientCertificatePassword: certPassword, + env.KubeloginROPCUsername: username, + env.KubeloginROPCPassword: password, + env.AzureTenantID: tenantID, + env.LoginMethod: DeviceCodeLogin, }, expected: Options{ ClientID: clientID, @@ -112,12 +113,12 @@ func TestOptionsWithEnvVars(t *testing.T) { name: "setting env var using terraform env var format", isTerraform: true, envVarMap: map[string]string{ - terraformClientID: clientID, - terraformClientSecret: clientSecret, - terraformClientCertificatePath: certPath, - terraformClientCertificatePassword: certPassword, - terraformTenantID: tenantID, - loginMethod: DeviceCodeLogin, + env.TerraformClientID: clientID, + env.TerraformClientSecret: clientSecret, + env.TerraformClientCertificatePath: certPath, + env.TerraformClientCertificatePassword: certPassword, + env.TerraformTenantID: tenantID, + env.LoginMethod: DeviceCodeLogin, }, expected: Options{ UseAzureRMTerraformEnv: true, @@ -134,16 +135,16 @@ func TestOptionsWithEnvVars(t *testing.T) { { name: "setting env var using azure sdk env var format", envVarMap: map[string]string{ - azureClientID: clientID, - azureClientSecret: clientSecret, - azureClientCertificatePath: certPath, - azureClientCertificatePassword: certPassword, - azureUsername: username, - azurePassword: password, - azureTenantID: tenantID, - loginMethod: WorkloadIdentityLogin, - azureFederatedTokenFile: tokenFile, - azureAuthorityHost: authorityHost, + env.AzureClientID: clientID, + env.AzureClientSecret: clientSecret, + env.AzureClientCertificatePath: certPath, + env.AzureClientCertificatePassword: certPassword, + env.AzureUsername: username, + env.AzurePassword: password, + env.AzureTenantID: tenantID, + env.LoginMethod: WorkloadIdentityLogin, + env.AzureFederatedTokenFile: tokenFile, + env.AzureAuthorityHost: authorityHost, }, expected: Options{ ClientID: clientID, From 6cc53b8ca0fdc27b7d59ab4c7346923a4728843f Mon Sep 17 00:00:00 2001 From: hbc Date: Mon, 18 Dec 2023 16:34:58 -0800 Subject: [PATCH 3/7] feat: implement OptionsWithEnv --- pkg/token/options.go | 4 ++ pkg/token/options_ctor.go | 46 +++++++++++++++++++ pkg/token/options_ctor_test.go | 83 ++++++++++++++++++++++++++++++++++ 3 files changed, 133 insertions(+) create mode 100644 pkg/token/options_ctor.go create mode 100644 pkg/token/options_ctor_test.go diff --git a/pkg/token/options.go b/pkg/token/options.go index 9989cc3f..fc3a78e8 100644 --- a/pkg/token/options.go +++ b/pkg/token/options.go @@ -30,6 +30,10 @@ type Options struct { IsPopTokenEnabled bool PoPTokenClaims string + // for ROPCLogin + Username string + Password string + // for MSILogin IdentityResourceID string diff --git a/pkg/token/options_ctor.go b/pkg/token/options_ctor.go new file mode 100644 index 00000000..854e9b97 --- /dev/null +++ b/pkg/token/options_ctor.go @@ -0,0 +1,46 @@ +package token + +import ( + "os" + + "github.com/Azure/kubelogin/pkg/internal/env" +) + +// OptionsWithEnv loads options from environment variables. +func OptionsWithEnv() *Options { + // initial default values + rv := &Options{ + LoginMethod: os.Getenv(env.LoginMethod), + TenantID: os.Getenv(env.AzureTenantID), + ClientID: os.Getenv(env.KubeloginClientID), + ClientSecret: os.Getenv(env.KubeloginClientSecret), + ClientCert: os.Getenv(env.KubeloginClientCertificatePath), + ClientCertPassword: os.Getenv(env.KubeloginClientCertificatePassword), + Username: os.Getenv(env.KubeloginROPCUsername), + Password: os.Getenv(env.KubeloginROPCPassword), + AuthorityHost: os.Getenv(env.AzureAuthorityHost), + FederatedTokenFile: os.Getenv(env.AzureFederatedTokenFile), + } + + // azure variant overrides + if v, ok := os.LookupEnv(env.AzureClientID); ok { + rv.ClientID = v + } + if v, ok := os.LookupEnv(env.AzureClientSecret); ok { + rv.ClientSecret = v + } + if v, ok := os.LookupEnv(env.AzureClientCertificatePath); ok { + rv.ClientCert = v + } + if v, ok := os.LookupEnv(env.AzureClientCertificatePassword); ok { + rv.ClientCertPassword = v + } + if v, ok := os.LookupEnv(env.AzureUsername); ok { + rv.Username = v + } + if v, ok := os.LookupEnv(env.AzurePassword); ok { + rv.Password = v + } + + return rv +} diff --git a/pkg/token/options_ctor_test.go b/pkg/token/options_ctor_test.go new file mode 100644 index 00000000..8872629c --- /dev/null +++ b/pkg/token/options_ctor_test.go @@ -0,0 +1,83 @@ +package token + +import ( + "testing" + + "github.com/Azure/kubelogin/pkg/internal/env" + "github.com/stretchr/testify/assert" +) + +func TestOptionsWithEnv(t *testing.T) { + t.Run("no env vars", func(t *testing.T) { + o := OptionsWithEnv() + assert.Equal(t, &Options{}, o) + }) + + t.Run("with kubelogin variant env vars", func(t *testing.T) { + for k, v := range map[string]string{ + env.LoginMethod: MSILogin, + env.AzureTenantID: "tenant-id", + env.KubeloginClientID: "client-id", + env.KubeloginClientSecret: "client-secret", + env.KubeloginClientCertificatePath: "client-cert-path", + env.KubeloginClientCertificatePassword: "client-cert-password", + env.KubeloginROPCUsername: "username", + env.KubeloginROPCPassword: "password", + env.AzureAuthorityHost: "authority-host", + env.AzureFederatedTokenFile: "federated-token-file", + } { + t.Setenv(k, v) + } + + o := OptionsWithEnv() + assert.Equal(t, &Options{ + LoginMethod: MSILogin, + TenantID: "tenant-id", + ClientID: "client-id", + ClientSecret: "client-secret", + ClientCert: "client-cert-path", + ClientCertPassword: "client-cert-password", + Username: "username", + Password: "password", + AuthorityHost: "authority-host", + FederatedTokenFile: "federated-token-file", + }, o) + }) + + t.Run("with azure variant env vars", func(t *testing.T) { + for k, v := range map[string]string{ + env.LoginMethod: MSILogin, + env.AzureTenantID: "tenant-id", + env.KubeloginClientID: "client-id", + env.AzureClientID: "azure-client-id", + env.KubeloginClientSecret: "client-secret", + env.AzureClientSecret: "azure-client-secret", + env.KubeloginClientCertificatePath: "client-cert-path", + env.AzureClientCertificatePath: "azure-client-cert-path", + env.KubeloginClientCertificatePassword: "client-cert-password", + env.AzureClientCertificatePassword: "azure-client-cert-password", + env.KubeloginROPCUsername: "username", + env.AzureUsername: "azure-username", + env.KubeloginROPCPassword: "password", + env.AzurePassword: "azure-password", + env.AzureAuthorityHost: "authority-host", + env.AzureFederatedTokenFile: "federated-token-file", + } { + t.Setenv(k, v) + } + + o := OptionsWithEnv() + assert.Equal(t, &Options{ + LoginMethod: MSILogin, + TenantID: "tenant-id", + ClientID: "azure-client-id", + ClientSecret: "azure-client-secret", + ClientCert: "azure-client-cert-path", + ClientCertPassword: "azure-client-cert-password", + Username: "azure-username", + Password: "azure-password", + AuthorityHost: "authority-host", + FederatedTokenFile: "federated-token-file", + }, o) + }) +} From ec6237198fe1f2608c1a2be79193988fc8fbb3ff Mon Sep 17 00:00:00 2001 From: hbc Date: Tue, 19 Dec 2023 11:21:50 -0800 Subject: [PATCH 4/7] feat: convert library options to internal options --- pkg/token/options.go | 4 +- pkg/token/options_ctor.go | 21 +++++++++ pkg/token/options_ctor_test.go | 86 ++++++++++++++++++++++++++++++++++ 3 files changed, 110 insertions(+), 1 deletion(-) diff --git a/pkg/token/options.go b/pkg/token/options.go index fc3a78e8..8b729947 100644 --- a/pkg/token/options.go +++ b/pkg/token/options.go @@ -12,6 +12,8 @@ const ( ) // Options defines the options for getting token. +// This struct is a subset of internal/token.Options where its values are copied +// to internal type. See internal/token/options.go for details type Options struct { LoginMethod string @@ -27,7 +29,7 @@ type Options struct { ClientSecret string ClientCert string ClientCertPassword string - IsPopTokenEnabled bool + IsPoPTokenEnabled bool PoPTokenClaims string // for ROPCLogin diff --git a/pkg/token/options_ctor.go b/pkg/token/options_ctor.go index 854e9b97..751d650d 100644 --- a/pkg/token/options_ctor.go +++ b/pkg/token/options_ctor.go @@ -4,6 +4,7 @@ import ( "os" "github.com/Azure/kubelogin/pkg/internal/env" + "github.com/Azure/kubelogin/pkg/internal/token" ) // OptionsWithEnv loads options from environment variables. @@ -44,3 +45,23 @@ func OptionsWithEnv() *Options { return rv } + +func (opts *Options) toInternalOptions() *token.Options { + return &token.Options{ + LoginMethod: opts.LoginMethod, + Environment: opts.Environment, + TenantID: opts.TenantID, + ServerID: opts.ServerID, + ClientID: opts.ClientID, + ClientSecret: opts.ClientSecret, + ClientCert: opts.ClientCert, + ClientCertPassword: opts.ClientCertPassword, + IsPoPTokenEnabled: opts.IsPoPTokenEnabled, + PoPTokenClaims: opts.PoPTokenClaims, + Username: opts.Username, + Password: opts.Password, + IdentityResourceID: opts.IdentityResourceID, + AuthorityHost: opts.AuthorityHost, + FederatedTokenFile: opts.FederatedTokenFile, + } +} diff --git a/pkg/token/options_ctor_test.go b/pkg/token/options_ctor_test.go index 8872629c..9f922173 100644 --- a/pkg/token/options_ctor_test.go +++ b/pkg/token/options_ctor_test.go @@ -1,9 +1,11 @@ package token import ( + "reflect" "testing" "github.com/Azure/kubelogin/pkg/internal/env" + "github.com/Azure/kubelogin/pkg/internal/token" "github.com/stretchr/testify/assert" ) @@ -81,3 +83,87 @@ func TestOptionsWithEnv(t *testing.T) { }, o) }) } + +func TestOptions_toInternalOptions(t *testing.T) { + t.Run("basic", func(t *testing.T) { + o := &Options{ + LoginMethod: "login-method", + Environment: "environment", + TenantID: "tenant-id", + ServerID: "server-id", + ClientID: "client-id", + ClientSecret: "client-secret", + ClientCert: "client-cert", + ClientCertPassword: "client-cert-password", + IsPoPTokenEnabled: true, + PoPTokenClaims: "pop-token-claims", + Username: "username", + Password: "password", + IdentityResourceID: "identity-resource-id", + AuthorityHost: "authority-host", + FederatedTokenFile: "federated-token-file", + } + assert.Equal(t, &token.Options{ + LoginMethod: "login-method", + Environment: "environment", + TenantID: "tenant-id", + ServerID: "server-id", + ClientID: "client-id", + ClientSecret: "client-secret", + ClientCert: "client-cert", + ClientCertPassword: "client-cert-password", + IsPoPTokenEnabled: true, + PoPTokenClaims: "pop-token-claims", + Username: "username", + Password: "password", + IdentityResourceID: "identity-resource-id", + AuthorityHost: "authority-host", + FederatedTokenFile: "federated-token-file", + }, o.toInternalOptions()) + }) + + + // this test uses reflection to ensure all fields in *Options + // are copied to *token.Options without modification. + t.Run("fields assignment", func(t *testing.T) { + boolValue := true + stringValue := "string-value" + + o := &Options{} + + // fill up all fields in *Options + oType := reflect.TypeOf(o).Elem() + oValue := reflect.ValueOf(o).Elem() + for i := 0; i < oValue.NumField(); i++ { + fieldValue := oValue.Field(i) + fieldType := oType.Field(i) + switch k := fieldType.Type.Kind(); k { + case reflect.Bool: + // set bool value + fieldValue.SetBool(boolValue) + case reflect.String: + fieldValue.SetString(stringValue) + default: + t.Errorf("unexpected type: %s", k) + } + } + + internalOpts := o.toInternalOptions() + assert.NotNil(t, internalOpts) + + internalOptsValue := reflect.ValueOf(internalOpts).Elem() + for i := 0; i < oValue.NumField(); i++ { + fieldType := oType.Field(i) + t.Log(fieldType.Name) + internalOptsFieldValue := internalOptsValue.FieldByName(fieldType.Name) + switch k := fieldType.Type.Kind(); k { + case reflect.Bool: + assert.Equal(t, boolValue, internalOptsFieldValue.Bool(), "field: %s", fieldType.Name) + case reflect.String: + assert.Equal(t, stringValue, internalOptsFieldValue.String(), "field: %s", fieldType.Name) + default: + t.Errorf("unexpected type: %s", k) + } + } + }) +} From 6bb5f1c023b657b0e3bc363935a347b84b3eeccb Mon Sep 17 00:00:00 2001 From: hbc Date: Tue, 19 Dec 2023 11:39:10 -0800 Subject: [PATCH 5/7] feat: implement library token provider --- pkg/token/options_ctor_test.go | 1 - pkg/token/provider.go | 41 +++++++++++++++++++ pkg/token/provider_test.go | 74 ++++++++++++++++++++++++++++++++++ 3 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 pkg/token/provider.go create mode 100644 pkg/token/provider_test.go diff --git a/pkg/token/options_ctor_test.go b/pkg/token/options_ctor_test.go index 9f922173..bf0032f9 100644 --- a/pkg/token/options_ctor_test.go +++ b/pkg/token/options_ctor_test.go @@ -122,7 +122,6 @@ func TestOptions_toInternalOptions(t *testing.T) { }, o.toInternalOptions()) }) - // this test uses reflection to ensure all fields in *Options // are copied to *token.Options without modification. t.Run("fields assignment", func(t *testing.T) { diff --git a/pkg/token/provider.go b/pkg/token/provider.go new file mode 100644 index 00000000..479d9ce1 --- /dev/null +++ b/pkg/token/provider.go @@ -0,0 +1,41 @@ +package token + +import ( + "context" + + "github.com/Azure/kubelogin/pkg/internal/token" +) + +type tokenProviderShim struct { + impl token.TokenProvider +} + +var _ TokenProvider = (*tokenProviderShim)(nil) + +func (tp *tokenProviderShim) GetAccessToken(ctx context.Context) (AccessToken, error) { + t, err := tp.impl.Token(ctx) + if err != nil { + return AccessToken{}, err + } + + rv := AccessToken{ + Token: t.AccessToken, + ExpiresOn: t.Expires(), + } + + return rv, nil +} + +// GetTokenProvider returns a token provider based on the given options. +func GetTokenProvider(options *Options) (TokenProvider, error) { + impl, err := token.NewTokenProvider(options.toInternalOptions()) + if err != nil { + return nil, err + } + + rv := &tokenProviderShim{ + impl: impl, + } + + return rv, nil +} diff --git a/pkg/token/provider_test.go b/pkg/token/provider_test.go new file mode 100644 index 00000000..4bc41e00 --- /dev/null +++ b/pkg/token/provider_test.go @@ -0,0 +1,74 @@ +package token + +import ( + "context" + "encoding/json" + "testing" + + "github.com/Azure/go-autorest/autorest/adal" + "github.com/Azure/kubelogin/pkg/internal/token/mock_token" + "github.com/stretchr/testify/assert" + "go.uber.org/mock/gomock" +) + +func TestGetTokenProvider(t *testing.T) { + t.Run("invalid login method", func(t *testing.T) { + opts := &Options{ + LoginMethod: "invalid-login-method", + } + tp, err := GetTokenProvider(opts) + assert.Error(t, err) + assert.Nil(t, tp) + }) + + t.Run("basic", func(t *testing.T) { + opts := &Options{ + LoginMethod: MSILogin, + ClientID: "client-id", + IdentityResourceID: "identity-resource-id", + ServerID: "server-id", + } + tp, err := GetTokenProvider(opts) + assert.NoError(t, err) + assert.NotNil(t, tp) + }) +} + +func TestTokenProviderShim_GetAccessToken(t *testing.T) { + t.Run("failure case", func(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + mockTokenProvider := mock_token.NewMockTokenProvider(mockCtrl) + mockTokenProvider.EXPECT().Token(gomock.Any()).Return(adal.Token{}, assert.AnError) + + tp := &tokenProviderShim{ + impl: mockTokenProvider, + } + + token, err := tp.GetAccessToken(context.Background()) + assert.Equal(t, AccessToken{}, token) + assert.Equal(t, assert.AnError, err) + }) + + t.Run("success case", func(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + adalToken := adal.Token{ + AccessToken: "access-token", + ExpiresOn: json.Number("1700000000"), + } + mockTokenProvider := mock_token.NewMockTokenProvider(mockCtrl) + mockTokenProvider.EXPECT().Token(gomock.Any()).Return(adalToken, nil) + + tp := &tokenProviderShim{ + impl: mockTokenProvider, + } + + token, err := tp.GetAccessToken(context.Background()) + assert.NoError(t, err) + assert.Equal(t, adalToken.AccessToken, token.Token) + assert.Equal(t, adalToken.Expires(), token.ExpiresOn) + }) +} From 72287cfbf41bafd404e4de7a602bde60b3d31792 Mon Sep 17 00:00:00 2001 From: hbc Date: Tue, 19 Dec 2023 18:24:48 -0800 Subject: [PATCH 6/7] fix: drop ROPCLogin from library usage for now --- pkg/token/options.go | 7 +------ pkg/token/options_ctor.go | 10 ---------- pkg/token/options_ctor_test.go | 14 -------------- version_test.go | 2 +- 4 files changed, 2 insertions(+), 31 deletions(-) diff --git a/pkg/token/options.go b/pkg/token/options.go index 8b729947..15dc9c14 100644 --- a/pkg/token/options.go +++ b/pkg/token/options.go @@ -6,7 +6,6 @@ import "github.com/Azure/kubelogin/pkg/internal/token" const ( ServicePrincipalLogin = token.ServicePrincipalLogin - ROPCLogin = token.ROPCLogin MSILogin = token.MSILogin WorkloadIdentityLogin = token.WorkloadIdentityLogin ) @@ -24,7 +23,7 @@ type Options struct { ServerID string ClientID string - // for ServicePrincipalLogin & ROPCLogin + // for ServicePrincipalLogin ClientSecret string ClientCert string @@ -32,10 +31,6 @@ type Options struct { IsPoPTokenEnabled bool PoPTokenClaims string - // for ROPCLogin - Username string - Password string - // for MSILogin IdentityResourceID string diff --git a/pkg/token/options_ctor.go b/pkg/token/options_ctor.go index 751d650d..f91499a3 100644 --- a/pkg/token/options_ctor.go +++ b/pkg/token/options_ctor.go @@ -17,8 +17,6 @@ func OptionsWithEnv() *Options { ClientSecret: os.Getenv(env.KubeloginClientSecret), ClientCert: os.Getenv(env.KubeloginClientCertificatePath), ClientCertPassword: os.Getenv(env.KubeloginClientCertificatePassword), - Username: os.Getenv(env.KubeloginROPCUsername), - Password: os.Getenv(env.KubeloginROPCPassword), AuthorityHost: os.Getenv(env.AzureAuthorityHost), FederatedTokenFile: os.Getenv(env.AzureFederatedTokenFile), } @@ -36,12 +34,6 @@ func OptionsWithEnv() *Options { if v, ok := os.LookupEnv(env.AzureClientCertificatePassword); ok { rv.ClientCertPassword = v } - if v, ok := os.LookupEnv(env.AzureUsername); ok { - rv.Username = v - } - if v, ok := os.LookupEnv(env.AzurePassword); ok { - rv.Password = v - } return rv } @@ -58,8 +50,6 @@ func (opts *Options) toInternalOptions() *token.Options { ClientCertPassword: opts.ClientCertPassword, IsPoPTokenEnabled: opts.IsPoPTokenEnabled, PoPTokenClaims: opts.PoPTokenClaims, - Username: opts.Username, - Password: opts.Password, IdentityResourceID: opts.IdentityResourceID, AuthorityHost: opts.AuthorityHost, FederatedTokenFile: opts.FederatedTokenFile, diff --git a/pkg/token/options_ctor_test.go b/pkg/token/options_ctor_test.go index bf0032f9..be30a187 100644 --- a/pkg/token/options_ctor_test.go +++ b/pkg/token/options_ctor_test.go @@ -23,8 +23,6 @@ func TestOptionsWithEnv(t *testing.T) { env.KubeloginClientSecret: "client-secret", env.KubeloginClientCertificatePath: "client-cert-path", env.KubeloginClientCertificatePassword: "client-cert-password", - env.KubeloginROPCUsername: "username", - env.KubeloginROPCPassword: "password", env.AzureAuthorityHost: "authority-host", env.AzureFederatedTokenFile: "federated-token-file", } { @@ -39,8 +37,6 @@ func TestOptionsWithEnv(t *testing.T) { ClientSecret: "client-secret", ClientCert: "client-cert-path", ClientCertPassword: "client-cert-password", - Username: "username", - Password: "password", AuthorityHost: "authority-host", FederatedTokenFile: "federated-token-file", }, o) @@ -58,10 +54,6 @@ func TestOptionsWithEnv(t *testing.T) { env.AzureClientCertificatePath: "azure-client-cert-path", env.KubeloginClientCertificatePassword: "client-cert-password", env.AzureClientCertificatePassword: "azure-client-cert-password", - env.KubeloginROPCUsername: "username", - env.AzureUsername: "azure-username", - env.KubeloginROPCPassword: "password", - env.AzurePassword: "azure-password", env.AzureAuthorityHost: "authority-host", env.AzureFederatedTokenFile: "federated-token-file", } { @@ -76,8 +68,6 @@ func TestOptionsWithEnv(t *testing.T) { ClientSecret: "azure-client-secret", ClientCert: "azure-client-cert-path", ClientCertPassword: "azure-client-cert-password", - Username: "azure-username", - Password: "azure-password", AuthorityHost: "authority-host", FederatedTokenFile: "federated-token-file", }, o) @@ -97,8 +87,6 @@ func TestOptions_toInternalOptions(t *testing.T) { ClientCertPassword: "client-cert-password", IsPoPTokenEnabled: true, PoPTokenClaims: "pop-token-claims", - Username: "username", - Password: "password", IdentityResourceID: "identity-resource-id", AuthorityHost: "authority-host", FederatedTokenFile: "federated-token-file", @@ -114,8 +102,6 @@ func TestOptions_toInternalOptions(t *testing.T) { ClientCertPassword: "client-cert-password", IsPoPTokenEnabled: true, PoPTokenClaims: "pop-token-claims", - Username: "username", - Password: "password", IdentityResourceID: "identity-resource-id", AuthorityHost: "authority-host", FederatedTokenFile: "federated-token-file", diff --git a/version_test.go b/version_test.go index 913799a8..9df4ca36 100644 --- a/version_test.go +++ b/version_test.go @@ -8,4 +8,4 @@ func Test_loadVersion(t *testing.T) { if versionString == "" { t.Errorf("versionString is empty") } -} \ No newline at end of file +} From aef75af8cae68ab163cdc33e8f7ebdcbb88fb5ef Mon Sep 17 00:00:00 2001 From: hbc Date: Tue, 19 Dec 2023 18:25:41 -0800 Subject: [PATCH 7/7] doc: typo --- pkg/token/types.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/token/types.go b/pkg/token/types.go index e14d666e..c03480d5 100644 --- a/pkg/token/types.go +++ b/pkg/token/types.go @@ -11,6 +11,6 @@ type AccessToken = azcore.AccessToken // TokenProvider provides access to tokens. type TokenProvider interface { - // GetAccessToken returns an access token from giving settings. + // GetAccessToken returns an access token from given settings. GetAccessToken(ctx context.Context) (AccessToken, error) }