From fbea940f10d80410e070098cb3ec43b4304bcb00 Mon Sep 17 00:00:00 2001 From: Jackson Tian Date: Thu, 15 Aug 2024 15:04:25 +0800 Subject: [PATCH] Add default credentials provider(credentials provider chain) --- .../default_credentials_provider.go | 68 ++++++++++++ .../default_credentials_provider_test.go | 102 ++++++++++++++++++ .../profile_credentials_provider.go | 2 +- .../profile_credentials_provider_test.go | 64 ++++------- 4 files changed, 192 insertions(+), 44 deletions(-) create mode 100644 sdk/auth/credentials/default_credentials_provider.go create mode 100644 sdk/auth/credentials/default_credentials_provider_test.go diff --git a/sdk/auth/credentials/default_credentials_provider.go b/sdk/auth/credentials/default_credentials_provider.go new file mode 100644 index 000000000..cf5ab18f8 --- /dev/null +++ b/sdk/auth/credentials/default_credentials_provider.go @@ -0,0 +1,68 @@ +package credentials + +import ( + "fmt" + "os" + "strings" +) + +type DefaultCredentialsProvider struct { + providerChain []CredentialsProvider + lastUsedProvider CredentialsProvider +} + +func NewDefaultCredentialsProvider() (provider *DefaultCredentialsProvider) { + providers := []CredentialsProvider{} + + // Add static ak or sts credentials provider + providers = append(providers, NewEnvironmentVariableCredentialsProvider()) + + // oidc check + oidcProvider, err := NewOIDCCredentialsProviderBuilder().Build() + if err == nil { + providers = append(providers, oidcProvider) + } + + // cli credentials provider + providers = append(providers, NewCLIProfileCredentialsProviderBuilder().Build()) + + // profile credentials provider + // providers = append(providers) + providers = append(providers, NewProfileCredentialsProviderBuilder().Build()) + + // Add IMDS + if os.Getenv("ALIBABA_CLOUD_ECS_METADATA") != "" { + ecsRamRoleProvider := NewECSRAMRoleCredentialsProvider(os.Getenv("ALIBABA_CLOUD_ECS_METADATA")) + providers = append(providers, ecsRamRoleProvider) + } + + // TODO: ALIBABA_CLOUD_CREDENTIALS_URI check + + return &DefaultCredentialsProvider{ + providerChain: providers, + } +} + +func (provider *DefaultCredentialsProvider) GetCredentials() (cc *Credentials, err error) { + if provider.lastUsedProvider != nil { + return provider.lastUsedProvider.GetCredentials() + } + + errors := []string{} + for _, p := range provider.providerChain { + provider.lastUsedProvider = p + cc, err = p.GetCredentials() + if err != nil { + errors = append(errors, err.Error()) + // 如果有错误,进入下一个获取过程 + continue + } + + if cc != nil { + return + } + } + + err = fmt.Errorf("unable to get credentials from any of the providers in the chain: %s", strings.Join(errors, ", ")) + return +} diff --git a/sdk/auth/credentials/default_credentials_provider_test.go b/sdk/auth/credentials/default_credentials_provider_test.go new file mode 100644 index 000000000..6a030bb31 --- /dev/null +++ b/sdk/auth/credentials/default_credentials_provider_test.go @@ -0,0 +1,102 @@ +package credentials + +import ( + "os" + "testing" + + "github.com/aliyun/alibaba-cloud-sdk-go/sdk/internal" + "github.com/stretchr/testify/assert" +) + +func TestDefaultCredentialsProvider(t *testing.T) { + provider := NewDefaultCredentialsProvider() + assert.NotNil(t, provider) + assert.Len(t, provider.providerChain, 3) + _, ok := provider.providerChain[0].(*EnvironmentVariableCredentialsProvider) + assert.True(t, ok) + + _, ok = provider.providerChain[1].(*CLIProfileCredentialsProvider) + assert.True(t, ok) + + _, ok = provider.providerChain[2].(*ProfileCredentialsProvider) + assert.True(t, ok) + + // Add oidc provider + rollback := internal.Memory("ALIBABA_CLOUD_OIDC_TOKEN_FILE", + "ALIBABA_CLOUD_OIDC_PROVIDER_ARN", + "ALIBABA_CLOUD_ROLE_ARN", + "ALIBABA_CLOUD_ECS_METADATA") + + defer rollback() + os.Setenv("ALIBABA_CLOUD_OIDC_TOKEN_FILE", "/path/to/oidc.token") + os.Setenv("ALIBABA_CLOUD_OIDC_PROVIDER_ARN", "oidcproviderarn") + os.Setenv("ALIBABA_CLOUD_ROLE_ARN", "rolearn") + + provider = NewDefaultCredentialsProvider() + assert.NotNil(t, provider) + assert.Len(t, provider.providerChain, 4) + _, ok = provider.providerChain[0].(*EnvironmentVariableCredentialsProvider) + assert.True(t, ok) + + _, ok = provider.providerChain[1].(*OIDCCredentialsProvider) + assert.True(t, ok) + + _, ok = provider.providerChain[2].(*CLIProfileCredentialsProvider) + assert.True(t, ok) + + _, ok = provider.providerChain[3].(*ProfileCredentialsProvider) + assert.True(t, ok) + + // Add ecs ram role + os.Setenv("ALIBABA_CLOUD_ECS_METADATA", "rolename") + provider = NewDefaultCredentialsProvider() + assert.NotNil(t, provider) + assert.Len(t, provider.providerChain, 5) + _, ok = provider.providerChain[0].(*EnvironmentVariableCredentialsProvider) + assert.True(t, ok) + + _, ok = provider.providerChain[1].(*OIDCCredentialsProvider) + assert.True(t, ok) + + _, ok = provider.providerChain[2].(*CLIProfileCredentialsProvider) + assert.True(t, ok) + + _, ok = provider.providerChain[3].(*ProfileCredentialsProvider) + assert.True(t, ok) + + _, ok = provider.providerChain[4].(*ECSRAMRoleCredentialsProvider) + assert.True(t, ok) +} + +func TestDefaultCredentialsProvider_GetCredentials(t *testing.T) { + rollback := internal.Memory("ALIBABA_CLOUD_ACCESS_KEY_ID", + "ALIBABA_CLOUD_ACCESS_KEY_SECRET", + "ALIBABA_CLOUD_SECURITY_TOKEN") + + defer func() { + getHomePath = internal.GetHomePath + rollback() + }() + + // testcase: empty home + getHomePath = func() string { + return "" + } + + provider := NewDefaultCredentialsProvider() + assert.Len(t, provider.providerChain, 3) + _, err := provider.GetCredentials() + assert.EqualError(t, err, "unable to get credentials from any of the providers in the chain: unable to get credentials from enviroment variables, Access key ID must be specified via environment variable (ALIBABA_CLOUD_ACCESS_KEY_ID), cannot found home dir, cannot found home dir") + + os.Setenv("ALIBABA_CLOUD_ACCESS_KEY_ID", "akid") + os.Setenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET", "aksecret") + provider = NewDefaultCredentialsProvider() + assert.Len(t, provider.providerChain, 3) + cc, err := provider.GetCredentials() + assert.Nil(t, err) + assert.Equal(t, &Credentials{AccessKeyId: "akid", AccessKeySecret: "aksecret", SecurityToken: "", BearerToken: ""}, cc) + // get again + cc, err = provider.GetCredentials() + assert.Nil(t, err) + assert.Equal(t, &Credentials{AccessKeyId: "akid", AccessKeySecret: "aksecret", SecurityToken: "", BearerToken: ""}, cc) +} diff --git a/sdk/auth/credentials/profile_credentials_provider.go b/sdk/auth/credentials/profile_credentials_provider.go index 4589edc78..cbfe465e3 100644 --- a/sdk/auth/credentials/profile_credentials_provider.go +++ b/sdk/auth/credentials/profile_credentials_provider.go @@ -30,7 +30,7 @@ func (b *ProfileCredentialsProviderBuilder) WithProfileName(profileName string) return b } -func (b *ProfileCredentialsProviderBuilder) Build() (provider *ProfileCredentialsProvider, err error) { +func (b *ProfileCredentialsProviderBuilder) Build() (provider *ProfileCredentialsProvider) { // 优先级: // 1. 使用显示指定的 profileName // 2. 使用环境变量(ALIBABA_CLOUD_PROFILE)指定的 profileName diff --git a/sdk/auth/credentials/profile_credentials_provider_test.go b/sdk/auth/credentials/profile_credentials_provider_test.go index 3557daf9f..20d3d845c 100644 --- a/sdk/auth/credentials/profile_credentials_provider_test.go +++ b/sdk/auth/credentials/profile_credentials_provider_test.go @@ -91,29 +91,24 @@ func TestProfileCredentialsProviderBuilder(t *testing.T) { defer rollback() // profile name from specified - provider, err := NewProfileCredentialsProviderBuilder().WithProfileName("custom").Build() - assert.Nil(t, err) + provider := NewProfileCredentialsProviderBuilder().WithProfileName("custom").Build() assert.Equal(t, "custom", provider.profileName) // profile name from env os.Setenv("ALIBABA_CLOUD_PROFILE", "profile_from_env") - provider, err = NewProfileCredentialsProviderBuilder().Build() - assert.Nil(t, err) + provider = NewProfileCredentialsProviderBuilder().Build() assert.Equal(t, "profile_from_env", provider.profileName) // profile name from default os.Setenv("ALIBABA_CLOUD_PROFILE", "") - provider, err = NewProfileCredentialsProviderBuilder().Build() - assert.Nil(t, err) + provider = NewProfileCredentialsProviderBuilder().Build() assert.Equal(t, "default", provider.profileName) } func TestProfileCredentialsProvider_getCredentialsProvider(t *testing.T) { - provider, err := NewProfileCredentialsProviderBuilder().WithProfileName("custom").Build() - assert.Nil(t, err) - - _, err = provider.getCredentialsProvider(ini.Empty()) + provider := NewProfileCredentialsProviderBuilder().WithProfileName("custom").Build() + _, err := provider.getCredentialsProvider(ini.Empty()) assert.NotNil(t, err) assert.EqualError(t, err, "ERROR: Can not load sectionsection \"custom\" does not exist") @@ -122,29 +117,25 @@ func TestProfileCredentialsProvider_getCredentialsProvider(t *testing.T) { assert.NotNil(t, file) // no type - provider, err = NewProfileCredentialsProviderBuilder().WithProfileName("notype").Build() - assert.Nil(t, err) + provider = NewProfileCredentialsProviderBuilder().WithProfileName("notype").Build() _, err = provider.getCredentialsProvider(file) assert.NotNil(t, err) assert.EqualError(t, err, "ERROR: Can not find credential typeerror when getting key of section \"notype\": key \"type\" not exists") // no ak - provider, err = NewProfileCredentialsProviderBuilder().WithProfileName("noak").Build() - assert.Nil(t, err) + provider = NewProfileCredentialsProviderBuilder().WithProfileName("noak").Build() _, err = provider.getCredentialsProvider(file) assert.NotNil(t, err) assert.EqualError(t, err, "ERROR: Failed to get value") // value is empty - provider, err = NewProfileCredentialsProviderBuilder().WithProfileName("emptyak").Build() - assert.Nil(t, err) + provider = NewProfileCredentialsProviderBuilder().WithProfileName("emptyak").Build() _, err = provider.getCredentialsProvider(file) assert.NotNil(t, err) assert.EqualError(t, err, "ERROR: Value can't be empty") // static ak provider - provider, err = NewProfileCredentialsProviderBuilder().Build() - assert.Nil(t, err) + provider = NewProfileCredentialsProviderBuilder().Build() cp, err := provider.getCredentialsProvider(file) assert.Nil(t, err) akcp, ok := cp.(*StaticAKCredentialsProvider) @@ -154,42 +145,36 @@ func TestProfileCredentialsProvider_getCredentialsProvider(t *testing.T) { assert.Equal(t, &Credentials{AccessKeyId: "foo", AccessKeySecret: "bar", SecurityToken: "", BearerToken: ""}, cc) // ecs_ram_role without rolename - provider, err = NewProfileCredentialsProviderBuilder().WithProfileName("noecs").Build() - assert.Nil(t, err) + provider = NewProfileCredentialsProviderBuilder().WithProfileName("noecs").Build() _, err = provider.getCredentialsProvider(file) assert.EqualError(t, err, "ERROR: Failed to get value") // ecs_ram_role with rolename - provider, err = NewProfileCredentialsProviderBuilder().WithProfileName("ecs").Build() - assert.Nil(t, err) + provider = NewProfileCredentialsProviderBuilder().WithProfileName("ecs").Build() cp, err = provider.getCredentialsProvider(file) assert.Nil(t, err) _, ok = cp.(*ECSRAMRoleCredentialsProvider) assert.True(t, ok) // ram role arn without keys - provider, err = NewProfileCredentialsProviderBuilder().WithProfileName("noram").Build() - assert.Nil(t, err) + provider = NewProfileCredentialsProviderBuilder().WithProfileName("noram").Build() _, err = provider.getCredentialsProvider(file) assert.EqualError(t, err, "ERROR: Failed to get value") // ram role arn without values - provider, err = NewProfileCredentialsProviderBuilder().WithProfileName("emptyram").Build() - assert.Nil(t, err) + provider = NewProfileCredentialsProviderBuilder().WithProfileName("emptyram").Build() _, err = provider.getCredentialsProvider(file) assert.EqualError(t, err, "ERROR: Value can't be empty") // normal ram role arn - provider, err = NewProfileCredentialsProviderBuilder().WithProfileName("ram").Build() - assert.Nil(t, err) + provider = NewProfileCredentialsProviderBuilder().WithProfileName("ram").Build() cp, err = provider.getCredentialsProvider(file) assert.Nil(t, err) _, ok = cp.(*RAMRoleARNCredentialsProvider) assert.True(t, ok) // unsupported type - provider, err = NewProfileCredentialsProviderBuilder().WithProfileName("error_type").Build() - assert.Nil(t, err) + provider = NewProfileCredentialsProviderBuilder().WithProfileName("error_type").Build() _, err = provider.getCredentialsProvider(file) assert.EqualError(t, err, "ERROR: Failed to get credential") } @@ -205,9 +190,8 @@ func TestProfileCredentialsProviderGetCredentials(t *testing.T) { getHomePath = func() string { return "" } - provider, err := NewProfileCredentialsProviderBuilder().WithProfileName("custom").Build() - assert.Nil(t, err) - _, err = provider.GetCredentials() + provider := NewProfileCredentialsProviderBuilder().WithProfileName("custom").Build() + _, err := provider.GetCredentials() assert.EqualError(t, err, "cannot found home dir") // testcase: invalid home @@ -215,15 +199,13 @@ func TestProfileCredentialsProviderGetCredentials(t *testing.T) { return "/path/invalid/home/dir" } - provider, err = NewProfileCredentialsProviderBuilder().WithProfileName("custom").Build() - assert.Nil(t, err) + provider = NewProfileCredentialsProviderBuilder().WithProfileName("custom").Build() _, err = provider.GetCredentials() assert.EqualError(t, err, "ERROR: Can not open fileopen /path/invalid/home/dir/.alibabacloud/credentials: no such file or directory") // testcase: specify credentials file with env os.Setenv("ALIBABA_CLOUD_CREDENTIALS_FILE", "/path/to/credentials.invalid") - provider, err = NewProfileCredentialsProviderBuilder().WithProfileName("custom").Build() - assert.Nil(t, err) + provider = NewProfileCredentialsProviderBuilder().WithProfileName("custom").Build() _, err = provider.GetCredentials() assert.EqualError(t, err, "ERROR: Can not open fileopen /path/to/credentials.invalid: no such file or directory") os.Unsetenv("ALIBABA_CLOUD_CREDENTIALS_FILE") @@ -234,15 +216,11 @@ func TestProfileCredentialsProviderGetCredentials(t *testing.T) { return path.Join(wd, "fixtures") } - // - provider, err = NewProfileCredentialsProviderBuilder().WithProfileName("custom").Build() - assert.Nil(t, err) + provider = NewProfileCredentialsProviderBuilder().WithProfileName("custom").Build() _, err = provider.GetCredentials() assert.EqualError(t, err, "ERROR: Can not load sectionsection \"custom\" does not exist") - // - provider, err = NewProfileCredentialsProviderBuilder().Build() - assert.Nil(t, err) + provider = NewProfileCredentialsProviderBuilder().Build() cc, err := provider.GetCredentials() assert.Nil(t, err) assert.Equal(t, &Credentials{AccessKeyId: "foo", AccessKeySecret: "bar", SecurityToken: "", BearerToken: ""}, cc)