-
Notifications
You must be signed in to change notification settings - Fork 270
Commit
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
package credentials | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"os" | ||
"path" | ||
|
||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/internal" | ||
) | ||
|
||
type CLIProfileCredentialsProvider struct { | ||
profileName string | ||
innerProvider CredentialsProvider | ||
} | ||
|
||
type CLIProfileCredentialsProviderBuilder struct { | ||
provider *CLIProfileCredentialsProvider | ||
} | ||
|
||
func (b *CLIProfileCredentialsProviderBuilder) WithProfileName(profileName string) *CLIProfileCredentialsProviderBuilder { | ||
b.provider.profileName = profileName | ||
return b | ||
} | ||
|
||
func (b *CLIProfileCredentialsProviderBuilder) Build() *CLIProfileCredentialsProvider { | ||
// 优先级: | ||
// 1. 使用显示指定的 profileName | ||
// 2. 使用环境变量(ALIBABA_CLOUD_PROFILE)制定的 profileName | ||
// 3. 使用 CLI 配置中的当前 profileName | ||
if b.provider.profileName == "" { | ||
b.provider.profileName = os.Getenv("ALIBABA_CLOUD_PROFILE") | ||
} | ||
|
||
return b.provider | ||
} | ||
|
||
func NewCLIProfileCredentialsProviderBuilder() *CLIProfileCredentialsProviderBuilder { | ||
return &CLIProfileCredentialsProviderBuilder{ | ||
provider: &CLIProfileCredentialsProvider{}, | ||
} | ||
} | ||
|
||
type profile struct { | ||
Name string `json:"name"` | ||
Mode string `json:"mode"` | ||
AccessKeyID string `json:"access_key_id"` | ||
AccessKeySecret string `json:"access_key_secret"` | ||
RegionID string `json:"region_id"` | ||
RoleArn string `json:"ram_role_arn"` | ||
RoleSessionName string `json:"ram_session_name"` | ||
DurationSeconds int `json:"expired_seconds"` | ||
StsRegion string `json:"sts_region"` | ||
SourceProfile string `json:"source_profile"` | ||
RoleName string `json:"ram_role_name"` | ||
OIDCTokenFile string `json:"oidc_token_file"` | ||
OIDCProviderARN string `json:"oidc_provider_arn"` | ||
} | ||
|
||
type configuration struct { | ||
Current string `json:"current"` | ||
Profiles []*profile `json:"profiles"` | ||
} | ||
|
||
func newConfigurationFromPath(cfgPath string) (conf *configuration, err error) { | ||
bytes, err := os.ReadFile(cfgPath) | ||
Check failure on line 66 in sdk/auth/credentials/cli_profile_credentials_provider.go GitHub Actions / build (1.13)
Check failure on line 66 in sdk/auth/credentials/cli_profile_credentials_provider.go GitHub Actions / build (1.14)
|
||
if err != nil { | ||
err = fmt.Errorf("reading aliyun cli config from '%s' failed %v", cfgPath, err) | ||
return | ||
} | ||
|
||
conf = &configuration{} | ||
|
||
err = json.Unmarshal(bytes, conf) | ||
if err != nil { | ||
err = fmt.Errorf("unmarshal aliyun cli config from '%s' failed: %s", cfgPath, string(bytes)) | ||
return | ||
} | ||
|
||
if conf.Profiles == nil || len(conf.Profiles) == 0 { | ||
err = fmt.Errorf("no any configured profiles in '%s'", cfgPath) | ||
return | ||
} | ||
|
||
return | ||
} | ||
|
||
func (conf *configuration) getProfile(name string) (profile *profile, err error) { | ||
for _, p := range conf.Profiles { | ||
if p.Name == name { | ||
profile = p | ||
return | ||
} | ||
} | ||
|
||
err = fmt.Errorf("unable to get profile with '%s'", name) | ||
return | ||
} | ||
|
||
func (provider *CLIProfileCredentialsProvider) getCredentialsProvider(conf *configuration, profileName string) (credentialsProvider CredentialsProvider, err error) { | ||
p, err := conf.getProfile(profileName) | ||
if err != nil { | ||
return | ||
} | ||
|
||
switch p.Mode { | ||
case "AK": | ||
credentialsProvider = NewStaticAKCredentialsProvider(p.AccessKeyID, p.AccessKeySecret) | ||
case "RamRoleArn": | ||
previousProvider := NewStaticAKCredentialsProvider(p.AccessKeyID, p.AccessKeySecret) | ||
credentialsProvider, err = NewRAMRoleARNCredentialsProvider(previousProvider, p.RoleArn, p.RoleSessionName, p.DurationSeconds, "", p.StsRegion, "") | ||
case "EcsRamRole": | ||
credentialsProvider = NewECSRAMRoleCredentialsProvider(p.RoleName) | ||
case "OIDC": | ||
credentialsProvider, err = NewOIDCCredentialsProviderBuilder(). | ||
WithOIDCTokenFilePath(p.OIDCTokenFile). | ||
WithOIDCProviderARN(p.OIDCProviderARN). | ||
WithRoleArn(p.RoleArn). | ||
WithStsRegion(p.StsRegion). | ||
WithDurationSeconds(p.DurationSeconds). | ||
WithRoleSessionName(p.RoleSessionName). | ||
Build() | ||
case "ChainableRamRoleArn": | ||
previousProvider, err1 := provider.getCredentialsProvider(conf, p.SourceProfile) | ||
if err1 != nil { | ||
err = fmt.Errorf("get source profile failed: %s", err1.Error()) | ||
return | ||
} | ||
credentialsProvider, err = NewRAMRoleARNCredentialsProvider(previousProvider, p.RoleArn, p.RoleSessionName, p.DurationSeconds, "", p.StsRegion, "") | ||
default: | ||
err = fmt.Errorf("unsupported profile mode '%s'", p.Mode) | ||
} | ||
|
||
return | ||
} | ||
|
||
// 默认设置为 GetHomePath,测试时便于 mock | ||
var getHomePath = internal.GetHomePath | ||
|
||
func (provider *CLIProfileCredentialsProvider) GetCredentials() (cc *Credentials, err error) { | ||
if provider.innerProvider == nil { | ||
homedir := getHomePath() | ||
if homedir == "" { | ||
err = fmt.Errorf("cannot found home dir") | ||
return | ||
} | ||
|
||
cfgPath := path.Join(homedir, ".aliyun/config.json") | ||
|
||
conf, err1 := newConfigurationFromPath(cfgPath) | ||
if err1 != nil { | ||
err = err1 | ||
return | ||
} | ||
|
||
if provider.profileName == "" { | ||
provider.profileName = conf.Current | ||
} | ||
|
||
provider.innerProvider, err = provider.getCredentialsProvider(conf, provider.profileName) | ||
if err != nil { | ||
return | ||
} | ||
} | ||
|
||
return provider.innerProvider.GetCredentials() | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,192 @@ | ||
package credentials | ||
|
||
import ( | ||
"os" | ||
"path" | ||
"strings" | ||
"testing" | ||
|
||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/internal" | ||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestCLIProfileCredentialsProvider(t *testing.T) { | ||
rollback := Memory("ALIBABA_CLOUD_PROFILE") | ||
Check failure on line 14 in sdk/auth/credentials/cli_profile_credentials_provider_test.go GitHub Actions / build (1.13)
Check failure on line 14 in sdk/auth/credentials/cli_profile_credentials_provider_test.go GitHub Actions / build (1.14)
Check failure on line 14 in sdk/auth/credentials/cli_profile_credentials_provider_test.go GitHub Actions / build (1.15)
Check failure on line 14 in sdk/auth/credentials/cli_profile_credentials_provider_test.go GitHub Actions / build (1.16)
Check failure on line 14 in sdk/auth/credentials/cli_profile_credentials_provider_test.go GitHub Actions / build (1.17)
Check failure on line 14 in sdk/auth/credentials/cli_profile_credentials_provider_test.go GitHub Actions / build (1.18)
Check failure on line 14 in sdk/auth/credentials/cli_profile_credentials_provider_test.go GitHub Actions / build (1.19)
|
||
defer rollback() | ||
b := NewCLIProfileCredentialsProviderBuilder().Build() | ||
assert.Equal(t, "", b.profileName) | ||
|
||
// get from env | ||
os.Setenv("ALIBABA_CLOUD_PROFILE", "custom_profile") | ||
b = NewCLIProfileCredentialsProviderBuilder().Build() | ||
assert.Equal(t, "custom_profile", b.profileName) | ||
|
||
b = NewCLIProfileCredentialsProviderBuilder().WithProfileName("profilename").Build() | ||
assert.Equal(t, "profilename", b.profileName) | ||
} | ||
|
||
func Test_configuration(t *testing.T) { | ||
wd, _ := os.Getwd() | ||
_, err := newConfigurationFromPath(path.Join(wd, "fixtures/inexist_cli_config.json")) | ||
assert.NotNil(t, err) | ||
assert.True(t, strings.HasPrefix(err.Error(), "reading aliyun cli config from ")) | ||
|
||
_, err = newConfigurationFromPath(path.Join(wd, "fixtures/invalid_cli_config.json")) | ||
assert.NotNil(t, err) | ||
assert.True(t, strings.HasPrefix(err.Error(), "unmarshal aliyun cli config from ")) | ||
|
||
_, err = newConfigurationFromPath(path.Join(wd, "fixtures/mock_empty_cli_config.json")) | ||
assert.True(t, strings.HasPrefix(err.Error(), "no any configured profiles in ")) | ||
|
||
conf, err := newConfigurationFromPath(path.Join(wd, "fixtures/mock_cli_config.json")) | ||
assert.Nil(t, err) | ||
assert.Equal(t, &configuration{ | ||
Current: "default", | ||
Profiles: []*profile{ | ||
{ | ||
Mode: "AK", | ||
Name: "default", | ||
AccessKeyID: "akid", | ||
AccessKeySecret: "secret", | ||
}, | ||
{ | ||
Mode: "AK", | ||
Name: "jacksontian", | ||
AccessKeyID: "akid", | ||
AccessKeySecret: "secret", | ||
}, | ||
}, | ||
}, conf) | ||
|
||
_, err = conf.getProfile("inexists") | ||
assert.EqualError(t, err, "unable to get profile with 'inexists'") | ||
|
||
p, err := conf.getProfile("jacksontian") | ||
assert.Nil(t, err) | ||
assert.Equal(t, p.Name, "jacksontian") | ||
assert.Equal(t, p.Mode, "AK") | ||
} | ||
|
||
func TestCLIProfileCredentialsProvider_getCredentialsProvider(t *testing.T) { | ||
conf := &configuration{ | ||
Current: "AK", | ||
Profiles: []*profile{ | ||
{ | ||
Mode: "AK", | ||
Name: "AK", | ||
AccessKeyID: "akid", | ||
AccessKeySecret: "secret", | ||
}, | ||
{ | ||
Mode: "RamRoleArn", | ||
Name: "RamRoleArn", | ||
AccessKeyID: "akid", | ||
AccessKeySecret: "secret", | ||
RoleArn: "arn", | ||
}, | ||
{ | ||
Mode: "EcsRamRole", | ||
Name: "EcsRamRole", | ||
RoleName: "rolename", | ||
}, | ||
{ | ||
Mode: "OIDC", | ||
Name: "OIDC", | ||
RoleArn: "role_arn", | ||
OIDCTokenFile: "path/to/oidc/file", | ||
OIDCProviderARN: "provider_arn", | ||
}, | ||
{ | ||
Mode: "ChainableRamRoleArn", | ||
Name: "ChainableRamRoleArn", | ||
SourceProfile: "AK", | ||
}, | ||
{ | ||
Mode: "ChainableRamRoleArn", | ||
Name: "ChainableRamRoleArn2", | ||
SourceProfile: "InvalidSource", | ||
}, | ||
{ | ||
Mode: "Unsupported", | ||
Name: "Unsupported", | ||
}, | ||
}, | ||
} | ||
|
||
provider := NewCLIProfileCredentialsProviderBuilder().Build() | ||
_, err := provider.getCredentialsProvider(conf, "inexist") | ||
assert.EqualError(t, err, "unable to get profile with 'inexist'") | ||
|
||
// AK | ||
cp, err := provider.getCredentialsProvider(conf, "AK") | ||
assert.Nil(t, err) | ||
akcp, ok := cp.(*StaticAKCredentialsProvider) | ||
assert.True(t, ok) | ||
cc, err := akcp.GetCredentials() | ||
assert.Nil(t, err) | ||
assert.Equal(t, cc, &Credentials{AccessKeyId: "akid", AccessKeySecret: "secret", SecurityToken: ""}) | ||
// RamRoleArn | ||
cp, err = provider.getCredentialsProvider(conf, "RamRoleArn") | ||
assert.Nil(t, err) | ||
_, ok = cp.(*RAMRoleARNCredentialsProvider) | ||
assert.True(t, ok) | ||
// EcsRamRole | ||
cp, err = provider.getCredentialsProvider(conf, "EcsRamRole") | ||
assert.Nil(t, err) | ||
_, ok = cp.(*ECSRAMRoleCredentialsProvider) | ||
assert.True(t, ok) | ||
// OIDC | ||
cp, err = provider.getCredentialsProvider(conf, "OIDC") | ||
assert.Nil(t, err) | ||
_, ok = cp.(*OIDCCredentialsProvider) | ||
assert.True(t, ok) | ||
|
||
// ChainableRamRoleArn | ||
cp, err = provider.getCredentialsProvider(conf, "ChainableRamRoleArn") | ||
assert.Nil(t, err) | ||
_, ok = cp.(*RAMRoleARNCredentialsProvider) | ||
assert.True(t, ok) | ||
|
||
// ChainableRamRoleArn with invalid source profile | ||
_, err = provider.getCredentialsProvider(conf, "ChainableRamRoleArn2") | ||
assert.EqualError(t, err, "get source profile failed: unable to get profile with 'InvalidSource'") | ||
|
||
// Unsupported | ||
_, err = provider.getCredentialsProvider(conf, "Unsupported") | ||
assert.EqualError(t, err, "unsupported profile mode 'Unsupported'") | ||
} | ||
|
||
func TestCLIProfileCredentialsProvider_GetCredentials(t *testing.T) { | ||
defer func() { | ||
getHomePath = internal.GetHomePath | ||
}() | ||
|
||
getHomePath = func() string { | ||
return "" | ||
} | ||
provider := NewCLIProfileCredentialsProviderBuilder().Build() | ||
_, err := provider.GetCredentials() | ||
assert.EqualError(t, err, "cannot found home dir") | ||
|
||
getHomePath = func() string { | ||
return "/path/invalid/home/dir" | ||
} | ||
provider = NewCLIProfileCredentialsProviderBuilder().Build() | ||
_, err = provider.GetCredentials() | ||
assert.EqualError(t, err, "reading aliyun cli config from '/path/invalid/home/dir/.aliyun/config.json' failed open /path/invalid/home/dir/.aliyun/config.json: no such file or directory") | ||
|
||
getHomePath = func() string { | ||
wd, _ := os.Getwd() | ||
return path.Join(wd, "fixtures") | ||
} | ||
|
||
// get credentials by current profile | ||
provider = NewCLIProfileCredentialsProviderBuilder().Build() | ||
cc, err := provider.GetCredentials() | ||
assert.Nil(t, err) | ||
assert.Equal(t, &Credentials{AccessKeyId: "akid", AccessKeySecret: "secret", SecurityToken: "", BearerToken: ""}, cc) | ||
|
||
provider = NewCLIProfileCredentialsProviderBuilder().WithProfileName("inexist").Build() | ||
_, err = provider.GetCredentials() | ||
assert.EqualError(t, err, "unable to get profile with 'inexist'") | ||
} |