credentials/providers/ram_role_arn.go (311 lines of code) (raw):
package providers
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"net/url"
"os"
"strconv"
"strings"
"time"
httputil "github.com/aliyun/credentials-go/credentials/internal/http"
"github.com/aliyun/credentials-go/credentials/internal/utils"
)
type assumedRoleUser struct {
}
type credentials struct {
SecurityToken *string `json:"SecurityToken"`
Expiration *string `json:"Expiration"`
AccessKeySecret *string `json:"AccessKeySecret"`
AccessKeyId *string `json:"AccessKeyId"`
}
type assumeRoleResponse struct {
RequestID *string `json:"RequestId"`
AssumedRoleUser *assumedRoleUser `json:"AssumedRoleUser"`
Credentials *credentials `json:"Credentials"`
}
type sessionCredentials struct {
AccessKeyId string
AccessKeySecret string
SecurityToken string
Expiration string
}
type HttpOptions struct {
Proxy string
// Connection timeout, in milliseconds.
ConnectTimeout int
// Read timeout, in milliseconds.
ReadTimeout int
}
type RAMRoleARNCredentialsProvider struct {
// for previous credentials
accessKeyId string
accessKeySecret string
securityToken string
credentialsProvider CredentialsProvider
roleArn string
roleSessionName string
durationSeconds int
policy string
externalId string
// for sts endpoint
stsRegionId string
enableVpc bool
stsEndpoint string
// for http options
httpOptions *HttpOptions
// inner
expirationTimestamp int64
lastUpdateTimestamp int64
previousProviderName string
sessionCredentials *sessionCredentials
}
type RAMRoleARNCredentialsProviderBuilder struct {
provider *RAMRoleARNCredentialsProvider
}
func NewRAMRoleARNCredentialsProviderBuilder() *RAMRoleARNCredentialsProviderBuilder {
return &RAMRoleARNCredentialsProviderBuilder{
provider: &RAMRoleARNCredentialsProvider{},
}
}
func (builder *RAMRoleARNCredentialsProviderBuilder) WithAccessKeyId(accessKeyId string) *RAMRoleARNCredentialsProviderBuilder {
builder.provider.accessKeyId = accessKeyId
return builder
}
func (builder *RAMRoleARNCredentialsProviderBuilder) WithAccessKeySecret(accessKeySecret string) *RAMRoleARNCredentialsProviderBuilder {
builder.provider.accessKeySecret = accessKeySecret
return builder
}
func (builder *RAMRoleARNCredentialsProviderBuilder) WithSecurityToken(securityToken string) *RAMRoleARNCredentialsProviderBuilder {
builder.provider.securityToken = securityToken
return builder
}
func (builder *RAMRoleARNCredentialsProviderBuilder) WithCredentialsProvider(credentialsProvider CredentialsProvider) *RAMRoleARNCredentialsProviderBuilder {
builder.provider.credentialsProvider = credentialsProvider
return builder
}
func (builder *RAMRoleARNCredentialsProviderBuilder) WithRoleArn(roleArn string) *RAMRoleARNCredentialsProviderBuilder {
builder.provider.roleArn = roleArn
return builder
}
func (builder *RAMRoleARNCredentialsProviderBuilder) WithStsRegionId(regionId string) *RAMRoleARNCredentialsProviderBuilder {
builder.provider.stsRegionId = regionId
return builder
}
func (builder *RAMRoleARNCredentialsProviderBuilder) WithEnableVpc(enableVpc bool) *RAMRoleARNCredentialsProviderBuilder {
builder.provider.enableVpc = enableVpc
return builder
}
func (builder *RAMRoleARNCredentialsProviderBuilder) WithStsEndpoint(endpoint string) *RAMRoleARNCredentialsProviderBuilder {
builder.provider.stsEndpoint = endpoint
return builder
}
func (builder *RAMRoleARNCredentialsProviderBuilder) WithRoleSessionName(roleSessionName string) *RAMRoleARNCredentialsProviderBuilder {
builder.provider.roleSessionName = roleSessionName
return builder
}
func (builder *RAMRoleARNCredentialsProviderBuilder) WithPolicy(policy string) *RAMRoleARNCredentialsProviderBuilder {
builder.provider.policy = policy
return builder
}
func (builder *RAMRoleARNCredentialsProviderBuilder) WithExternalId(externalId string) *RAMRoleARNCredentialsProviderBuilder {
builder.provider.externalId = externalId
return builder
}
func (builder *RAMRoleARNCredentialsProviderBuilder) WithDurationSeconds(durationSeconds int) *RAMRoleARNCredentialsProviderBuilder {
builder.provider.durationSeconds = durationSeconds
return builder
}
func (builder *RAMRoleARNCredentialsProviderBuilder) WithHttpOptions(httpOptions *HttpOptions) *RAMRoleARNCredentialsProviderBuilder {
builder.provider.httpOptions = httpOptions
return builder
}
func (builder *RAMRoleARNCredentialsProviderBuilder) Build() (provider *RAMRoleARNCredentialsProvider, err error) {
if builder.provider.credentialsProvider == nil {
if builder.provider.accessKeyId != "" && builder.provider.accessKeySecret != "" && builder.provider.securityToken != "" {
builder.provider.credentialsProvider, err = NewStaticSTSCredentialsProviderBuilder().
WithAccessKeyId(builder.provider.accessKeyId).
WithAccessKeySecret(builder.provider.accessKeySecret).
WithSecurityToken(builder.provider.securityToken).
Build()
if err != nil {
return
}
} else if builder.provider.accessKeyId != "" && builder.provider.accessKeySecret != "" {
builder.provider.credentialsProvider, err = NewStaticAKCredentialsProviderBuilder().
WithAccessKeyId(builder.provider.accessKeyId).
WithAccessKeySecret(builder.provider.accessKeySecret).
Build()
if err != nil {
return
}
} else {
err = errors.New("must specify a previous credentials provider to assume role")
return
}
}
if builder.provider.roleArn == "" {
if roleArn := os.Getenv("ALIBABA_CLOUD_ROLE_ARN"); roleArn != "" {
builder.provider.roleArn = roleArn
} else {
err = errors.New("the RoleArn is empty")
return
}
}
if builder.provider.roleSessionName == "" {
if roleSessionName := os.Getenv("ALIBABA_CLOUD_ROLE_SESSION_NAME"); roleSessionName != "" {
builder.provider.roleSessionName = roleSessionName
} else {
builder.provider.roleSessionName = "credentials-go-" + strconv.FormatInt(time.Now().UnixNano()/1000, 10)
}
}
// duration seconds
if builder.provider.durationSeconds == 0 {
// default to 3600
builder.provider.durationSeconds = 3600
}
if builder.provider.durationSeconds < 900 {
err = errors.New("session duration should be in the range of 900s - max session duration")
return
}
// sts endpoint
if builder.provider.stsEndpoint == "" {
if !builder.provider.enableVpc {
builder.provider.enableVpc = strings.ToLower(os.Getenv("ALIBABA_CLOUD_VPC_ENDPOINT_ENABLED")) == "true"
}
prefix := "sts"
if builder.provider.enableVpc {
prefix = "sts-vpc"
}
if builder.provider.stsRegionId != "" {
builder.provider.stsEndpoint = fmt.Sprintf("%s.%s.aliyuncs.com", prefix, builder.provider.stsRegionId)
} else if region := os.Getenv("ALIBABA_CLOUD_STS_REGION"); region != "" {
builder.provider.stsEndpoint = fmt.Sprintf("%s.%s.aliyuncs.com", prefix, region)
} else {
builder.provider.stsEndpoint = "sts.aliyuncs.com"
}
}
provider = builder.provider
return
}
func (provider *RAMRoleARNCredentialsProvider) getCredentials(cc *Credentials) (session *sessionCredentials, err error) {
method := "POST"
req := &httputil.Request{
Method: method,
Protocol: "https",
Host: provider.stsEndpoint,
Headers: map[string]string{},
}
queries := make(map[string]string)
queries["Version"] = "2015-04-01"
queries["Action"] = "AssumeRole"
queries["Format"] = "JSON"
queries["Timestamp"] = utils.GetTimeInFormatISO8601()
queries["SignatureMethod"] = "HMAC-SHA1"
queries["SignatureVersion"] = "1.0"
queries["SignatureNonce"] = utils.GetNonce()
queries["AccessKeyId"] = cc.AccessKeyId
if cc.SecurityToken != "" {
queries["SecurityToken"] = cc.SecurityToken
}
bodyForm := make(map[string]string)
bodyForm["RoleArn"] = provider.roleArn
if provider.policy != "" {
bodyForm["Policy"] = provider.policy
}
if provider.externalId != "" {
bodyForm["ExternalId"] = provider.externalId
}
bodyForm["RoleSessionName"] = provider.roleSessionName
bodyForm["DurationSeconds"] = strconv.Itoa(provider.durationSeconds)
req.Form = bodyForm
// caculate signature
signParams := make(map[string]string)
for key, value := range queries {
signParams[key] = value
}
for key, value := range bodyForm {
signParams[key] = value
}
stringToSign := utils.GetURLFormedMap(signParams)
stringToSign = strings.Replace(stringToSign, "+", "%20", -1)
stringToSign = strings.Replace(stringToSign, "*", "%2A", -1)
stringToSign = strings.Replace(stringToSign, "%7E", "~", -1)
stringToSign = url.QueryEscape(stringToSign)
stringToSign = method + "&%2F&" + stringToSign
secret := cc.AccessKeySecret + "&"
queries["Signature"] = utils.ShaHmac1(stringToSign, secret)
req.Queries = queries
// set headers
req.Headers["Accept-Encoding"] = "identity"
req.Headers["Content-Type"] = "application/x-www-form-urlencoded"
req.Headers["x-acs-credentials-provider"] = cc.ProviderName
connectTimeout := 5 * time.Second
readTimeout := 10 * 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 {
return
}
if res.StatusCode != http.StatusOK {
err = errors.New("refresh session token failed: " + string(res.Body))
return
}
var data assumeRoleResponse
err = json.Unmarshal(res.Body, &data)
if err != nil {
err = fmt.Errorf("refresh RoleArn sts token err, json.Unmarshal fail: %s", err.Error())
return
}
if data.Credentials == nil {
err = fmt.Errorf("refresh RoleArn sts token err, fail to get credentials")
return
}
if data.Credentials.AccessKeyId == nil || data.Credentials.AccessKeySecret == nil || data.Credentials.SecurityToken == nil {
err = fmt.Errorf("refresh RoleArn sts token err, fail to get credentials")
return
}
session = &sessionCredentials{
AccessKeyId: *data.Credentials.AccessKeyId,
AccessKeySecret: *data.Credentials.AccessKeySecret,
SecurityToken: *data.Credentials.SecurityToken,
Expiration: *data.Credentials.Expiration,
}
return
}
func (provider *RAMRoleARNCredentialsProvider) needUpdateCredential() (result bool) {
if provider.expirationTimestamp == 0 {
return true
}
return provider.expirationTimestamp-time.Now().Unix() <= 180
}
func (provider *RAMRoleARNCredentialsProvider) GetCredentials() (cc *Credentials, err error) {
if provider.sessionCredentials == nil || provider.needUpdateCredential() {
// 获取前置凭证
previousCredentials, err1 := provider.credentialsProvider.GetCredentials()
if err1 != nil {
return nil, err1
}
sessionCredentials, err2 := provider.getCredentials(previousCredentials)
if err2 != nil {
return nil, err2
}
expirationTime, err := time.Parse("2006-01-02T15:04:05Z", sessionCredentials.Expiration)
if err != nil {
return nil, err
}
provider.expirationTimestamp = expirationTime.Unix()
provider.lastUpdateTimestamp = time.Now().Unix()
provider.previousProviderName = previousCredentials.ProviderName
provider.sessionCredentials = sessionCredentials
}
cc = &Credentials{
AccessKeyId: provider.sessionCredentials.AccessKeyId,
AccessKeySecret: provider.sessionCredentials.AccessKeySecret,
SecurityToken: provider.sessionCredentials.SecurityToken,
ProviderName: fmt.Sprintf("%s/%s", provider.GetProviderName(), provider.previousProviderName),
}
return
}
func (provider *RAMRoleARNCredentialsProvider) GetProviderName() string {
return "ram_role_arn"
}