ram_role_arn.go raw

   1  package providers
   2  
   3  import (
   4  	"encoding/json"
   5  	"errors"
   6  	"fmt"
   7  	"net/http"
   8  	"net/url"
   9  	"os"
  10  	"strconv"
  11  	"strings"
  12  	"time"
  13  
  14  	httputil "github.com/aliyun/credentials-go/credentials/internal/http"
  15  	"github.com/aliyun/credentials-go/credentials/internal/utils"
  16  )
  17  
  18  type assumedRoleUser struct {
  19  }
  20  
  21  type credentials struct {
  22  	SecurityToken   *string `json:"SecurityToken"`
  23  	Expiration      *string `json:"Expiration"`
  24  	AccessKeySecret *string `json:"AccessKeySecret"`
  25  	AccessKeyId     *string `json:"AccessKeyId"`
  26  }
  27  
  28  type assumeRoleResponse struct {
  29  	RequestID       *string          `json:"RequestId"`
  30  	AssumedRoleUser *assumedRoleUser `json:"AssumedRoleUser"`
  31  	Credentials     *credentials     `json:"Credentials"`
  32  }
  33  
  34  type sessionCredentials struct {
  35  	AccessKeyId     string
  36  	AccessKeySecret string
  37  	SecurityToken   string
  38  	Expiration      string
  39  }
  40  
  41  type HttpOptions struct {
  42  	Proxy string
  43  	// Connection timeout, in milliseconds.
  44  	ConnectTimeout int
  45  	// Read timeout, in milliseconds.
  46  	ReadTimeout int
  47  }
  48  
  49  type RAMRoleARNCredentialsProvider struct {
  50  	// for previous credentials
  51  	accessKeyId         string
  52  	accessKeySecret     string
  53  	securityToken       string
  54  	credentialsProvider CredentialsProvider
  55  
  56  	roleArn         string
  57  	roleSessionName string
  58  	durationSeconds int
  59  	policy          string
  60  	externalId      string
  61  	// for sts endpoint
  62  	stsRegionId string
  63  	enableVpc   bool
  64  	stsEndpoint string
  65  	// for http options
  66  	httpOptions *HttpOptions
  67  	// inner
  68  	expirationTimestamp  int64
  69  	lastUpdateTimestamp  int64
  70  	previousProviderName string
  71  	sessionCredentials   *sessionCredentials
  72  }
  73  
  74  type RAMRoleARNCredentialsProviderBuilder struct {
  75  	provider *RAMRoleARNCredentialsProvider
  76  }
  77  
  78  func NewRAMRoleARNCredentialsProviderBuilder() *RAMRoleARNCredentialsProviderBuilder {
  79  	return &RAMRoleARNCredentialsProviderBuilder{
  80  		provider: &RAMRoleARNCredentialsProvider{},
  81  	}
  82  }
  83  
  84  func (builder *RAMRoleARNCredentialsProviderBuilder) WithAccessKeyId(accessKeyId string) *RAMRoleARNCredentialsProviderBuilder {
  85  	builder.provider.accessKeyId = accessKeyId
  86  	return builder
  87  }
  88  
  89  func (builder *RAMRoleARNCredentialsProviderBuilder) WithAccessKeySecret(accessKeySecret string) *RAMRoleARNCredentialsProviderBuilder {
  90  	builder.provider.accessKeySecret = accessKeySecret
  91  	return builder
  92  }
  93  
  94  func (builder *RAMRoleARNCredentialsProviderBuilder) WithSecurityToken(securityToken string) *RAMRoleARNCredentialsProviderBuilder {
  95  	builder.provider.securityToken = securityToken
  96  	return builder
  97  }
  98  
  99  func (builder *RAMRoleARNCredentialsProviderBuilder) WithCredentialsProvider(credentialsProvider CredentialsProvider) *RAMRoleARNCredentialsProviderBuilder {
 100  	builder.provider.credentialsProvider = credentialsProvider
 101  	return builder
 102  }
 103  
 104  func (builder *RAMRoleARNCredentialsProviderBuilder) WithRoleArn(roleArn string) *RAMRoleARNCredentialsProviderBuilder {
 105  	builder.provider.roleArn = roleArn
 106  	return builder
 107  }
 108  
 109  func (builder *RAMRoleARNCredentialsProviderBuilder) WithStsRegionId(regionId string) *RAMRoleARNCredentialsProviderBuilder {
 110  	builder.provider.stsRegionId = regionId
 111  	return builder
 112  }
 113  
 114  func (builder *RAMRoleARNCredentialsProviderBuilder) WithEnableVpc(enableVpc bool) *RAMRoleARNCredentialsProviderBuilder {
 115  	builder.provider.enableVpc = enableVpc
 116  	return builder
 117  }
 118  
 119  func (builder *RAMRoleARNCredentialsProviderBuilder) WithStsEndpoint(endpoint string) *RAMRoleARNCredentialsProviderBuilder {
 120  	builder.provider.stsEndpoint = endpoint
 121  	return builder
 122  }
 123  
 124  func (builder *RAMRoleARNCredentialsProviderBuilder) WithRoleSessionName(roleSessionName string) *RAMRoleARNCredentialsProviderBuilder {
 125  	builder.provider.roleSessionName = roleSessionName
 126  	return builder
 127  }
 128  
 129  func (builder *RAMRoleARNCredentialsProviderBuilder) WithPolicy(policy string) *RAMRoleARNCredentialsProviderBuilder {
 130  	builder.provider.policy = policy
 131  	return builder
 132  }
 133  
 134  func (builder *RAMRoleARNCredentialsProviderBuilder) WithExternalId(externalId string) *RAMRoleARNCredentialsProviderBuilder {
 135  	builder.provider.externalId = externalId
 136  	return builder
 137  }
 138  
 139  func (builder *RAMRoleARNCredentialsProviderBuilder) WithDurationSeconds(durationSeconds int) *RAMRoleARNCredentialsProviderBuilder {
 140  	builder.provider.durationSeconds = durationSeconds
 141  	return builder
 142  }
 143  
 144  func (builder *RAMRoleARNCredentialsProviderBuilder) WithHttpOptions(httpOptions *HttpOptions) *RAMRoleARNCredentialsProviderBuilder {
 145  	builder.provider.httpOptions = httpOptions
 146  	return builder
 147  }
 148  
 149  func (builder *RAMRoleARNCredentialsProviderBuilder) Build() (provider *RAMRoleARNCredentialsProvider, err error) {
 150  	if builder.provider.credentialsProvider == nil {
 151  		if builder.provider.accessKeyId != "" && builder.provider.accessKeySecret != "" && builder.provider.securityToken != "" {
 152  			builder.provider.credentialsProvider, err = NewStaticSTSCredentialsProviderBuilder().
 153  				WithAccessKeyId(builder.provider.accessKeyId).
 154  				WithAccessKeySecret(builder.provider.accessKeySecret).
 155  				WithSecurityToken(builder.provider.securityToken).
 156  				Build()
 157  			if err != nil {
 158  				return
 159  			}
 160  		} else if builder.provider.accessKeyId != "" && builder.provider.accessKeySecret != "" {
 161  			builder.provider.credentialsProvider, err = NewStaticAKCredentialsProviderBuilder().
 162  				WithAccessKeyId(builder.provider.accessKeyId).
 163  				WithAccessKeySecret(builder.provider.accessKeySecret).
 164  				Build()
 165  			if err != nil {
 166  				return
 167  			}
 168  		} else {
 169  			err = errors.New("must specify a previous credentials provider to assume role")
 170  			return
 171  		}
 172  	}
 173  
 174  	if builder.provider.roleArn == "" {
 175  		if roleArn := os.Getenv("ALIBABA_CLOUD_ROLE_ARN"); roleArn != "" {
 176  			builder.provider.roleArn = roleArn
 177  		} else {
 178  			err = errors.New("the RoleArn is empty")
 179  			return
 180  		}
 181  	}
 182  
 183  	if builder.provider.roleSessionName == "" {
 184  		if roleSessionName := os.Getenv("ALIBABA_CLOUD_ROLE_SESSION_NAME"); roleSessionName != "" {
 185  			builder.provider.roleSessionName = roleSessionName
 186  		} else {
 187  			builder.provider.roleSessionName = "credentials-go-" + strconv.FormatInt(time.Now().UnixNano()/1000, 10)
 188  		}
 189  	}
 190  
 191  	// duration seconds
 192  	if builder.provider.durationSeconds == 0 {
 193  		// default to 3600
 194  		builder.provider.durationSeconds = 3600
 195  	}
 196  
 197  	if builder.provider.durationSeconds < 900 {
 198  		err = errors.New("session duration should be in the range of 900s - max session duration")
 199  		return
 200  	}
 201  
 202  	// sts endpoint
 203  	if builder.provider.stsEndpoint == "" {
 204  		if !builder.provider.enableVpc {
 205  			builder.provider.enableVpc = strings.ToLower(os.Getenv("ALIBABA_CLOUD_VPC_ENDPOINT_ENABLED")) == "true"
 206  		}
 207  		prefix := "sts"
 208  		if builder.provider.enableVpc {
 209  			prefix = "sts-vpc"
 210  		}
 211  		if builder.provider.stsRegionId != "" {
 212  			builder.provider.stsEndpoint = fmt.Sprintf("%s.%s.aliyuncs.com", prefix, builder.provider.stsRegionId)
 213  		} else if region := os.Getenv("ALIBABA_CLOUD_STS_REGION"); region != "" {
 214  			builder.provider.stsEndpoint = fmt.Sprintf("%s.%s.aliyuncs.com", prefix, region)
 215  		} else {
 216  			builder.provider.stsEndpoint = "sts.aliyuncs.com"
 217  		}
 218  	}
 219  
 220  	provider = builder.provider
 221  	return
 222  }
 223  
 224  func (provider *RAMRoleARNCredentialsProvider) getCredentials(cc *Credentials) (session *sessionCredentials, err error) {
 225  	method := "POST"
 226  	req := &httputil.Request{
 227  		Method:   method,
 228  		Protocol: "https",
 229  		Host:     provider.stsEndpoint,
 230  		Headers:  map[string]string{},
 231  	}
 232  
 233  	queries := make(map[string]string)
 234  	queries["Version"] = "2015-04-01"
 235  	queries["Action"] = "AssumeRole"
 236  	queries["Format"] = "JSON"
 237  	queries["Timestamp"] = utils.GetTimeInFormatISO8601()
 238  	queries["SignatureMethod"] = "HMAC-SHA1"
 239  	queries["SignatureVersion"] = "1.0"
 240  	queries["SignatureNonce"] = utils.GetNonce()
 241  	queries["AccessKeyId"] = cc.AccessKeyId
 242  
 243  	if cc.SecurityToken != "" {
 244  		queries["SecurityToken"] = cc.SecurityToken
 245  	}
 246  
 247  	bodyForm := make(map[string]string)
 248  	bodyForm["RoleArn"] = provider.roleArn
 249  	if provider.policy != "" {
 250  		bodyForm["Policy"] = provider.policy
 251  	}
 252  	if provider.externalId != "" {
 253  		bodyForm["ExternalId"] = provider.externalId
 254  	}
 255  	bodyForm["RoleSessionName"] = provider.roleSessionName
 256  	bodyForm["DurationSeconds"] = strconv.Itoa(provider.durationSeconds)
 257  	req.Form = bodyForm
 258  
 259  	// caculate signature
 260  	signParams := make(map[string]string)
 261  	for key, value := range queries {
 262  		signParams[key] = value
 263  	}
 264  	for key, value := range bodyForm {
 265  		signParams[key] = value
 266  	}
 267  
 268  	stringToSign := utils.GetURLFormedMap(signParams)
 269  	stringToSign = strings.Replace(stringToSign, "+", "%20", -1)
 270  	stringToSign = strings.Replace(stringToSign, "*", "%2A", -1)
 271  	stringToSign = strings.Replace(stringToSign, "%7E", "~", -1)
 272  	stringToSign = url.QueryEscape(stringToSign)
 273  	stringToSign = method + "&%2F&" + stringToSign
 274  	secret := cc.AccessKeySecret + "&"
 275  	queries["Signature"] = utils.ShaHmac1(stringToSign, secret)
 276  
 277  	req.Queries = queries
 278  
 279  	// set headers
 280  	req.Headers["Accept-Encoding"] = "identity"
 281  	req.Headers["Content-Type"] = "application/x-www-form-urlencoded"
 282  	req.Headers["x-acs-credentials-provider"] = cc.ProviderName
 283  
 284  	connectTimeout := 5 * time.Second
 285  	readTimeout := 10 * time.Second
 286  
 287  	if provider.httpOptions != nil && provider.httpOptions.ConnectTimeout > 0 {
 288  		connectTimeout = time.Duration(provider.httpOptions.ConnectTimeout) * time.Millisecond
 289  	}
 290  	if provider.httpOptions != nil && provider.httpOptions.ReadTimeout > 0 {
 291  		readTimeout = time.Duration(provider.httpOptions.ReadTimeout) * time.Millisecond
 292  	}
 293  	if provider.httpOptions != nil && provider.httpOptions.Proxy != "" {
 294  		req.Proxy = provider.httpOptions.Proxy
 295  	}
 296  	req.ConnectTimeout = connectTimeout
 297  	req.ReadTimeout = readTimeout
 298  
 299  	res, err := httpDo(req)
 300  	if err != nil {
 301  		return
 302  	}
 303  
 304  	if res.StatusCode != http.StatusOK {
 305  		err = errors.New("refresh session token failed: " + string(res.Body))
 306  		return
 307  	}
 308  	var data assumeRoleResponse
 309  	err = json.Unmarshal(res.Body, &data)
 310  	if err != nil {
 311  		err = fmt.Errorf("refresh RoleArn sts token err, json.Unmarshal fail: %s", err.Error())
 312  		return
 313  	}
 314  	if data.Credentials == nil {
 315  		err = fmt.Errorf("refresh RoleArn sts token err, fail to get credentials")
 316  		return
 317  	}
 318  
 319  	if data.Credentials.AccessKeyId == nil || data.Credentials.AccessKeySecret == nil || data.Credentials.SecurityToken == nil {
 320  		err = fmt.Errorf("refresh RoleArn sts token err, fail to get credentials")
 321  		return
 322  	}
 323  
 324  	session = &sessionCredentials{
 325  		AccessKeyId:     *data.Credentials.AccessKeyId,
 326  		AccessKeySecret: *data.Credentials.AccessKeySecret,
 327  		SecurityToken:   *data.Credentials.SecurityToken,
 328  		Expiration:      *data.Credentials.Expiration,
 329  	}
 330  	return
 331  }
 332  
 333  func (provider *RAMRoleARNCredentialsProvider) needUpdateCredential() (result bool) {
 334  	if provider.expirationTimestamp == 0 {
 335  		return true
 336  	}
 337  
 338  	return provider.expirationTimestamp-time.Now().Unix() <= 180
 339  }
 340  
 341  func (provider *RAMRoleARNCredentialsProvider) GetCredentials() (cc *Credentials, err error) {
 342  	if provider.sessionCredentials == nil || provider.needUpdateCredential() {
 343  		// 获取前置凭证
 344  		previousCredentials, err1 := provider.credentialsProvider.GetCredentials()
 345  		if err1 != nil {
 346  			return nil, err1
 347  		}
 348  		sessionCredentials, err2 := provider.getCredentials(previousCredentials)
 349  		if err2 != nil {
 350  			return nil, err2
 351  		}
 352  
 353  		expirationTime, err := time.Parse("2006-01-02T15:04:05Z", sessionCredentials.Expiration)
 354  		if err != nil {
 355  			return nil, err
 356  		}
 357  
 358  		provider.expirationTimestamp = expirationTime.Unix()
 359  		provider.lastUpdateTimestamp = time.Now().Unix()
 360  		provider.previousProviderName = previousCredentials.ProviderName
 361  		provider.sessionCredentials = sessionCredentials
 362  	}
 363  
 364  	cc = &Credentials{
 365  		AccessKeyId:     provider.sessionCredentials.AccessKeyId,
 366  		AccessKeySecret: provider.sessionCredentials.AccessKeySecret,
 367  		SecurityToken:   provider.sessionCredentials.SecurityToken,
 368  		ProviderName:    fmt.Sprintf("%s/%s", provider.GetProviderName(), provider.previousProviderName),
 369  	}
 370  	return
 371  }
 372  
 373  func (provider *RAMRoleARNCredentialsProvider) GetProviderName() string {
 374  	return "ram_role_arn"
 375  }
 376