credentials/providers/ecs_ram_role.go (236 lines of code) (raw):

package providers import ( "encoding/json" "errors" "fmt" "os" "strconv" "strings" "time" httputil "github.com/aliyun/credentials-go/credentials/internal/http" ) type ECSRAMRoleCredentialsProvider struct { roleName string disableIMDSv1 bool // for sts session *sessionCredentials expirationTimestamp int64 // for http options httpOptions *HttpOptions } type ECSRAMRoleCredentialsProviderBuilder struct { provider *ECSRAMRoleCredentialsProvider } func NewECSRAMRoleCredentialsProviderBuilder() *ECSRAMRoleCredentialsProviderBuilder { return &ECSRAMRoleCredentialsProviderBuilder{ provider: &ECSRAMRoleCredentialsProvider{}, } } func (builder *ECSRAMRoleCredentialsProviderBuilder) WithRoleName(roleName string) *ECSRAMRoleCredentialsProviderBuilder { builder.provider.roleName = roleName return builder } func (builder *ECSRAMRoleCredentialsProviderBuilder) WithDisableIMDSv1(disableIMDSv1 bool) *ECSRAMRoleCredentialsProviderBuilder { builder.provider.disableIMDSv1 = disableIMDSv1 return builder } func (builder *ECSRAMRoleCredentialsProviderBuilder) WithHttpOptions(httpOptions *HttpOptions) *ECSRAMRoleCredentialsProviderBuilder { builder.provider.httpOptions = httpOptions return builder } const defaultMetadataTokenDuration = 21600 // 6 hours func (builder *ECSRAMRoleCredentialsProviderBuilder) Build() (provider *ECSRAMRoleCredentialsProvider, err error) { if strings.ToLower(os.Getenv("ALIBABA_CLOUD_ECS_METADATA_DISABLED")) == "true" { err = errors.New("IMDS credentials is disabled") return } // 设置 roleName 默认值 if builder.provider.roleName == "" { builder.provider.roleName = os.Getenv("ALIBABA_CLOUD_ECS_METADATA") } if !builder.provider.disableIMDSv1 { builder.provider.disableIMDSv1 = strings.ToLower(os.Getenv("ALIBABA_CLOUD_IMDSV1_DISABLED")) == "true" } provider = builder.provider return } type ecsRAMRoleResponse struct { Code *string `json:"Code"` AccessKeyId *string `json:"AccessKeyId"` AccessKeySecret *string `json:"AccessKeySecret"` SecurityToken *string `json:"SecurityToken"` LastUpdated *string `json:"LastUpdated"` Expiration *string `json:"Expiration"` } func (provider *ECSRAMRoleCredentialsProvider) needUpdateCredential() bool { if provider.expirationTimestamp == 0 { return true } return provider.expirationTimestamp-time.Now().Unix() <= 180 } func (provider *ECSRAMRoleCredentialsProvider) getRoleName() (roleName string, err error) { req := &httputil.Request{ Method: "GET", Protocol: "http", Host: "100.100.100.200", Path: "/latest/meta-data/ram/security-credentials/", Headers: map[string]string{}, } connectTimeout := 1 * time.Second readTimeout := 1 * time.Second if provider.httpOptions != nil && provider.httpOptions.ConnectTimeout > 0 { connectTimeout = time.Duration(provider.httpOptions.ConnectTimeout) * time.Millisecond } if provider.httpOptions != nil && provider.httpOptions.ReadTimeout > 0 { readTimeout = time.Duration(provider.httpOptions.ReadTimeout) * time.Millisecond } if provider.httpOptions != nil && provider.httpOptions.Proxy != "" { req.Proxy = provider.httpOptions.Proxy } req.ConnectTimeout = connectTimeout req.ReadTimeout = readTimeout metadataToken, err := provider.getMetadataToken() if err != nil { return "", err } if metadataToken != "" { req.Headers["x-aliyun-ecs-metadata-token"] = metadataToken } res, err := httpDo(req) if err != nil { err = fmt.Errorf("get role name failed: %s", err.Error()) return } if res.StatusCode != 200 { err = fmt.Errorf("get role name failed: %s %d", req.BuildRequestURL(), res.StatusCode) return } roleName = strings.TrimSpace(string(res.Body)) return } func (provider *ECSRAMRoleCredentialsProvider) getCredentials() (session *sessionCredentials, err error) { roleName := provider.roleName if roleName == "" { roleName, err = provider.getRoleName() if err != nil { return } } req := &httputil.Request{ Method: "GET", Protocol: "http", Host: "100.100.100.200", Path: "/latest/meta-data/ram/security-credentials/" + roleName, Headers: map[string]string{}, } connectTimeout := 1 * time.Second readTimeout := 1 * time.Second if provider.httpOptions != nil && provider.httpOptions.ConnectTimeout > 0 { connectTimeout = time.Duration(provider.httpOptions.ConnectTimeout) * time.Millisecond } if provider.httpOptions != nil && provider.httpOptions.ReadTimeout > 0 { readTimeout = time.Duration(provider.httpOptions.ReadTimeout) * time.Millisecond } if provider.httpOptions != nil && provider.httpOptions.Proxy != "" { req.Proxy = provider.httpOptions.Proxy } req.ConnectTimeout = connectTimeout req.ReadTimeout = readTimeout metadataToken, err := provider.getMetadataToken() if err != nil { return nil, err } if metadataToken != "" { req.Headers["x-aliyun-ecs-metadata-token"] = metadataToken } res, err := httpDo(req) if err != nil { err = fmt.Errorf("refresh Ecs sts token err: %s", err.Error()) return } if res.StatusCode != 200 { err = fmt.Errorf("refresh Ecs sts token err, httpStatus: %d, message = %s", res.StatusCode, string(res.Body)) return } var data ecsRAMRoleResponse err = json.Unmarshal(res.Body, &data) if err != nil { err = fmt.Errorf("refresh Ecs sts token err, json.Unmarshal fail: %s", err.Error()) return } if data.AccessKeyId == nil || data.AccessKeySecret == nil || data.SecurityToken == nil { err = fmt.Errorf("refresh Ecs sts token err, fail to get credentials") return } if *data.Code != "Success" { err = fmt.Errorf("refresh Ecs sts token err, Code is not Success") return } session = &sessionCredentials{ AccessKeyId: *data.AccessKeyId, AccessKeySecret: *data.AccessKeySecret, SecurityToken: *data.SecurityToken, Expiration: *data.Expiration, } return } func (provider *ECSRAMRoleCredentialsProvider) GetCredentials() (cc *Credentials, err error) { if provider.session == nil || provider.needUpdateCredential() { session, err1 := provider.getCredentials() if err1 != nil { return nil, err1 } provider.session = session expirationTime, err2 := time.Parse("2006-01-02T15:04:05Z", session.Expiration) if err2 != nil { return nil, err2 } provider.expirationTimestamp = expirationTime.Unix() } cc = &Credentials{ AccessKeyId: provider.session.AccessKeyId, AccessKeySecret: provider.session.AccessKeySecret, SecurityToken: provider.session.SecurityToken, ProviderName: provider.GetProviderName(), } return } func (provider *ECSRAMRoleCredentialsProvider) GetProviderName() string { return "ecs_ram_role" } func (provider *ECSRAMRoleCredentialsProvider) getMetadataToken() (metadataToken string, err error) { // PUT http://100.100.100.200/latest/api/token req := &httputil.Request{ Method: "PUT", Protocol: "http", Host: "100.100.100.200", Path: "/latest/api/token", Headers: map[string]string{ "X-aliyun-ecs-metadata-token-ttl-seconds": strconv.Itoa(defaultMetadataTokenDuration), }, } connectTimeout := 1 * time.Second readTimeout := 1 * time.Second if provider.httpOptions != nil && provider.httpOptions.ConnectTimeout > 0 { connectTimeout = time.Duration(provider.httpOptions.ConnectTimeout) * time.Millisecond } if provider.httpOptions != nil && provider.httpOptions.ReadTimeout > 0 { readTimeout = time.Duration(provider.httpOptions.ReadTimeout) * time.Millisecond } if provider.httpOptions != nil && provider.httpOptions.Proxy != "" { req.Proxy = provider.httpOptions.Proxy } req.ConnectTimeout = connectTimeout req.ReadTimeout = readTimeout res, _err := httpDo(req) if _err != nil { if provider.disableIMDSv1 { err = fmt.Errorf("get metadata token failed: %s", _err.Error()) } return } if res.StatusCode != 200 { if provider.disableIMDSv1 { err = fmt.Errorf("refresh Ecs sts token err, httpStatus: %d, message = %s", res.StatusCode, string(res.Body)) } return } metadataToken = string(res.Body) return }