-
Notifications
You must be signed in to change notification settings - Fork 270
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
18b4bd8
commit 06d13ae
Showing
12 changed files
with
476 additions
and
6 deletions.
There are no files selected for viewing
168 changes: 168 additions & 0 deletions
168
sdk/auth/credentials/cli_profile_credentials_provider.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
package credentials | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"io/ioutil" | ||
"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 := ioutil.ReadFile(cfgPath) | ||
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() | ||
} |
192 changes: 192 additions & 0 deletions
192
sdk/auth/credentials/cli_profile_credentials_provider_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | ||
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'") | ||
} |
Oops, something went wrong.