github.com/aliyun/credentials-go@v1.4.7/credentials/providers/ecs_ram_role.go (about)

     1  package providers
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  	"os"
     8  	"strconv"
     9  	"strings"
    10  	"time"
    11  
    12  	httputil "github.com/aliyun/credentials-go/credentials/internal/http"
    13  )
    14  
    15  type ECSRAMRoleCredentialsProvider struct {
    16  	roleName      string
    17  	disableIMDSv1 bool
    18  	// for sts
    19  	session             *sessionCredentials
    20  	expirationTimestamp int64
    21  	// for http options
    22  	httpOptions *HttpOptions
    23  }
    24  
    25  type ECSRAMRoleCredentialsProviderBuilder struct {
    26  	provider *ECSRAMRoleCredentialsProvider
    27  }
    28  
    29  func NewECSRAMRoleCredentialsProviderBuilder() *ECSRAMRoleCredentialsProviderBuilder {
    30  	return &ECSRAMRoleCredentialsProviderBuilder{
    31  		provider: &ECSRAMRoleCredentialsProvider{},
    32  	}
    33  }
    34  
    35  func (builder *ECSRAMRoleCredentialsProviderBuilder) WithRoleName(roleName string) *ECSRAMRoleCredentialsProviderBuilder {
    36  	builder.provider.roleName = roleName
    37  	return builder
    38  }
    39  
    40  func (builder *ECSRAMRoleCredentialsProviderBuilder) WithDisableIMDSv1(disableIMDSv1 bool) *ECSRAMRoleCredentialsProviderBuilder {
    41  	builder.provider.disableIMDSv1 = disableIMDSv1
    42  	return builder
    43  }
    44  
    45  func (builder *ECSRAMRoleCredentialsProviderBuilder) WithHttpOptions(httpOptions *HttpOptions) *ECSRAMRoleCredentialsProviderBuilder {
    46  	builder.provider.httpOptions = httpOptions
    47  	return builder
    48  }
    49  
    50  const defaultMetadataTokenDuration = 21600 // 6 hours
    51  
    52  func (builder *ECSRAMRoleCredentialsProviderBuilder) Build() (provider *ECSRAMRoleCredentialsProvider, err error) {
    53  
    54  	if strings.ToLower(os.Getenv("ALIBABA_CLOUD_ECS_METADATA_DISABLED")) == "true" {
    55  		err = errors.New("IMDS credentials is disabled")
    56  		return
    57  	}
    58  
    59  	// 设置 roleName 默认值
    60  	if builder.provider.roleName == "" {
    61  		builder.provider.roleName = os.Getenv("ALIBABA_CLOUD_ECS_METADATA")
    62  	}
    63  
    64  	if !builder.provider.disableIMDSv1 {
    65  		builder.provider.disableIMDSv1 = strings.ToLower(os.Getenv("ALIBABA_CLOUD_IMDSV1_DISABLED")) == "true"
    66  	}
    67  
    68  	provider = builder.provider
    69  	return
    70  }
    71  
    72  type ecsRAMRoleResponse struct {
    73  	Code            *string `json:"Code"`
    74  	AccessKeyId     *string `json:"AccessKeyId"`
    75  	AccessKeySecret *string `json:"AccessKeySecret"`
    76  	SecurityToken   *string `json:"SecurityToken"`
    77  	LastUpdated     *string `json:"LastUpdated"`
    78  	Expiration      *string `json:"Expiration"`
    79  }
    80  
    81  func (provider *ECSRAMRoleCredentialsProvider) needUpdateCredential() bool {
    82  	if provider.expirationTimestamp == 0 {
    83  		return true
    84  	}
    85  
    86  	return provider.expirationTimestamp-time.Now().Unix() <= 180
    87  }
    88  
    89  func (provider *ECSRAMRoleCredentialsProvider) getRoleName() (roleName string, err error) {
    90  	req := &httputil.Request{
    91  		Method:   "GET",
    92  		Protocol: "http",
    93  		Host:     "100.100.100.200",
    94  		Path:     "/latest/meta-data/ram/security-credentials/",
    95  		Headers:  map[string]string{},
    96  	}
    97  
    98  	connectTimeout := 1 * time.Second
    99  	readTimeout := 1 * time.Second
   100  
   101  	if provider.httpOptions != nil && provider.httpOptions.ConnectTimeout > 0 {
   102  		connectTimeout = time.Duration(provider.httpOptions.ConnectTimeout) * time.Millisecond
   103  	}
   104  	if provider.httpOptions != nil && provider.httpOptions.ReadTimeout > 0 {
   105  		readTimeout = time.Duration(provider.httpOptions.ReadTimeout) * time.Millisecond
   106  	}
   107  	if provider.httpOptions != nil && provider.httpOptions.Proxy != "" {
   108  		req.Proxy = provider.httpOptions.Proxy
   109  	}
   110  	req.ConnectTimeout = connectTimeout
   111  	req.ReadTimeout = readTimeout
   112  
   113  	metadataToken, err := provider.getMetadataToken()
   114  	if err != nil {
   115  		return "", err
   116  	}
   117  	if metadataToken != "" {
   118  		req.Headers["x-aliyun-ecs-metadata-token"] = metadataToken
   119  	}
   120  
   121  	res, err := httpDo(req)
   122  	if err != nil {
   123  		err = fmt.Errorf("get role name failed: %s", err.Error())
   124  		return
   125  	}
   126  
   127  	if res.StatusCode != 200 {
   128  		err = fmt.Errorf("get role name failed: %s %d", req.BuildRequestURL(), res.StatusCode)
   129  		return
   130  	}
   131  
   132  	roleName = strings.TrimSpace(string(res.Body))
   133  	return
   134  }
   135  
   136  func (provider *ECSRAMRoleCredentialsProvider) getCredentials() (session *sessionCredentials, err error) {
   137  	roleName := provider.roleName
   138  	if roleName == "" {
   139  		roleName, err = provider.getRoleName()
   140  		if err != nil {
   141  			return
   142  		}
   143  	}
   144  
   145  	req := &httputil.Request{
   146  		Method:   "GET",
   147  		Protocol: "http",
   148  		Host:     "100.100.100.200",
   149  		Path:     "/latest/meta-data/ram/security-credentials/" + roleName,
   150  		Headers:  map[string]string{},
   151  	}
   152  
   153  	connectTimeout := 1 * time.Second
   154  	readTimeout := 1 * time.Second
   155  
   156  	if provider.httpOptions != nil && provider.httpOptions.ConnectTimeout > 0 {
   157  		connectTimeout = time.Duration(provider.httpOptions.ConnectTimeout) * time.Millisecond
   158  	}
   159  	if provider.httpOptions != nil && provider.httpOptions.ReadTimeout > 0 {
   160  		readTimeout = time.Duration(provider.httpOptions.ReadTimeout) * time.Millisecond
   161  	}
   162  	if provider.httpOptions != nil && provider.httpOptions.Proxy != "" {
   163  		req.Proxy = provider.httpOptions.Proxy
   164  	}
   165  	req.ConnectTimeout = connectTimeout
   166  	req.ReadTimeout = readTimeout
   167  
   168  	metadataToken, err := provider.getMetadataToken()
   169  	if err != nil {
   170  		return nil, err
   171  	}
   172  	if metadataToken != "" {
   173  		req.Headers["x-aliyun-ecs-metadata-token"] = metadataToken
   174  	}
   175  
   176  	res, err := httpDo(req)
   177  	if err != nil {
   178  		err = fmt.Errorf("refresh Ecs sts token err: %s", err.Error())
   179  		return
   180  	}
   181  
   182  	if res.StatusCode != 200 {
   183  		err = fmt.Errorf("refresh Ecs sts token err, httpStatus: %d, message = %s", res.StatusCode, string(res.Body))
   184  		return
   185  	}
   186  
   187  	var data ecsRAMRoleResponse
   188  	err = json.Unmarshal(res.Body, &data)
   189  	if err != nil {
   190  		err = fmt.Errorf("refresh Ecs sts token err, json.Unmarshal fail: %s", err.Error())
   191  		return
   192  	}
   193  
   194  	if data.AccessKeyId == nil || data.AccessKeySecret == nil || data.SecurityToken == nil {
   195  		err = fmt.Errorf("refresh Ecs sts token err, fail to get credentials")
   196  		return
   197  	}
   198  
   199  	if *data.Code != "Success" {
   200  		err = fmt.Errorf("refresh Ecs sts token err, Code is not Success")
   201  		return
   202  	}
   203  
   204  	session = &sessionCredentials{
   205  		AccessKeyId:     *data.AccessKeyId,
   206  		AccessKeySecret: *data.AccessKeySecret,
   207  		SecurityToken:   *data.SecurityToken,
   208  		Expiration:      *data.Expiration,
   209  	}
   210  	return
   211  }
   212  
   213  func (provider *ECSRAMRoleCredentialsProvider) GetCredentials() (cc *Credentials, err error) {
   214  	if provider.session == nil || provider.needUpdateCredential() {
   215  		session, err1 := provider.getCredentials()
   216  		if err1 != nil {
   217  			return nil, err1
   218  		}
   219  
   220  		provider.session = session
   221  		expirationTime, err2 := time.Parse("2006-01-02T15:04:05Z", session.Expiration)
   222  		if err2 != nil {
   223  			return nil, err2
   224  		}
   225  		provider.expirationTimestamp = expirationTime.Unix()
   226  	}
   227  
   228  	cc = &Credentials{
   229  		AccessKeyId:     provider.session.AccessKeyId,
   230  		AccessKeySecret: provider.session.AccessKeySecret,
   231  		SecurityToken:   provider.session.SecurityToken,
   232  		ProviderName:    provider.GetProviderName(),
   233  	}
   234  	return
   235  }
   236  
   237  func (provider *ECSRAMRoleCredentialsProvider) GetProviderName() string {
   238  	return "ecs_ram_role"
   239  }
   240  
   241  func (provider *ECSRAMRoleCredentialsProvider) getMetadataToken() (metadataToken string, err error) {
   242  	// PUT http://100.100.100.200/latest/api/token
   243  	req := &httputil.Request{
   244  		Method:   "PUT",
   245  		Protocol: "http",
   246  		Host:     "100.100.100.200",
   247  		Path:     "/latest/api/token",
   248  		Headers: map[string]string{
   249  			"X-aliyun-ecs-metadata-token-ttl-seconds": strconv.Itoa(defaultMetadataTokenDuration),
   250  		},
   251  	}
   252  
   253  	connectTimeout := 1 * time.Second
   254  	readTimeout := 1 * time.Second
   255  
   256  	if provider.httpOptions != nil && provider.httpOptions.ConnectTimeout > 0 {
   257  		connectTimeout = time.Duration(provider.httpOptions.ConnectTimeout) * time.Millisecond
   258  	}
   259  	if provider.httpOptions != nil && provider.httpOptions.ReadTimeout > 0 {
   260  		readTimeout = time.Duration(provider.httpOptions.ReadTimeout) * time.Millisecond
   261  	}
   262  	if provider.httpOptions != nil && provider.httpOptions.Proxy != "" {
   263  		req.Proxy = provider.httpOptions.Proxy
   264  	}
   265  	req.ConnectTimeout = connectTimeout
   266  	req.ReadTimeout = readTimeout
   267  
   268  	res, _err := httpDo(req)
   269  	if _err != nil {
   270  		if provider.disableIMDSv1 {
   271  			err = fmt.Errorf("get metadata token failed: %s", _err.Error())
   272  		}
   273  		return
   274  	}
   275  	if res.StatusCode != 200 {
   276  		if provider.disableIMDSv1 {
   277  			err = fmt.Errorf("refresh Ecs sts token err, httpStatus: %d, message = %s", res.StatusCode, string(res.Body))
   278  		}
   279  		return
   280  	}
   281  	metadataToken = string(res.Body)
   282  	return
   283  }