ecs_ram_role.go raw

   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  }
 284