From f14615a7acc417f171df902de7375158e792ab84 Mon Sep 17 00:00:00 2001 From: Jackson Tian Date: Tue, 20 Aug 2024 17:02:41 +0800 Subject: [PATCH] Add profile credentials provider --- .../fixtures/.alibabacloud/credentials | 73 ++++++ credentials/internal/providers/profile.go | 158 ++++++++++++ .../internal/providers/profile_test.go | 232 ++++++++++++++++++ 3 files changed, 463 insertions(+) create mode 100644 credentials/internal/providers/fixtures/.alibabacloud/credentials create mode 100644 credentials/internal/providers/profile.go create mode 100644 credentials/internal/providers/profile_test.go diff --git a/credentials/internal/providers/fixtures/.alibabacloud/credentials b/credentials/internal/providers/fixtures/.alibabacloud/credentials new file mode 100644 index 0000000..e308451 --- /dev/null +++ b/credentials/internal/providers/fixtures/.alibabacloud/credentials @@ -0,0 +1,73 @@ +[default] +enable = true +type = access_key +access_key_id = foo +access_key_secret = bar + +[notype] +access_key_id = foo +access_key_secret = bar + +[noak] +type = access_key +access_key_secret = bar + +[emptyak] +type = access_key +access_key_id = +access_key_secret = bar + +[ecs] +type = ecs_ram_role +role_name = EcsRamRoleTest + +[noecs] +type = ecs_ram_role + +[emptyecs] +type = ecs_ram_role +role_name = + +[ram] +type = ram_role_arn +access_key_id = foo +access_key_secret = bar +role_arn = role_arn +role_session_name = session_name + +[noram] +type = ram_role_arn +access_key_secret = bar +role_arn = role_arn +role_session_name = session_name + +[emptyram] +type = ram_role_arn +access_key_id = +access_key_secret = bar +role_arn = role_arn +role_session_name = session_name + +[rsa] +type = rsa_key_pair +public_key_id = publicKeyId +private_key_file = ./pk.pem + +[norsa] +type = rsa_key_pair +public_key_id = publicKeyId + +[emptyrsa] +type = rsa_key_pair +public_key_id = publicKeyId +private_key_file = + +[error_rsa] +type = rsa_key_pair +public_key_id = publicKeyId +private_key_file = ./pk_error.pem + +[error_type] +type = error_type +public_key_id = publicKeyId +private_key_file = ./pk_error.pem \ No newline at end of file diff --git a/credentials/internal/providers/profile.go b/credentials/internal/providers/profile.go new file mode 100644 index 0000000..6242e8b --- /dev/null +++ b/credentials/internal/providers/profile.go @@ -0,0 +1,158 @@ +package providers + +import ( + "errors" + "fmt" + "os" + "path" + + "github.com/aliyun/credentials-go/credentials/internal/utils" + "gopkg.in/ini.v1" +) + +type ProfileCredentialsProvider struct { + profileName string + innerProvider CredentialsProvider +} + +type ProfileCredentialsProviderBuilder struct { + provider *ProfileCredentialsProvider +} + +func NewProfileCredentialsProviderBuilder() (builder *ProfileCredentialsProviderBuilder) { + return &ProfileCredentialsProviderBuilder{ + provider: &ProfileCredentialsProvider{}, + } +} + +func (b *ProfileCredentialsProviderBuilder) WithProfileName(profileName string) *ProfileCredentialsProviderBuilder { + b.provider.profileName = profileName + return b +} + +func (b *ProfileCredentialsProviderBuilder) Build() (provider *ProfileCredentialsProvider) { + // 优先级: + // 1. 使用显示指定的 profileName + // 2. 使用环境变量(ALIBABA_CLOUD_PROFILE)指定的 profileName + // 3. 兜底使用 default 作为 profileName + b.provider.profileName = utils.GetDefaultString(b.provider.profileName, os.Getenv("ALIBABA_CLOUD_PROFILE"), "default") + + provider = b.provider + return +} + +func (provider *ProfileCredentialsProvider) getCredentialsProvider(ini *ini.File) (credentialsProvider CredentialsProvider, err error) { + section, err := ini.GetSection(provider.profileName) + if err != nil { + err = errors.New("ERROR: Can not load section" + err.Error()) + return + } + + value, err := section.GetKey("type") + if err != nil { + err = errors.New("ERROR: Can not find credential type" + err.Error()) + return + } + + switch value.String() { + case "access_key": + value1, err1 := section.GetKey("access_key_id") + value2, err2 := section.GetKey("access_key_secret") + if err1 != nil || err2 != nil { + err = errors.New("ERROR: Failed to get value") + return + } + + if value1.String() == "" || value2.String() == "" { + err = errors.New("ERROR: Value can't be empty") + return + } + + credentialsProvider, err = NewStaticAKCredentialsProviderBuilder(). + WithAccessKeyId(value1.String()). + WithAccessKeySecret(value2.String()). + Build() + case "ecs_ram_role": + value1, err1 := section.GetKey("role_name") + if err1 != nil { + err = errors.New("ERROR: Failed to get value") + return + } + credentialsProvider, err = NewECSRAMRoleCredentialsProviderBuilder().WithRoleName(value1.String()).Build() + case "ram_role_arn": + value1, err1 := section.GetKey("access_key_id") + value2, err2 := section.GetKey("access_key_secret") + value3, err3 := section.GetKey("role_arn") + value4, err4 := section.GetKey("role_session_name") + if err1 != nil || err2 != nil || err3 != nil || err4 != nil { + err = errors.New("ERROR: Failed to get value") + return + } + if value1.String() == "" || value2.String() == "" || value3.String() == "" || value4.String() == "" { + err = errors.New("ERROR: Value can't be empty") + return + } + previous, err5 := NewStaticAKCredentialsProviderBuilder(). + WithAccessKeyId(value1.String()). + WithAccessKeySecret(value2.String()). + Build() + if err5 != nil { + err = errors.New("get previous credentials provider failed") + return + } + + credentialsProvider, err = NewRAMRoleARNCredentialsProviderBuilder(). + WithCredentialsProvider(previous). + WithRoleArn(value3.String()). + WithRoleSessionName(value4.String()). + WithDurationSeconds(3600). + Build() + default: + err = errors.New("ERROR: Failed to get credential") + } + return +} + +func (provider *ProfileCredentialsProvider) GetCredentials() (cc *Credentials, err error) { + if provider.innerProvider == nil { + sharedCfgPath := os.Getenv("ALIBABA_CLOUD_CREDENTIALS_FILE") + if sharedCfgPath == "" { + homeDir := getHomePath() + if homeDir == "" { + err = fmt.Errorf("cannot found home dir") + return + } + + sharedCfgPath = path.Join(homeDir, ".alibabacloud/credentials") + } + + ini, err1 := ini.Load(sharedCfgPath) + if err1 != nil { + err = errors.New("ERROR: Can not open file" + err1.Error()) + return + } + + provider.innerProvider, err = provider.getCredentialsProvider(ini) + if err != nil { + return + } + } + + innerCC, err := provider.innerProvider.GetCredentials() + if err != nil { + return + } + + cc = &Credentials{ + AccessKeyId: innerCC.AccessKeyId, + AccessKeySecret: innerCC.AccessKeySecret, + SecurityToken: innerCC.SecurityToken, + ProviderName: fmt.Sprintf("%s/%s", provider.GetProviderName(), provider.innerProvider.GetProviderName()), + } + + return +} + +func (provider *ProfileCredentialsProvider) GetProviderName() string { + return "profile" +} diff --git a/credentials/internal/providers/profile_test.go b/credentials/internal/providers/profile_test.go new file mode 100644 index 0000000..bbae027 --- /dev/null +++ b/credentials/internal/providers/profile_test.go @@ -0,0 +1,232 @@ +package providers + +import ( + "os" + "path" + "testing" + + "github.com/aliyun/credentials-go/credentials/internal/utils" + "github.com/stretchr/testify/assert" + "gopkg.in/ini.v1" +) + +var inistr = ` +[default] +enable = true +type = access_key +access_key_id = foo +access_key_secret = bar + +[notype] +access_key_id = foo +access_key_secret = bar + +[noak] +type = access_key +access_key_secret = bar + +[emptyak] +type = access_key +access_key_id = +access_key_secret = bar + +[ecs] +type = ecs_ram_role +role_name = EcsRamRoleTest + +[noecs] +type = ecs_ram_role + +[emptyecs] +type = ecs_ram_role +role_name = + +[ram] +type = ram_role_arn +access_key_id = foo +access_key_secret = bar +role_arn = role_arn +role_session_name = session_name + +[noram] +type = ram_role_arn +access_key_secret = bar +role_arn = role_arn +role_session_name = session_name + +[emptyram] +type = ram_role_arn +access_key_id = +access_key_secret = bar +role_arn = role_arn +role_session_name = session_name + +[rsa] +type = rsa_key_pair +public_key_id = publicKeyId +private_key_file = ./pk.pem + +[norsa] +type = rsa_key_pair +public_key_id = publicKeyId + +[emptyrsa] +type = rsa_key_pair +public_key_id = publicKeyId +private_key_file = + +[error_rsa] +type = rsa_key_pair +public_key_id = publicKeyId +private_key_file = ./pk_error.pem + +[error_type] +type = error_type +public_key_id = publicKeyId +private_key_file = ./pk_error.pem +` + +func TestProfileCredentialsProviderBuilder(t *testing.T) { + rollback := utils.Memory("ALIBABA_CLOUD_PROFILE") + defer rollback() + + // profile name from specified + provider := NewProfileCredentialsProviderBuilder().WithProfileName("custom").Build() + assert.Equal(t, "custom", provider.profileName) + + // profile name from env + os.Setenv("ALIBABA_CLOUD_PROFILE", "profile_from_env") + provider = NewProfileCredentialsProviderBuilder().Build() + + assert.Equal(t, "profile_from_env", provider.profileName) + + // profile name from default + os.Setenv("ALIBABA_CLOUD_PROFILE", "") + provider = NewProfileCredentialsProviderBuilder().Build() + assert.Equal(t, "default", provider.profileName) +} + +func TestProfileCredentialsProvider_getCredentialsProvider(t *testing.T) { + 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") + + file, err := ini.Load([]byte(inistr)) + assert.Nil(t, err) + assert.NotNil(t, file) + + // no type + 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 = 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 = 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 = NewProfileCredentialsProviderBuilder().Build() + cp, err := provider.getCredentialsProvider(file) + assert.Nil(t, err) + akcp, ok := cp.(*StaticAKCredentialsProvider) + assert.True(t, ok) + cc, err := akcp.GetCredentials() + assert.Nil(t, err) + assert.Equal(t, &Credentials{AccessKeyId: "foo", AccessKeySecret: "bar", SecurityToken: "", ProviderName: "static_ak"}, cc) + + // ecs_ram_role without rolename + provider = NewProfileCredentialsProviderBuilder().WithProfileName("noecs").Build() + _, err = provider.getCredentialsProvider(file) + assert.EqualError(t, err, "ERROR: Failed to get value") + + // ecs_ram_role with rolename + 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 = NewProfileCredentialsProviderBuilder().WithProfileName("noram").Build() + _, err = provider.getCredentialsProvider(file) + assert.EqualError(t, err, "ERROR: Failed to get value") + + // ram role arn without values + provider = NewProfileCredentialsProviderBuilder().WithProfileName("emptyram").Build() + _, err = provider.getCredentialsProvider(file) + assert.EqualError(t, err, "ERROR: Value can't be empty") + + // normal ram role arn + provider = NewProfileCredentialsProviderBuilder().WithProfileName("ram").Build() + cp, err = provider.getCredentialsProvider(file) + assert.Nil(t, err) + _, ok = cp.(*RAMRoleARNCredentialsProvider) + assert.True(t, ok) + + // unsupported type + provider = NewProfileCredentialsProviderBuilder().WithProfileName("error_type").Build() + _, err = provider.getCredentialsProvider(file) + assert.EqualError(t, err, "ERROR: Failed to get credential") +} + +func TestProfileCredentialsProviderGetCredentials(t *testing.T) { + rollback := utils.Memory("ALIBABA_CLOUD_CREDENTIALS_FILE") + defer func() { + getHomePath = utils.GetHomePath + rollback() + }() + + // testcase: empty home + getHomePath = func() string { + return "" + } + provider := NewProfileCredentialsProviderBuilder().WithProfileName("custom").Build() + _, err := provider.GetCredentials() + assert.EqualError(t, err, "cannot found home dir") + + // testcase: invalid home + getHomePath = func() string { + return "/path/invalid/home/dir" + } + + 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 = 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") + + // get from credentials file + getHomePath = func() string { + wd, _ := os.Getwd() + return path.Join(wd, "fixtures") + } + + provider = NewProfileCredentialsProviderBuilder().WithProfileName("custom").Build() + _, err = provider.GetCredentials() + assert.EqualError(t, err, "ERROR: Can not load sectionsection \"custom\" does not exist") + + provider = NewProfileCredentialsProviderBuilder().Build() + cc, err := provider.GetCredentials() + assert.Nil(t, err) + assert.Equal(t, &Credentials{AccessKeyId: "foo", AccessKeySecret: "bar", SecurityToken: "", ProviderName: "profile/static_ak"}, cc) + + // get credentials again + cc, err = provider.GetCredentials() + assert.Nil(t, err) + assert.Equal(t, &Credentials{AccessKeyId: "foo", AccessKeySecret: "bar", SecurityToken: "", ProviderName: "profile/static_ak"}, cc) +}