sdk/auth/credentials/credentials.go (1,160 lines of code) (raw):
package credentials
import (
"context"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"net/url"
"os"
"strconv"
"strings"
"time"
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/errors"
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/utils"
)
type assumedRoleUser struct {
}
type credentials struct {
SecurityToken *string `json:"SecurityToken"`
Expiration *string `json:"Expiration"`
AccessKeySecret *string `json:"AccessKeySecret"`
AccessKeyId *string `json:"AccessKeyId"`
}
type ecsRAMRoleCredentials struct {
SecurityToken *string `json:"SecurityToken"`
Expiration *string `json:"Expiration"`
AccessKeySecret *string `json:"AccessKeySecret"`
AccessKeyId *string `json:"AccessKeyId"`
LastUpdated *string `json:"LastUpdated"`
Code *string `json:"Code"`
}
type assumeRoleResponse struct {
RequestID *string `json:"RequestId"`
AssumedRoleUser *assumedRoleUser `json:"AssumedRoleUser"`
Credentials *credentials `json:"Credentials"`
}
type generateSessionAccessKeyResponse struct {
RequestID *string `json:"RequestId"`
SessionAccessKey *sessionAccessKey `json:"SessionAccessKey"`
}
type sessionAccessKey struct {
SessionAccessKeyId *string `json:"SessionAccessKeyId"`
SessionAccessKeySecret *string `json:"SessionAccessKeySecret"`
Expiration *string `json:"Expiration"`
}
type SessionCredentials struct {
AccessKeyId string
AccessKeySecret string
SecurityToken string
Expiration string
}
type Credentials struct {
AccessKeyId string
AccessKeySecret string
SecurityToken string
BearerToken string
ProviderName string
}
type do func(req *http.Request) (*http.Response, error)
var hookDo = func(fn do) do {
return fn
}
type newReuqest func(method, url string, body io.Reader) (*http.Request, error)
var hookNewRequest = func(fn newReuqest) newReuqest {
return fn
}
type HttpOptions struct {
// Connection timeout
ConnectTimeout time.Duration
// Read timeout
ReadTimeout time.Duration
}
type CredentialsProvider interface {
GetCredentials() (cc *Credentials, err error)
GetProviderName() string
}
type StaticAKCredentialsProvider struct {
accessKeyId string
accessKeySecret string
}
type StaticAKCredentialsProviderBuilder struct {
provider *StaticAKCredentialsProvider
}
func NewStaticAKCredentialsProviderBuilder() *StaticAKCredentialsProviderBuilder {
return &StaticAKCredentialsProviderBuilder{
provider: &StaticAKCredentialsProvider{},
}
}
func (builder *StaticAKCredentialsProviderBuilder) WithAccessKeyId(accessKeyId string) *StaticAKCredentialsProviderBuilder {
builder.provider.accessKeyId = accessKeyId
return builder
}
func (builder *StaticAKCredentialsProviderBuilder) WithAccessKeySecret(accessKeySecret string) *StaticAKCredentialsProviderBuilder {
builder.provider.accessKeySecret = accessKeySecret
return builder
}
func (builder *StaticAKCredentialsProviderBuilder) Build() (provider *StaticAKCredentialsProvider, err error) {
if builder.provider.accessKeyId == "" {
builder.provider.accessKeyId = os.Getenv("ALIBABA_CLOUD_ACCESS_KEY_ID")
}
if builder.provider.accessKeyId == "" {
err = errors.NewClientError(errors.InvalidParamErrorCode, "The access key id is empty", nil)
return
}
if builder.provider.accessKeySecret == "" {
builder.provider.accessKeySecret = os.Getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET")
}
if builder.provider.accessKeySecret == "" {
err = errors.NewClientError(errors.InvalidParamErrorCode, "The access key secret is empty", nil)
return
}
provider = builder.provider
return
}
func NewStaticAKCredentialsProvider(accessKeyId, accessKeySecret string) *StaticAKCredentialsProvider {
return &StaticAKCredentialsProvider{
accessKeyId: accessKeyId,
accessKeySecret: accessKeySecret,
}
}
func (provider *StaticAKCredentialsProvider) GetCredentials() (cc *Credentials, err error) {
cc = &Credentials{
AccessKeyId: provider.accessKeyId,
AccessKeySecret: provider.accessKeySecret,
ProviderName: provider.GetProviderName(),
}
return
}
func (provider *StaticAKCredentialsProvider) GetProviderName() string {
return "static_ak"
}
type StaticSTSCredentialsProvider struct {
accessKeyId string
accessKeySecret string
securityToken string
}
type StaticSTSCredentialsProviderBuilder struct {
provider *StaticSTSCredentialsProvider
}
func NewStaticSTSCredentialsProviderBuilder() *StaticSTSCredentialsProviderBuilder {
return &StaticSTSCredentialsProviderBuilder{
provider: &StaticSTSCredentialsProvider{},
}
}
func (builder *StaticSTSCredentialsProviderBuilder) WithAccessKeyId(accessKeyId string) *StaticSTSCredentialsProviderBuilder {
builder.provider.accessKeyId = accessKeyId
return builder
}
func (builder *StaticSTSCredentialsProviderBuilder) WithAccessKeySecret(accessKeySecret string) *StaticSTSCredentialsProviderBuilder {
builder.provider.accessKeySecret = accessKeySecret
return builder
}
func (builder *StaticSTSCredentialsProviderBuilder) WithSecurityToken(securityToken string) *StaticSTSCredentialsProviderBuilder {
builder.provider.securityToken = securityToken
return builder
}
func (builder *StaticSTSCredentialsProviderBuilder) Build() (provider *StaticSTSCredentialsProvider, err error) {
if builder.provider.accessKeyId == "" {
builder.provider.accessKeyId = os.Getenv("ALIBABA_CLOUD_ACCESS_KEY_ID")
}
if builder.provider.accessKeyId == "" {
err = errors.NewClientError(errors.InvalidParamErrorCode, "The access key id is empty", nil)
return
}
if builder.provider.accessKeySecret == "" {
builder.provider.accessKeySecret = os.Getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET")
}
if builder.provider.accessKeySecret == "" {
err = errors.NewClientError(errors.InvalidParamErrorCode, "The access key secret is empty", nil)
return
}
if builder.provider.securityToken == "" {
builder.provider.securityToken = os.Getenv("ALIBABA_CLOUD_SECURITY_TOKEN")
}
if builder.provider.securityToken == "" {
err = errors.NewClientError(errors.InvalidParamErrorCode, "The security token is empty", nil)
return
}
provider = builder.provider
return
}
func NewStaticSTSCredentialsProvider(accessKeyId, accessKeySecret, securityToken string) *StaticSTSCredentialsProvider {
return &StaticSTSCredentialsProvider{
accessKeyId: accessKeyId,
accessKeySecret: accessKeySecret,
securityToken: securityToken,
}
}
func (provider *StaticSTSCredentialsProvider) GetCredentials() (cc *Credentials, err error) {
cc = &Credentials{
AccessKeyId: provider.accessKeyId,
AccessKeySecret: provider.accessKeySecret,
SecurityToken: provider.securityToken,
ProviderName: provider.GetProviderName(),
}
return
}
func (provider *StaticSTSCredentialsProvider) GetProviderName() string {
return "static_sts"
}
type BearerTokenCredentialsProvider struct {
bearerToken string
}
func NewBearerTokenCredentialsProvider(bearerToken string) *BearerTokenCredentialsProvider {
return &BearerTokenCredentialsProvider{
bearerToken: bearerToken,
}
}
func (provider *BearerTokenCredentialsProvider) GetCredentials() (cc *Credentials, err error) {
cc = &Credentials{
BearerToken: provider.bearerToken,
ProviderName: provider.GetProviderName(),
}
return
}
func (provider *BearerTokenCredentialsProvider) GetProviderName() string {
return "bearer_token"
}
// Deprecated: the RSA key pair credentials is deprecated
type RSAKeyPairCredentialsProvider struct {
PublicKeyId string
PrivateKeyId string
durationSeconds int
sessionAccessKey *sessionAccessKey
lastUpdateTimestamp int64
expirationTimestamp int64
}
// Deprecated: the RSA key pair credentials is deprecated
func NewRSAKeyPairCredentialsProvider(publicKeyId, privateKeyId string, durationSeconds int) (provider *RSAKeyPairCredentialsProvider, err error) {
provider = &RSAKeyPairCredentialsProvider{
PublicKeyId: publicKeyId,
PrivateKeyId: privateKeyId,
}
if durationSeconds > 0 {
if durationSeconds >= 900 && durationSeconds <= 3600 {
provider.durationSeconds = durationSeconds
} else {
err = errors.NewClientError(errors.InvalidParamErrorCode, "Key Pair session duration should be in the range of 15min - 1hr", nil)
}
} else {
// set to default value
provider.durationSeconds = 3600
}
return
}
// Deprecated: the RSA key pair credentials is deprecated
func (provider *RSAKeyPairCredentialsProvider) GetCredentials() (cc *Credentials, err error) {
if provider.sessionAccessKey == nil || provider.needUpdateCredential() {
sessionAccessKey, err := provider.getCredentials()
if err != nil {
return nil, err
}
expirationTime, err := time.Parse("2006-01-02T15:04:05Z", *sessionAccessKey.Expiration)
if err != nil {
return nil, err
}
provider.sessionAccessKey = sessionAccessKey
provider.lastUpdateTimestamp = time.Now().Unix()
provider.expirationTimestamp = expirationTime.Unix()
}
cc = &Credentials{
AccessKeyId: *provider.sessionAccessKey.SessionAccessKeyId,
AccessKeySecret: *provider.sessionAccessKey.SessionAccessKeySecret,
ProviderName: provider.GetProviderName(),
}
return
}
func (provider *RSAKeyPairCredentialsProvider) needUpdateCredential() bool {
if provider.expirationTimestamp == 0 {
return true
}
return provider.expirationTimestamp-time.Now().Unix() <= 180
}
func (provider *RSAKeyPairCredentialsProvider) getCredentials() (sessionAK *sessionAccessKey, err error) {
method := "POST"
host := "sts.ap-northeast-1.aliyuncs.com"
queries := make(map[string]string)
queries["Version"] = "2015-04-01"
queries["Action"] = "GenerateSessionAccessKey"
queries["Format"] = "JSON"
queries["Timestamp"] = utils.GetTimeInFormatISO8601()
queries["SignatureMethod"] = "SHA256withRSA"
queries["SignatureVersion"] = "1.0"
queries["SignatureNonce"] = utils.GetNonce()
queries["PublicKeyId"] = provider.PublicKeyId
queries["SignatureType"] = "PRIVATEKEY"
bodyForm := make(map[string]string)
bodyForm["DurationSeconds"] = strconv.Itoa(provider.durationSeconds)
// 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
queries["Signature"] = utils.Sha256WithRsa(stringToSign, provider.PrivateKeyId)
querystring := utils.GetUrlFormedMap(queries)
// do request
httpUrl := fmt.Sprintf("https://%s/?%s", host, querystring)
body := utils.GetUrlFormedMap(bodyForm)
httpRequest, err := hookNewRequest(http.NewRequest)(method, httpUrl, strings.NewReader(body))
if err != nil {
return
}
// set headers
httpRequest.Header["Accept-Encoding"] = []string{"identity"}
httpRequest.Header["Content-Type"] = []string{"application/x-www-form-urlencoded"}
httpClient := &http.Client{}
httpResponse, err := hookDo(httpClient.Do)(httpRequest)
if err != nil {
return
}
defer httpResponse.Body.Close()
responseBody, err := ioutil.ReadAll(httpResponse.Body)
if err != nil {
return
}
if httpResponse.StatusCode != http.StatusOK {
message := "refresh temp ak failed"
err = errors.NewServerError(httpResponse.StatusCode, string(responseBody), message)
return
}
var data generateSessionAccessKeyResponse
err = json.Unmarshal(responseBody, &data)
if err != nil {
err = fmt.Errorf("refresh temp ak err, json.Unmarshal fail: %s", err.Error())
return
}
if data.SessionAccessKey == nil {
err = fmt.Errorf("refresh temp ak token err, fail to get credentials")
return
}
if data.SessionAccessKey.SessionAccessKeyId == nil || data.SessionAccessKey.SessionAccessKeySecret == nil {
err = fmt.Errorf("refresh temp ak token err, fail to get credentials")
return
}
sessionAK = data.SessionAccessKey
return
}
func (provider *RSAKeyPairCredentialsProvider) GetProviderName() string {
return "rsa_key_pair"
}
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
stsRegion 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) WithStsRegion(region string) *RAMRoleARNCredentialsProviderBuilder {
builder.provider.stsRegion = region
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.NewClientError(errors.InvalidParamErrorCode, "Must specify a previous credentials provider to assume role", nil)
return
}
}
if builder.provider.roleArn == "" {
if roleArn := os.Getenv("ALIBABA_CLOUD_ROLE_ARN"); roleArn != "" {
builder.provider.roleArn = roleArn
} else {
err = errors.NewClientError(errors.InvalidParamErrorCode, "The RoleArn is empty", nil)
return
}
}
if builder.provider.roleSessionName == "" {
if roleSessionName := os.Getenv("ALIBABA_CLOUD_ROLE_SESSION_NAME"); roleSessionName != "" {
builder.provider.roleSessionName = roleSessionName
} else {
builder.provider.roleSessionName = "aliyun-go-sdk-" + 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.NewClientError(errors.InvalidParamErrorCode, "Session duration should be in the range of 900s - max session duration", nil)
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.stsRegion != "" {
builder.provider.stsEndpoint = fmt.Sprintf("%s.%s.aliyuncs.com", prefix, builder.provider.stsRegion)
} 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 NewRAMRoleARNCredentialsProvider(credentialsProvider CredentialsProvider, roleArn, roleSessionName string, durationSeconds int, policy, stsRegion, externalId string) (provider *RAMRoleARNCredentialsProvider, err error) {
provider = &RAMRoleARNCredentialsProvider{
credentialsProvider: credentialsProvider,
roleArn: roleArn,
durationSeconds: durationSeconds,
policy: policy,
stsRegion: stsRegion,
externalId: externalId,
}
if len(roleSessionName) > 0 {
provider.roleSessionName = roleSessionName
} else {
provider.roleSessionName = "aliyun-go-sdk-" + strconv.FormatInt(time.Now().UnixNano()/1000, 10)
}
if durationSeconds > 0 {
if durationSeconds >= 900 && durationSeconds <= 3600 {
provider.durationSeconds = durationSeconds
} else {
err = errors.NewClientError(errors.InvalidParamErrorCode, "Assume Role session duration should be in the range of 15min - 1hr", nil)
}
} else {
// default to 3600
provider.durationSeconds = 3600
}
return
}
func (provider *RAMRoleARNCredentialsProvider) getCredentials(cc *Credentials) (sessionCredentials *SessionCredentials, err error) {
method := "POST"
var host string
if provider.stsEndpoint != "" {
host = provider.stsEndpoint
} else if provider.stsRegion != "" {
host = fmt.Sprintf("sts.%s.aliyuncs.com", provider.stsRegion)
} else {
host = "sts.aliyuncs.com"
}
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)
// 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)
querystring := utils.GetUrlFormedMap(queries)
// do request
httpUrl := fmt.Sprintf("https://%s/?%s", host, querystring)
body := utils.GetUrlFormedMap(bodyForm)
httpRequest, err := hookNewRequest(http.NewRequest)(method, httpUrl, strings.NewReader(body))
if err != nil {
return
}
// set headers
httpRequest.Header["Accept-Encoding"] = []string{"identity"}
httpRequest.Header["Content-Type"] = []string{"application/x-www-form-urlencoded"}
connectTimeout := 5 * time.Second
readTimeout := 10 * time.Second
if provider.httpOptions != nil && provider.httpOptions.ConnectTimeout > 0 {
connectTimeout = provider.httpOptions.ConnectTimeout
}
if provider.httpOptions != nil && provider.httpOptions.ReadTimeout > 0 {
readTimeout = provider.httpOptions.ReadTimeout
}
transport := http.DefaultTransport.(*http.Transport).Clone()
transport.DialContext = func(ctx context.Context, network, address string) (net.Conn, error) {
return (&net.Dialer{
Timeout: connectTimeout,
DualStack: true,
}).DialContext(ctx, network, address)
}
httpClient := &http.Client{
Timeout: connectTimeout + readTimeout,
Transport: transport,
}
httpResponse, err := hookDo(httpClient.Do)(httpRequest)
if err != nil {
return
}
defer httpResponse.Body.Close()
responseBody, err := ioutil.ReadAll(httpResponse.Body)
if err != nil {
return
}
if httpResponse.StatusCode != http.StatusOK {
message := "refresh session token failed"
err = errors.NewServerError(httpResponse.StatusCode, string(responseBody), message)
return
}
var data assumeRoleResponse
err = json.Unmarshal(responseBody, &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
}
sessionCredentials = &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
}
if provider.previousProviderName == "" {
provider.previousProviderName = provider.credentialsProvider.GetProviderName()
}
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"
}
type ECSRAMRoleCredentialsProvider struct {
roleName string
disableIMDSv1 bool
// for http options
httpOptions *HttpOptions
sessionCredentials *SessionCredentials
expirationTimestamp int64
}
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 = fmt.Errorf("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
}
func NewECSRAMRoleCredentialsProvider(roleName string) *ECSRAMRoleCredentialsProvider {
return &ECSRAMRoleCredentialsProvider{
roleName: roleName,
}
}
func (provider *ECSRAMRoleCredentialsProvider) needUpdateCredential() bool {
if provider.expirationTimestamp == 0 {
return true
}
return provider.expirationTimestamp-time.Now().Unix() <= 180
}
func (provider *ECSRAMRoleCredentialsProvider) getMetadataToken() (metadataToken string, err error) {
// PUT http://100.100.100.200/latest/api/token
var requestUrl = "http://100.100.100.200/latest/api/token"
httpRequest, _err := hookNewRequest(http.NewRequest)("PUT", requestUrl, strings.NewReader(""))
if _err != nil {
if provider.disableIMDSv1 {
err = fmt.Errorf("get metadata token failed: %s", _err.Error())
}
return
}
httpRequest.Header.Set("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 = provider.httpOptions.ConnectTimeout
}
if provider.httpOptions != nil && provider.httpOptions.ReadTimeout > 0 {
readTimeout = provider.httpOptions.ReadTimeout
}
transport := http.DefaultTransport.(*http.Transport).Clone()
transport.DialContext = func(ctx context.Context, network, address string) (net.Conn, error) {
return (&net.Dialer{
Timeout: connectTimeout,
DualStack: true,
}).DialContext(ctx, network, address)
}
httpClient := &http.Client{
Timeout: connectTimeout + readTimeout,
Transport: transport,
}
httpResponse, _err := hookDo(httpClient.Do)(httpRequest)
if _err != nil {
if provider.disableIMDSv1 {
err = fmt.Errorf("get metadata token failed: %s", _err.Error())
}
return
}
defer httpResponse.Body.Close()
responseBody, _err := ioutil.ReadAll(httpResponse.Body)
if _err != nil {
if provider.disableIMDSv1 {
err = fmt.Errorf("get metadata token failed: %s", _err.Error())
}
return
}
if httpResponse.StatusCode != http.StatusOK {
if provider.disableIMDSv1 {
err = errors.NewServerError(httpResponse.StatusCode, string(responseBody), "refresh Ecs sts token err")
}
return
}
metadataToken = strings.TrimSpace(string(responseBody))
return
}
func (provider *ECSRAMRoleCredentialsProvider) getRoleName() (roleName string, err error) {
connectTimeout := 1 * time.Second
readTimeout := 1 * time.Second
if provider.httpOptions != nil && provider.httpOptions.ConnectTimeout > 0 {
connectTimeout = provider.httpOptions.ConnectTimeout
}
if provider.httpOptions != nil && provider.httpOptions.ReadTimeout > 0 {
readTimeout = provider.httpOptions.ReadTimeout
}
transport := http.DefaultTransport.(*http.Transport).Clone()
transport.DialContext = func(ctx context.Context, network, address string) (net.Conn, error) {
return (&net.Dialer{
Timeout: connectTimeout,
DualStack: true,
}).DialContext(ctx, network, address)
}
httpClient := &http.Client{
Timeout: connectTimeout + readTimeout,
Transport: transport,
}
var securityCredURL = "http://100.100.100.200/latest/meta-data/ram/security-credentials/"
httpRequest, err := hookNewRequest(http.NewRequest)("GET", securityCredURL, strings.NewReader(""))
if err != nil {
err = fmt.Errorf("get role name failed: %s", err.Error())
return
}
metadataToken, err := provider.getMetadataToken()
if err != nil {
return
}
if metadataToken != "" {
httpRequest.Header.Set("X-aliyun-ecs-metadata-token", metadataToken)
}
httpResponse, err := hookDo(httpClient.Do)(httpRequest)
if err != nil {
err = fmt.Errorf("get role name failed: %s", err.Error())
return
}
if httpResponse.StatusCode != http.StatusOK {
err = fmt.Errorf("get role name failed: request %s %d", securityCredURL, httpResponse.StatusCode)
return
}
defer httpResponse.Body.Close()
responseBody, err := ioutil.ReadAll(httpResponse.Body)
if err != nil {
return
}
roleName = strings.TrimSpace(string(responseBody))
return
}
func (provider *ECSRAMRoleCredentialsProvider) getCredentials() (sessionCredentials *SessionCredentials, err error) {
roleName := provider.roleName
if roleName == "" {
roleName, err = provider.getRoleName()
if err != nil {
return
}
}
connectTimeout := 1 * time.Second
readTimeout := 1 * time.Second
if provider.httpOptions != nil && provider.httpOptions.ConnectTimeout > 0 {
connectTimeout = provider.httpOptions.ConnectTimeout
}
if provider.httpOptions != nil && provider.httpOptions.ReadTimeout > 0 {
readTimeout = provider.httpOptions.ReadTimeout
}
transport := http.DefaultTransport.(*http.Transport).Clone()
transport.DialContext = func(ctx context.Context, network, address string) (net.Conn, error) {
return (&net.Dialer{
Timeout: connectTimeout,
DualStack: true,
}).DialContext(ctx, network, address)
}
httpClient := &http.Client{
Timeout: connectTimeout + readTimeout,
Transport: transport,
}
var requestUrl = "http://100.100.100.200/latest/meta-data/ram/security-credentials/" + roleName
httpRequest, err := hookNewRequest(http.NewRequest)("GET", requestUrl, strings.NewReader(""))
if err != nil {
err = fmt.Errorf("refresh Ecs sts token err: %s", err.Error())
return
}
metadataToken, err := provider.getMetadataToken()
if err != nil {
return
}
if metadataToken != "" {
httpRequest.Header.Set("X-aliyun-ecs-metadata-token", metadataToken)
}
httpResponse, err := hookDo(httpClient.Do)(httpRequest)
if err != nil {
err = fmt.Errorf("refresh Ecs sts token err: %s", err.Error())
return
}
defer httpResponse.Body.Close()
responseBody, err := ioutil.ReadAll(httpResponse.Body)
if err != nil {
return
}
if httpResponse.StatusCode != http.StatusOK {
err = fmt.Errorf("refresh Ecs sts token err, httpStatus: %d, message = %s", httpResponse.StatusCode, string(responseBody))
return
}
var data ecsRAMRoleCredentials
err = json.Unmarshal(responseBody, &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
}
sessionCredentials = &SessionCredentials{
AccessKeyId: *data.AccessKeyId,
AccessKeySecret: *data.AccessKeySecret,
SecurityToken: *data.SecurityToken,
Expiration: *data.Expiration,
}
return
}
func (provider *ECSRAMRoleCredentialsProvider) GetCredentials() (cc *Credentials, err error) {
if provider.sessionCredentials == nil || provider.needUpdateCredential() {
sessionCredentials, err1 := provider.getCredentials()
if err1 != nil {
return nil, err1
}
provider.sessionCredentials = sessionCredentials
expirationTime, err2 := time.Parse("2006-01-02T15:04:05Z", sessionCredentials.Expiration)
if err2 != nil {
return nil, err2
}
provider.expirationTimestamp = expirationTime.Unix()
}
cc = &Credentials{
AccessKeyId: provider.sessionCredentials.AccessKeyId,
AccessKeySecret: provider.sessionCredentials.AccessKeySecret,
SecurityToken: provider.sessionCredentials.SecurityToken,
ProviderName: provider.GetProviderName(),
}
return
}
func (provider *ECSRAMRoleCredentialsProvider) GetProviderName() string {
return "ecs_ram_role"
}
type OIDCCredentialsProvider struct {
oidcProviderARN string
oidcTokenFilePath string
roleArn string
roleSessionName string
durationSeconds int
policy string
// for sts endpoint
stsRegion string
enableVpc bool
stsEndpoint string
lastUpdateTimestamp int64
expirationTimestamp int64
sessionCredentials *SessionCredentials
// for http options
httpOptions *HttpOptions
}
type OIDCCredentialsProviderBuilder struct {
provider *OIDCCredentialsProvider
}
func NewOIDCCredentialsProviderBuilder() *OIDCCredentialsProviderBuilder {
return &OIDCCredentialsProviderBuilder{
provider: &OIDCCredentialsProvider{},
}
}
func (b *OIDCCredentialsProviderBuilder) WithOIDCProviderARN(oidcProviderArn string) *OIDCCredentialsProviderBuilder {
b.provider.oidcProviderARN = oidcProviderArn
return b
}
func (b *OIDCCredentialsProviderBuilder) WithOIDCTokenFilePath(oidcTokenFilePath string) *OIDCCredentialsProviderBuilder {
b.provider.oidcTokenFilePath = oidcTokenFilePath
return b
}
func (b *OIDCCredentialsProviderBuilder) WithRoleArn(roleArn string) *OIDCCredentialsProviderBuilder {
b.provider.roleArn = roleArn
return b
}
func (b *OIDCCredentialsProviderBuilder) WithRoleSessionName(roleSessionName string) *OIDCCredentialsProviderBuilder {
b.provider.roleSessionName = roleSessionName
return b
}
func (b *OIDCCredentialsProviderBuilder) WithDurationSeconds(durationSeconds int) *OIDCCredentialsProviderBuilder {
b.provider.durationSeconds = durationSeconds
return b
}
func (b *OIDCCredentialsProviderBuilder) WithStsRegion(region string) *OIDCCredentialsProviderBuilder {
b.provider.stsRegion = region
return b
}
func (b *OIDCCredentialsProviderBuilder) WithEnableVpc(enableVpc bool) *OIDCCredentialsProviderBuilder {
b.provider.enableVpc = enableVpc
return b
}
func (b *OIDCCredentialsProviderBuilder) WithSTSEndpoint(stsEndpoint string) *OIDCCredentialsProviderBuilder {
b.provider.stsEndpoint = stsEndpoint
return b
}
func (b *OIDCCredentialsProviderBuilder) WithPolicy(policy string) *OIDCCredentialsProviderBuilder {
b.provider.policy = policy
return b
}
func (b *OIDCCredentialsProviderBuilder) WithHttpOptions(httpOptions *HttpOptions) *OIDCCredentialsProviderBuilder {
b.provider.httpOptions = httpOptions
return b
}
func (b *OIDCCredentialsProviderBuilder) Build() (provider *OIDCCredentialsProvider, err error) {
provider = b.provider
if provider.roleSessionName == "" {
provider.roleSessionName = "aliyun-go-sdk-" + strconv.FormatInt(time.Now().UnixNano()/1000, 10)
}
if provider.oidcTokenFilePath == "" {
provider.oidcTokenFilePath = os.Getenv("ALIBABA_CLOUD_OIDC_TOKEN_FILE")
}
if provider.oidcTokenFilePath == "" {
err = errors.NewClientError(errors.InvalidParamErrorCode, "OIDCTokenFilePath can not be empty", nil)
return
}
if provider.oidcProviderARN == "" {
provider.oidcProviderARN = os.Getenv("ALIBABA_CLOUD_OIDC_PROVIDER_ARN")
}
if provider.oidcProviderARN == "" {
err = errors.NewClientError(errors.InvalidParamErrorCode, "OIDCProviderARN can not be empty", nil)
return
}
if provider.roleArn == "" {
provider.roleArn = os.Getenv("ALIBABA_CLOUD_ROLE_ARN")
}
if provider.roleArn == "" {
err = errors.NewClientError(errors.InvalidParamErrorCode, "RoleArn can not be empty", nil)
return
}
if provider.durationSeconds == 0 {
provider.durationSeconds = 3600
}
if provider.durationSeconds < 900 || provider.durationSeconds > 3600 {
err = errors.NewClientError(errors.InvalidParamErrorCode, "Assume Role session duration should be in the range of 15min - 1hr", nil)
}
// sts endpoint
if provider.stsEndpoint == "" {
if !provider.enableVpc {
provider.enableVpc = strings.ToLower(os.Getenv("ALIBABA_CLOUD_VPC_ENDPOINT_ENABLED")) == "true"
}
prefix := "sts"
if provider.enableVpc {
prefix = "sts-vpc"
}
if provider.stsRegion != "" {
provider.stsEndpoint = fmt.Sprintf("%s.%s.aliyuncs.com", prefix, provider.stsRegion)
} else if region := os.Getenv("ALIBABA_CLOUD_STS_REGION"); region != "" {
provider.stsEndpoint = fmt.Sprintf("%s.%s.aliyuncs.com", prefix, region)
} else {
provider.stsEndpoint = "sts.aliyuncs.com"
}
}
return
}
func (provider *OIDCCredentialsProvider) getCredentials() (sessionCredentials *SessionCredentials, err error) {
method := "POST"
var host string
if provider.stsEndpoint != "" {
host = provider.stsEndpoint
} else if provider.stsRegion != "" {
host = fmt.Sprintf("sts.%s.aliyuncs.com", provider.stsRegion)
} else {
host = "sts.aliyuncs.com"
}
queries := make(map[string]string)
queries["Version"] = "2015-04-01"
queries["Action"] = "AssumeRoleWithOIDC"
queries["Format"] = "JSON"
queries["Timestamp"] = utils.GetTimeInFormatISO8601()
bodyForm := make(map[string]string)
bodyForm["RoleArn"] = provider.roleArn
bodyForm["OIDCProviderArn"] = provider.oidcProviderARN
token, err := ioutil.ReadFile(provider.oidcTokenFilePath)
if err != nil {
return
}
bodyForm["OIDCToken"] = string(token)
if provider.policy != "" {
bodyForm["Policy"] = provider.policy
}
bodyForm["RoleSessionName"] = provider.roleSessionName
bodyForm["DurationSeconds"] = strconv.Itoa(provider.durationSeconds)
// caculate signature
signParams := make(map[string]string)
for key, value := range queries {
signParams[key] = value
}
for key, value := range bodyForm {
signParams[key] = value
}
querystring := utils.GetUrlFormedMap(queries)
// do request
httpUrl := fmt.Sprintf("https://%s/?%s", host, querystring)
body := utils.GetUrlFormedMap(bodyForm)
httpRequest, err := hookNewRequest(http.NewRequest)(method, httpUrl, strings.NewReader(body))
if err != nil {
return
}
// set headers
httpRequest.Header["Accept-Encoding"] = []string{"identity"}
httpRequest.Header["Content-Type"] = []string{"application/x-www-form-urlencoded"}
connectTimeout := 5 * time.Second
readTimeout := 10 * time.Second
if provider.httpOptions != nil && provider.httpOptions.ConnectTimeout > 0 {
connectTimeout = provider.httpOptions.ConnectTimeout
}
if provider.httpOptions != nil && provider.httpOptions.ReadTimeout > 0 {
readTimeout = provider.httpOptions.ReadTimeout
}
transport := http.DefaultTransport.(*http.Transport).Clone()
transport.DialContext = func(ctx context.Context, network, address string) (net.Conn, error) {
return (&net.Dialer{
Timeout: connectTimeout,
DualStack: true,
}).DialContext(ctx, network, address)
}
httpClient := &http.Client{
Timeout: connectTimeout + readTimeout,
Transport: transport,
}
httpResponse, err := hookDo(httpClient.Do)(httpRequest)
if err != nil {
return
}
defer httpResponse.Body.Close()
responseBody, err := ioutil.ReadAll(httpResponse.Body)
if err != nil {
return
}
if httpResponse.StatusCode != http.StatusOK {
message := "get session token failed"
err = errors.NewServerError(httpResponse.StatusCode, string(responseBody), message)
return
}
var data assumeRoleResponse
err = json.Unmarshal(responseBody, &data)
if err != nil {
err = fmt.Errorf("get oidc sts token err, json.Unmarshal fail: %s", err.Error())
return
}
if data.Credentials == nil {
err = fmt.Errorf("get oidc 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
}
sessionCredentials = &SessionCredentials{
AccessKeyId: *data.Credentials.AccessKeyId,
AccessKeySecret: *data.Credentials.AccessKeySecret,
SecurityToken: *data.Credentials.SecurityToken,
Expiration: *data.Credentials.Expiration,
}
return
}
func (provider *OIDCCredentialsProvider) needUpdateCredential() (result bool) {
if provider.expirationTimestamp == 0 {
return true
}
return provider.expirationTimestamp-time.Now().Unix() <= 180
}
func (provider *OIDCCredentialsProvider) GetCredentials() (cc *Credentials, err error) {
if provider.sessionCredentials == nil || provider.needUpdateCredential() {
sessionCredentials, err1 := provider.getCredentials()
if err1 != nil {
return nil, err1
}
provider.sessionCredentials = sessionCredentials
expirationTime, err2 := time.Parse("2006-01-02T15:04:05Z", sessionCredentials.Expiration)
if err2 != nil {
return nil, err2
}
provider.lastUpdateTimestamp = time.Now().Unix()
provider.expirationTimestamp = expirationTime.Unix()
}
cc = &Credentials{
AccessKeyId: provider.sessionCredentials.AccessKeyId,
AccessKeySecret: provider.sessionCredentials.AccessKeySecret,
SecurityToken: provider.sessionCredentials.SecurityToken,
ProviderName: provider.GetProviderName(),
}
return
}
func (provider *OIDCCredentialsProvider) GetProviderName() string {
return "oidc_role_arn"
}