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