config/profile.go (474 lines of code) (raw):
// Copyright (c) 2009-present, Alibaba Cloud All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package config
import (
"bytes"
"encoding/json"
"fmt"
"github.com/aliyun/aliyun-cli/v3/cloudsso"
"io"
"net/http"
"os"
"os/exec"
"regexp"
"strings"
"github.com/aliyun/aliyun-cli/v3/cli"
"github.com/aliyun/aliyun-cli/v3/i18n"
"github.com/aliyun/aliyun-cli/v3/util"
credentialsv2 "github.com/aliyun/credentials-go/credentials"
)
var tryRefreshStsTokenFunc = cloudsso.TryRefreshStsToken
type AuthenticateMode string
const (
AK = AuthenticateMode("AK")
// Deprecated: StsToken is deprecated
StsToken = AuthenticateMode("StsToken")
RamRoleArn = AuthenticateMode("RamRoleArn")
EcsRamRole = AuthenticateMode("EcsRamRole")
// Deprecated: RsaKeyPair is deprecated
RsaKeyPair = AuthenticateMode("RsaKeyPair")
// Deprecated: RamRoleArnWithRoleName is deprecated, use ChainableRamRoleArn instead of
RamRoleArnWithEcs = AuthenticateMode("RamRoleArnWithRoleName")
ChainableRamRoleArn = AuthenticateMode("ChainableRamRoleArn")
External = AuthenticateMode("External")
CredentialsURI = AuthenticateMode("CredentialsURI")
OIDC = AuthenticateMode("OIDC")
CloudSSO = AuthenticateMode("CloudSSO")
)
type Profile struct {
Name string `json:"name"`
Mode AuthenticateMode `json:"mode"`
AccessKeyId string `json:"access_key_id,omitempty"`
AccessKeySecret string `json:"access_key_secret,omitempty"`
StsToken string `json:"sts_token,omitempty"`
StsRegion string `json:"sts_region,omitempty"`
RamRoleName string `json:"ram_role_name,omitempty"`
RamRoleArn string `json:"ram_role_arn,omitempty"`
RoleSessionName string `json:"ram_session_name,omitempty"`
ExternalId string `json:"external_id,omitempty"`
SourceProfile string `json:"source_profile,omitempty"`
PrivateKey string `json:"private_key,omitempty"`
KeyPairName string `json:"key_pair_name,omitempty"`
ExpiredSeconds int `json:"expired_seconds,omitempty"`
Verified string `json:"verified,omitempty"`
RegionId string `json:"region_id,omitempty"`
OutputFormat string `json:"output_format,omitempty"`
Language string `json:"language,omitempty"`
Site string `json:"site,omitempty"`
ReadTimeout int `json:"retry_timeout,omitempty"`
ConnectTimeout int `json:"connect_timeout,omitempty"`
RetryCount int `json:"retry_count,omitempty"`
ProcessCommand string `json:"process_command,omitempty"`
CredentialsURI string `json:"credentials_uri,omitempty"`
OIDCProviderARN string `json:"oidc_provider_arn,omitempty"`
OIDCTokenFile string `json:"oidc_token_file,omitempty"`
CloudSSOSignInUrl string `json:"cloud_sso_sign_in_url,omitempty"`
AccessToken string `json:"access_token,omitempty"` // for CloudSSO, read only
CloudSSOAccessTokenExpire int64 `json:"cloud_sso_access_token_expire,omitempty"` // for CloudSSO, read only
StsExpiration int64 `json:"sts_expiration,omitempty"` // for CloudSSO, read only
CloudSSOAccessConfig string `json:"cloud_sso_access_config,omitempty"` // for CloudSSO
CloudSSOAccountId string `json:"cloud_sso_account_id,omitempty"` // for CloudSSO, read only
parent *Configuration //`json:"-"`
}
func NewProfile(name string) Profile {
return Profile{
Name: name,
Mode: "",
OutputFormat: "json",
Language: i18n.GetLanguage(),
}
}
func (cp *Profile) Validate() error {
if cp.RegionId == "" {
return fmt.Errorf("region can't be empty")
}
if !IsRegion(cp.RegionId) {
return fmt.Errorf("invalid region %s", cp.RegionId)
}
if cp.Mode == "" {
return fmt.Errorf("profile %s is not configure yet, run `aliyun configure --profile %s` first", cp.Name, cp.Name)
}
switch cp.Mode {
case AK:
return cp.ValidateAK()
case StsToken:
err := cp.ValidateAK()
if err != nil {
return err
}
if cp.StsToken == "" {
return fmt.Errorf("invalid sts_token")
}
case RamRoleArn:
err := cp.ValidateAK()
if err != nil {
return err
}
if cp.RamRoleArn == "" {
return fmt.Errorf("invalid ram_role_arn")
}
if cp.RoleSessionName == "" {
return fmt.Errorf("invalid role_session_name")
}
case EcsRamRole, RamRoleArnWithEcs:
case RsaKeyPair:
if cp.PrivateKey == "" {
return fmt.Errorf("invalid private_key")
}
if cp.KeyPairName == "" {
return fmt.Errorf("invalid key_pair_name")
}
case External:
if cp.ProcessCommand == "" {
return fmt.Errorf("invalid process_command")
}
case CredentialsURI:
if cp.CredentialsURI == "" {
return fmt.Errorf("invalid credentials_uri")
}
case OIDC:
if cp.OIDCProviderARN == "" {
return fmt.Errorf("invalid oidc_provider_arn")
}
if cp.OIDCTokenFile == "" {
return fmt.Errorf("invalid oidc_token_file")
}
if cp.RamRoleArn == "" {
return fmt.Errorf("invalid ram_role_arn")
}
if cp.RoleSessionName == "" {
return fmt.Errorf("invalid role_session_name")
}
case ChainableRamRoleArn:
if cp.SourceProfile == "" {
return fmt.Errorf("invalid source_profile")
}
if cp.RamRoleArn == "" {
return fmt.Errorf("invalid ram_role_arn")
}
if cp.RoleSessionName == "" {
return fmt.Errorf("invalid role_session_name")
}
case CloudSSO:
if cp.CloudSSOSignInUrl == "" {
return fmt.Errorf("invalid cloud_sso_sign_in_url")
}
default:
return fmt.Errorf("invalid mode: %s", cp.Mode)
}
return nil
}
func (cp *Profile) GetParent() *Configuration {
return cp.parent
}
func (cp *Profile) OverwriteWithFlags(ctx *cli.Context) {
cp.Mode = AuthenticateMode(ModeFlag(ctx.Flags()).GetStringOrDefault(string(cp.Mode)))
cp.AccessKeyId = AccessKeyIdFlag(ctx.Flags()).GetStringOrDefault(cp.AccessKeyId)
cp.AccessKeySecret = AccessKeySecretFlag(ctx.Flags()).GetStringOrDefault(cp.AccessKeySecret)
cp.StsToken = StsTokenFlag(ctx.Flags()).GetStringOrDefault(cp.StsToken)
cp.StsRegion = StsRegionFlag(ctx.Flags()).GetStringOrDefault(cp.StsRegion)
cp.RamRoleName = RamRoleNameFlag(ctx.Flags()).GetStringOrDefault(cp.RamRoleName)
cp.RamRoleArn = RamRoleArnFlag(ctx.Flags()).GetStringOrDefault(cp.RamRoleArn)
cp.ExternalId = ExternalIdFlag(ctx.Flags()).GetStringOrDefault(cp.ExternalId)
cp.RoleSessionName = RoleSessionNameFlag(ctx.Flags()).GetStringOrDefault(cp.RoleSessionName)
cp.KeyPairName = KeyPairNameFlag(ctx.Flags()).GetStringOrDefault(cp.KeyPairName)
cp.PrivateKey = PrivateKeyFlag(ctx.Flags()).GetStringOrDefault(cp.PrivateKey)
cp.RegionId = RegionFlag(ctx.Flags()).GetStringOrDefault(cp.RegionId)
cp.Language = LanguageFlag(ctx.Flags()).GetStringOrDefault(cp.Language)
cp.ReadTimeout = ReadTimeoutFlag(ctx.Flags()).GetIntegerOrDefault(cp.ReadTimeout)
cp.ConnectTimeout = ConnectTimeoutFlag(ctx.Flags()).GetIntegerOrDefault(cp.ConnectTimeout)
cp.RetryCount = RetryCountFlag(ctx.Flags()).GetIntegerOrDefault(cp.RetryCount)
cp.ExpiredSeconds = ExpiredSecondsFlag(ctx.Flags()).GetIntegerOrDefault(cp.ExpiredSeconds)
cp.ProcessCommand = ProcessCommandFlag(ctx.Flags()).GetStringOrDefault(cp.ProcessCommand)
cp.OIDCProviderARN = OIDCProviderARNFlag(ctx.Flags()).GetStringOrDefault(cp.OIDCProviderARN)
cp.OIDCTokenFile = OIDCTokenFileFlag(ctx.Flags()).GetStringOrDefault(cp.OIDCTokenFile)
cp.CloudSSOSignInUrl = CloudSSOSignInUrlFlag(ctx.Flags()).GetStringOrDefault(cp.CloudSSOSignInUrl)
cp.CloudSSOAccessConfig = CloudSSOAccessConfigFlag(ctx.Flags()).GetStringOrDefault(cp.CloudSSOAccessConfig)
cp.CloudSSOAccountId = CloudSSOAccountIdFlag(ctx.Flags()).GetStringOrDefault(cp.CloudSSOAccountId)
if cp.AccessKeyId == "" {
cp.AccessKeyId = util.GetFromEnv("ALIBABA_CLOUD_ACCESS_KEY_ID", "ALIBABACLOUD_ACCESS_KEY_ID", "ALICLOUD_ACCESS_KEY_ID", "ACCESS_KEY_ID")
}
if cp.AccessKeySecret == "" {
cp.AccessKeySecret = util.GetFromEnv("ALIBABA_CLOUD_ACCESS_KEY_SECRET", "ALIBABACLOUD_ACCESS_KEY_SECRET", "ALICLOUD_ACCESS_KEY_SECRET", "ACCESS_KEY_SECRET")
}
if cp.StsToken == "" {
cp.StsToken = util.GetFromEnv("ALIBABA_CLOUD_SECURITY_TOKEN", "ALIBABACLOUD_SECURITY_TOKEN", "ALICLOUD_SECURITY_TOKEN", "SECURITY_TOKEN")
}
if cp.RegionId == "" {
cp.RegionId = util.GetFromEnv("ALIBABA_CLOUD_REGION_ID", "ALIBABACLOUD_REGION_ID", "ALICLOUD_REGION_ID", "REGION_ID", "REGION")
}
if cp.CredentialsURI == "" {
cp.CredentialsURI = os.Getenv("ALIBABA_CLOUD_CREDENTIALS_URI")
}
if cp.OIDCProviderARN == "" {
cp.OIDCProviderARN = util.GetFromEnv("ALIBABACLOUD_OIDC_PROVIDER_ARN", "ALIBABA_CLOUD_OIDC_PROVIDER_ARN")
}
if cp.OIDCTokenFile == "" {
cp.OIDCTokenFile = util.GetFromEnv("ALIBABACLOUD_OIDC_TOKEN_FILE", "ALIBABA_CLOUD_OIDC_TOKEN_FILE")
}
if cp.RamRoleArn == "" {
cp.RamRoleArn = util.GetFromEnv("ALIBABACLOUD_ROLE_ARN", "ALIBABA_CLOUD_ROLE_ARN")
}
if cp.ExternalId == "" {
cp.ExternalId = util.GetFromEnv("ALIBABACLOUD_EXTERNAL_ID", "ALIBAB_ACLOUD_EXTERNAL_ID")
}
AutoModeRecognition(cp)
}
func AutoModeRecognition(cp *Profile) {
if cp.Mode != AuthenticateMode("") {
return
}
if cp.AccessKeyId != "" && cp.AccessKeySecret != "" {
cp.Mode = AK
if cp.StsToken != "" {
cp.Mode = StsToken
} else if cp.RamRoleArn != "" {
cp.Mode = RamRoleArn
}
} else if cp.PrivateKey != "" && cp.KeyPairName != "" {
cp.Mode = RsaKeyPair
} else if cp.RamRoleName != "" {
cp.Mode = EcsRamRole
} else if cp.ProcessCommand != "" {
cp.Mode = External
} else if cp.OIDCProviderARN != "" && cp.OIDCTokenFile != "" && cp.RamRoleArn != "" {
cp.Mode = OIDC
} else if cp.CloudSSOSignInUrl != "" {
cp.Mode = CloudSSO
}
}
func (cp *Profile) ValidateAK() error {
if len(cp.AccessKeyId) == 0 {
return fmt.Errorf("invalid access_key_id: %s", cp.AccessKeyId)
}
if len(cp.AccessKeySecret) == 0 {
return fmt.Errorf("invaild access_key_secret: %s", cp.AccessKeySecret)
}
return nil
}
func getSTSEndpoint(regionId string) string {
if regionId != "" {
return fmt.Sprintf("sts.%s.aliyuncs.com", regionId)
}
return "sts.aliyuncs.com"
}
func (cp *Profile) GetCredential(ctx *cli.Context, proxyHost *string) (cred credentialsv2.Credential, err error) {
config := new(credentialsv2.Config)
// The AK, StsToken are direct credential
// Others are indirect credential
cp.Validate()
switch cp.Mode {
case AK:
if cp.AccessKeyId == "" || cp.AccessKeySecret == "" {
err = fmt.Errorf("AccessKeyId/AccessKeySecret is empty! run `aliyun configure` first")
return
}
if cp.RegionId == "" {
err = fmt.Errorf("default RegionId is empty! run `aliyun configure` first")
return
}
config.SetType("access_key").
SetAccessKeyId(cp.AccessKeyId).
SetAccessKeySecret(cp.AccessKeySecret)
case StsToken:
config.SetType("sts").
SetAccessKeyId(cp.AccessKeyId).
SetAccessKeySecret(cp.AccessKeySecret).
SetSecurityToken(cp.StsToken)
case RamRoleArn:
config.SetType("ram_role_arn").
SetAccessKeyId(cp.AccessKeyId).
SetAccessKeySecret(cp.AccessKeySecret).
SetRoleArn(cp.RamRoleArn).
SetRoleSessionName(cp.RoleSessionName).
SetRoleSessionExpiration(cp.ExpiredSeconds).
SetExternalId(cp.ExternalId).
SetSTSEndpoint(getSTSEndpoint(cp.StsRegion))
if cp.StsToken != "" {
config.SetSecurityToken(cp.StsToken)
}
case EcsRamRole:
config.SetType("ecs_ram_role").
SetRoleName(cp.RamRoleName)
case RsaKeyPair:
config.SetType("rsa_key_pair").
SetPrivateKeyFile(cp.PrivateKey).
SetPublicKeyId(cp.KeyPairName).
SetSessionExpiration(cp.ExpiredSeconds).
SetSTSEndpoint(getSTSEndpoint(cp.StsRegion))
case RamRoleArnWithEcs:
config.SetType("ecs_ram_role").
SetRoleName(cp.RamRoleName)
client, err := credentialsv2.NewCredential(config)
if err != nil {
return nil, err
}
// 从 ECS RAM Role 获取中间 STS
model, err := client.GetCredential()
if err != nil {
return nil, err
}
// 扮演最终角色
config.SetType("ram_role_arn").
SetAccessKeyId(*model.AccessKeyId).
SetAccessKeySecret(*model.AccessKeySecret).
SetSecurityToken(*model.SecurityToken).
SetRoleArn(cp.RamRoleArn).
SetRoleSessionName(cp.RoleSessionName).
SetRoleSessionExpiration(cp.ExpiredSeconds).
SetSTSEndpoint(getSTSEndpoint(cp.StsRegion))
case ChainableRamRoleArn:
profileName := cp.SourceProfile
// 从 configuration 中重新获取 source profile
source, loaded := cp.parent.GetProfile(profileName)
if !loaded {
err = fmt.Errorf("can not load the source profile: " + profileName)
return
}
source.parent = cp.parent
source.parent.CurrentProfile = profileName
middle, err2 := source.GetCredential(ctx, proxyHost)
if err2 != nil {
err = err2
return
}
// 从上游处获得中间 AK/STS
model, err3 := middle.GetCredential()
if err3 != nil {
err = err3
return
}
// 扮演最终角色
config.SetType("ram_role_arn").
SetAccessKeyId(*model.AccessKeyId).
SetAccessKeySecret(*model.AccessKeySecret).
SetRoleArn(cp.RamRoleArn).
SetRoleSessionName(cp.RoleSessionName).
SetRoleSessionExpiration(cp.ExpiredSeconds).
SetExternalId(cp.ExternalId).
SetSTSEndpoint(getSTSEndpoint(cp.StsRegion))
if model.SecurityToken != nil {
config.SetSecurityToken(*model.SecurityToken)
}
case External:
args := strings.Fields(cp.ProcessCommand)
cmd := exec.Command(args[0], args[1:]...)
// 创建一个buffer来捕获标准输出
var stdoutBuf bytes.Buffer
cmd.Stdout = &stdoutBuf
// 将标准错误输出直接传递到终端
cmd.Stderr = os.Stderr
cmd.Stdin = os.Stdin
// 执行命令
err = cmd.Run()
if err != nil {
return nil, err
}
// 只解析标准输出
buf := stdoutBuf.Bytes()
// 解析得到新的 profile 配置
err = json.Unmarshal(buf, cp)
if err != nil {
fmt.Println(cp.ProcessCommand)
fmt.Println(string(buf))
return nil, err
}
return cp.GetCredential(ctx, proxyHost)
case CredentialsURI:
uri := cp.CredentialsURI
if uri == "" {
uri = os.Getenv("ALIBABA_CLOUD_CREDENTIALS_URI")
}
if uri == "" {
return nil, fmt.Errorf("invalid credentials uri")
}
res, err := http.Get(uri)
if err != nil {
return nil, err
}
if res.StatusCode != 200 {
return nil, fmt.Errorf("get credentials from %s failed, status code %d", uri, res.StatusCode)
}
body, err := io.ReadAll(res.Body)
res.Body.Close()
if err != nil {
return nil, err
}
type Response struct {
Code string
AccessKeyId string
AccessKeySecret string
SecurityToken string
Expiration string
}
var response Response
err = json.Unmarshal(body, &response)
if err != nil {
return nil, fmt.Errorf("unmarshal credentials failed, the body %s", string(body))
}
if response.Code != "Success" {
return nil, fmt.Errorf("get sts token err, Code is not Success")
}
config.SetType("sts").
SetAccessKeyId(response.AccessKeyId).
SetAccessKeySecret(response.AccessKeySecret).
SetSecurityToken(response.SecurityToken)
case OIDC:
config.SetType("oidc_role_arn").
SetOIDCProviderArn(cp.OIDCProviderARN).
SetOIDCTokenFilePath(cp.OIDCTokenFile).
SetRoleArn(cp.RamRoleArn).
SetRoleSessionName(cp.RoleSessionName).
SetSTSEndpoint(getSTSEndpoint(cp.StsRegion)).
SetSessionExpiration(3600)
case CloudSSO:
// check sts expiration
stsExpiration := cp.StsExpiration
currentUnixTime := util.GetCurrentUnixTime()
httpClient := util.NewHttpClient()
// check access token expiration
if cp.CloudSSOSignInUrl == "" || cp.CloudSSOAccountId == "" || cp.CloudSSOAccessConfig == "" {
reLoginCommand := fmt.Sprintf("aliyun configure --profile %s --mode CloudSSO", cp.Name)
return nil, fmt.Errorf(i18n.T(
"CloudSSO sign in url or account id or access config is empty, please configure with command: %s",
"CloudSSO登录链接或账号ID或访问配置无效,请通过命令:%s 重新完成配置").GetMessage(), reLoginCommand)
}
if cp.CloudSSOAccessTokenExpire == 0 || cp.CloudSSOAccessTokenExpire <= currentUnixTime {
// not support refresh access token yet, need to re-login
var reLoginCommand string
reLoginCommand = fmt.Sprintf("aliyun configure --profile %s", cp.Name)
return nil, fmt.Errorf(i18n.T(
"CloudSSO access token is expired, please re-login with command: %s",
"CloudSSO访问令牌已过期,请通过命令:%s 重新登录").GetMessage(), reLoginCommand)
}
if stsExpiration == 0 || stsExpiration <= currentUnixTime ||
cp.AccessKeyId == "" || cp.AccessKeySecret == "" || cp.StsToken == "" {
token, err := tryRefreshStsTokenFunc(&cp.CloudSSOSignInUrl,
&cp.AccessToken, &cp.CloudSSOAccessConfig, &cp.CloudSSOAccountId, httpClient)
if err != nil {
println(i18n.T("Create STS from CloudSSO failed", "从 CloudSSO 接口创建STS凭证失败,请重试或检查配置是否错误").GetMessage())
return nil, err
}
// update
cp.AccessKeyId = token.AccessKeyId
cp.AccessKeySecret = token.AccessKeySecret
cp.StsToken = token.SecurityToken
// update expiration
cp.StsExpiration = token.ExpirationInt64 - 5
// flush back
conf, err := loadConfiguration()
if err != nil {
return nil, err
}
for i, profile := range conf.Profiles {
if profile.Name == cp.Name {
conf.Profiles[i] = *cp
break
}
}
err = saveConfigurationFunc(conf)
if err != nil {
return nil, err
}
}
config.SetType("sts").
SetAccessKeyId(cp.AccessKeyId).
SetAccessKeySecret(cp.AccessKeySecret).
SetSecurityToken(cp.StsToken)
default:
return nil, fmt.Errorf("unexcepted certificate mode: %s", cp.Mode)
}
if proxyHost != nil {
config.SetProxy(*proxyHost)
} else {
proxy := util.GetFromEnv("HTTPS_PROXY", "https_proxy")
if proxy != "" {
config.SetProxy(proxy)
}
}
return credentialsv2.NewCredential(config)
}
var saveConfigurationFunc = SaveConfiguration
func IsRegion(region string) bool {
if match, _ := regexp.MatchString("^[a-zA-Z0-9-]*$", region); !match {
return false
}
return true
}