cli_profile.go raw
1 package providers
2
3 import (
4 "encoding/json"
5 "errors"
6 "fmt"
7 "io/ioutil"
8 "os"
9 "path"
10 "strings"
11
12 "github.com/aliyun/credentials-go/credentials/internal/utils"
13 )
14
15 type CLIProfileCredentialsProvider struct {
16 profileFile string
17 profileName string
18 innerProvider CredentialsProvider
19 }
20
21 type CLIProfileCredentialsProviderBuilder struct {
22 provider *CLIProfileCredentialsProvider
23 }
24
25 func (b *CLIProfileCredentialsProviderBuilder) WithProfileFile(profileFile string) *CLIProfileCredentialsProviderBuilder {
26 b.provider.profileFile = profileFile
27 return b
28 }
29
30 func (b *CLIProfileCredentialsProviderBuilder) WithProfileName(profileName string) *CLIProfileCredentialsProviderBuilder {
31 b.provider.profileName = profileName
32 return b
33 }
34
35 func (b *CLIProfileCredentialsProviderBuilder) Build() (provider *CLIProfileCredentialsProvider, err error) {
36 // 优先级:
37 // 1. 使用显示指定的 profileFile
38 // 2. 使用环境变量(ALIBABA_CLOUD_CONFIG_FILE)指定的 profileFile
39 // 3. 兜底使用 path.Join(homeDir, ".aliyun/config") 作为 profileFile
40 if b.provider.profileFile == "" {
41 b.provider.profileFile = os.Getenv("ALIBABA_CLOUD_CONFIG_FILE")
42 }
43 // 优先级:
44 // 1. 使用显示指定的 profileName
45 // 2. 使用环境变量(ALIBABA_CLOUD_PROFILE)制定的 profileName
46 // 3. 使用 CLI 配置中的当前 profileName
47 if b.provider.profileName == "" {
48 b.provider.profileName = os.Getenv("ALIBABA_CLOUD_PROFILE")
49 }
50
51 if strings.ToLower(os.Getenv("ALIBABA_CLOUD_CLI_PROFILE_DISABLED")) == "true" {
52 err = errors.New("the CLI profile is disabled")
53 return
54 }
55
56 provider = b.provider
57 return
58 }
59
60 func NewCLIProfileCredentialsProviderBuilder() *CLIProfileCredentialsProviderBuilder {
61 return &CLIProfileCredentialsProviderBuilder{
62 provider: &CLIProfileCredentialsProvider{},
63 }
64 }
65
66 type profile struct {
67 Name string `json:"name"`
68 Mode string `json:"mode"`
69 AccessKeyID string `json:"access_key_id"`
70 AccessKeySecret string `json:"access_key_secret"`
71 SecurityToken string `json:"sts_token"`
72 RegionID string `json:"region_id"`
73 RoleArn string `json:"ram_role_arn"`
74 RoleSessionName string `json:"ram_session_name"`
75 DurationSeconds int `json:"expired_seconds"`
76 StsRegion string `json:"sts_region"`
77 EnableVpc bool `json:"enable_vpc"`
78 SourceProfile string `json:"source_profile"`
79 RoleName string `json:"ram_role_name"`
80 OIDCTokenFile string `json:"oidc_token_file"`
81 OIDCProviderARN string `json:"oidc_provider_arn"`
82 Policy string `json:"policy"`
83 ExternalId string `json:"external_id"`
84 SignInUrl string `json:"cloud_sso_sign_in_url"`
85 AccountId string `json:"cloud_sso_account_id"`
86 AccessConfig string `json:"cloud_sso_access_config"`
87 AccessToken string `json:"access_token"`
88 AccessTokenExpire int64 `json:"cloud_sso_access_token_expire"`
89 }
90
91 type configuration struct {
92 Current string `json:"current"`
93 Profiles []*profile `json:"profiles"`
94 }
95
96 func newConfigurationFromPath(cfgPath string) (conf *configuration, err error) {
97 bytes, err := ioutil.ReadFile(cfgPath)
98 if err != nil {
99 err = fmt.Errorf("reading aliyun cli config from '%s' failed %v", cfgPath, err)
100 return
101 }
102
103 conf = &configuration{}
104
105 err = json.Unmarshal(bytes, conf)
106 if err != nil {
107 err = fmt.Errorf("unmarshal aliyun cli config from '%s' failed: %s", cfgPath, string(bytes))
108 return
109 }
110
111 if conf.Profiles == nil || len(conf.Profiles) == 0 {
112 err = fmt.Errorf("no any configured profiles in '%s'", cfgPath)
113 return
114 }
115
116 return
117 }
118
119 func (conf *configuration) getProfile(name string) (profile *profile, err error) {
120 for _, p := range conf.Profiles {
121 if p.Name == name {
122 profile = p
123 return
124 }
125 }
126
127 err = fmt.Errorf("unable to get profile with '%s'", name)
128 return
129 }
130
131 func (provider *CLIProfileCredentialsProvider) getCredentialsProvider(conf *configuration, profileName string) (credentialsProvider CredentialsProvider, err error) {
132 p, err := conf.getProfile(profileName)
133 if err != nil {
134 return
135 }
136
137 switch p.Mode {
138 case "AK":
139 credentialsProvider, err = NewStaticAKCredentialsProviderBuilder().
140 WithAccessKeyId(p.AccessKeyID).
141 WithAccessKeySecret(p.AccessKeySecret).
142 Build()
143 case "StsToken":
144 credentialsProvider, err = NewStaticSTSCredentialsProviderBuilder().
145 WithAccessKeyId(p.AccessKeyID).
146 WithAccessKeySecret(p.AccessKeySecret).
147 WithSecurityToken(p.SecurityToken).
148 Build()
149 case "RamRoleArn":
150 previousProvider, err1 := NewStaticAKCredentialsProviderBuilder().
151 WithAccessKeyId(p.AccessKeyID).
152 WithAccessKeySecret(p.AccessKeySecret).
153 Build()
154 if err1 != nil {
155 return nil, err1
156 }
157
158 credentialsProvider, err = NewRAMRoleARNCredentialsProviderBuilder().
159 WithCredentialsProvider(previousProvider).
160 WithRoleArn(p.RoleArn).
161 WithRoleSessionName(p.RoleSessionName).
162 WithDurationSeconds(p.DurationSeconds).
163 WithStsRegionId(p.StsRegion).
164 WithEnableVpc(p.EnableVpc).
165 WithPolicy(p.Policy).
166 WithExternalId(p.ExternalId).
167 Build()
168 case "EcsRamRole":
169 credentialsProvider, err = NewECSRAMRoleCredentialsProviderBuilder().WithRoleName(p.RoleName).Build()
170 case "OIDC":
171 credentialsProvider, err = NewOIDCCredentialsProviderBuilder().
172 WithOIDCTokenFilePath(p.OIDCTokenFile).
173 WithOIDCProviderARN(p.OIDCProviderARN).
174 WithRoleArn(p.RoleArn).
175 WithStsRegionId(p.StsRegion).
176 WithEnableVpc(p.EnableVpc).
177 WithDurationSeconds(p.DurationSeconds).
178 WithRoleSessionName(p.RoleSessionName).
179 WithPolicy(p.Policy).
180 Build()
181 case "ChainableRamRoleArn":
182 previousProvider, err1 := provider.getCredentialsProvider(conf, p.SourceProfile)
183 if err1 != nil {
184 err = fmt.Errorf("get source profile failed: %s", err1.Error())
185 return
186 }
187 credentialsProvider, err = NewRAMRoleARNCredentialsProviderBuilder().
188 WithCredentialsProvider(previousProvider).
189 WithRoleArn(p.RoleArn).
190 WithRoleSessionName(p.RoleSessionName).
191 WithDurationSeconds(p.DurationSeconds).
192 WithStsRegionId(p.StsRegion).
193 WithEnableVpc(p.EnableVpc).
194 WithPolicy(p.Policy).
195 WithExternalId(p.ExternalId).
196 Build()
197 case "CloudSSO":
198 credentialsProvider, err = NewCloudSSOCredentialsProviderBuilder().
199 WithSignInUrl(p.SignInUrl).
200 WithAccountId(p.AccountId).
201 WithAccessConfig(p.AccessConfig).
202 WithAccessToken(p.AccessToken).
203 WithAccessTokenExpire(p.AccessTokenExpire).
204 Build()
205 default:
206 err = fmt.Errorf("unsupported profile mode '%s'", p.Mode)
207 }
208
209 return
210 }
211
212 // 默认设置为 GetHomePath,测试时便于 mock
213 var getHomePath = utils.GetHomePath
214
215 func (provider *CLIProfileCredentialsProvider) GetCredentials() (cc *Credentials, err error) {
216 if provider.innerProvider == nil {
217 cfgPath := provider.profileFile
218 if cfgPath == "" {
219 homeDir := getHomePath()
220 if homeDir == "" {
221 err = fmt.Errorf("cannot found home dir")
222 return
223 }
224
225 cfgPath = path.Join(homeDir, ".aliyun/config.json")
226 }
227
228 conf, err1 := newConfigurationFromPath(cfgPath)
229 if err1 != nil {
230 err = err1
231 return
232 }
233
234 if provider.profileName == "" {
235 provider.profileName = conf.Current
236 }
237
238 provider.innerProvider, err = provider.getCredentialsProvider(conf, provider.profileName)
239 if err != nil {
240 return
241 }
242 }
243
244 innerCC, err := provider.innerProvider.GetCredentials()
245 if err != nil {
246 return
247 }
248
249 providerName := innerCC.ProviderName
250 if providerName == "" {
251 providerName = provider.innerProvider.GetProviderName()
252 }
253
254 cc = &Credentials{
255 AccessKeyId: innerCC.AccessKeyId,
256 AccessKeySecret: innerCC.AccessKeySecret,
257 SecurityToken: innerCC.SecurityToken,
258 ProviderName: fmt.Sprintf("%s/%s", provider.GetProviderName(), providerName),
259 }
260
261 return
262 }
263
264 func (provider *CLIProfileCredentialsProvider) GetProviderName() string {
265 return "cli_profile"
266 }
267