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

     1  package providers
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  	"net/http"
     8  	"net/url"
     9  	"time"
    10  
    11  	httputil "github.com/aliyun/credentials-go/credentials/internal/http"
    12  )
    13  
    14  type CloudSSOCredentialsProvider struct {
    15  	signInUrl         string
    16  	accountId         string
    17  	accessConfig      string
    18  	accessToken       string
    19  	accessTokenExpire int64
    20  
    21  	lastUpdateTimestamp int64
    22  	expirationTimestamp int64
    23  	sessionCredentials  *sessionCredentials
    24  	// for http options
    25  	httpOptions *HttpOptions
    26  }
    27  
    28  type CloudSSOCredentialsProviderBuilder struct {
    29  	provider *CloudSSOCredentialsProvider
    30  }
    31  
    32  type cloudCredentialOptions struct {
    33  	AccountId             string `json:"AccountId"`
    34  	AccessConfigurationId string `json:"AccessConfigurationId"`
    35  }
    36  
    37  type cloudCredentials struct {
    38  	AccessKeyId     string `json:"AccessKeyId"`
    39  	AccessKeySecret string `json:"AccessKeySecret"`
    40  	SecurityToken   string `json:"SecurityToken"`
    41  	Expiration      string `json:"Expiration"`
    42  }
    43  
    44  type cloudCredentialResponse struct {
    45  	CloudCredential *cloudCredentials `json:"CloudCredential"`
    46  	RequestId       string            `json:"RequestId"`
    47  }
    48  
    49  func NewCloudSSOCredentialsProviderBuilder() *CloudSSOCredentialsProviderBuilder {
    50  	return &CloudSSOCredentialsProviderBuilder{
    51  		provider: &CloudSSOCredentialsProvider{},
    52  	}
    53  }
    54  
    55  func (b *CloudSSOCredentialsProviderBuilder) WithSignInUrl(signInUrl string) *CloudSSOCredentialsProviderBuilder {
    56  	b.provider.signInUrl = signInUrl
    57  	return b
    58  }
    59  
    60  func (b *CloudSSOCredentialsProviderBuilder) WithAccountId(accountId string) *CloudSSOCredentialsProviderBuilder {
    61  	b.provider.accountId = accountId
    62  	return b
    63  }
    64  
    65  func (b *CloudSSOCredentialsProviderBuilder) WithAccessConfig(accessConfig string) *CloudSSOCredentialsProviderBuilder {
    66  	b.provider.accessConfig = accessConfig
    67  	return b
    68  }
    69  
    70  func (b *CloudSSOCredentialsProviderBuilder) WithAccessToken(accessToken string) *CloudSSOCredentialsProviderBuilder {
    71  	b.provider.accessToken = accessToken
    72  	return b
    73  }
    74  
    75  func (b *CloudSSOCredentialsProviderBuilder) WithAccessTokenExpire(accessTokenExpire int64) *CloudSSOCredentialsProviderBuilder {
    76  	b.provider.accessTokenExpire = accessTokenExpire
    77  	return b
    78  }
    79  
    80  func (b *CloudSSOCredentialsProviderBuilder) WithHttpOptions(httpOptions *HttpOptions) *CloudSSOCredentialsProviderBuilder {
    81  	b.provider.httpOptions = httpOptions
    82  	return b
    83  }
    84  
    85  func (b *CloudSSOCredentialsProviderBuilder) Build() (provider *CloudSSOCredentialsProvider, err error) {
    86  	if b.provider.accessToken == "" || b.provider.accessTokenExpire == 0 || b.provider.accessTokenExpire-time.Now().Unix() <= 0 {
    87  		err = errors.New("CloudSSO access token is empty or expired, please re-login with cli")
    88  		return
    89  	}
    90  
    91  	if b.provider.signInUrl == "" || b.provider.accountId == "" || b.provider.accessConfig == "" {
    92  		err = errors.New("CloudSSO sign in url or account id or access config is empty")
    93  		return
    94  	}
    95  
    96  	provider = b.provider
    97  	return
    98  }
    99  
   100  func (provider *CloudSSOCredentialsProvider) getCredentials() (session *sessionCredentials, err error) {
   101  	url, err := url.Parse(provider.signInUrl)
   102  	if err != nil {
   103  		return nil, err
   104  	}
   105  
   106  	req := &httputil.Request{
   107  		Method:   "POST",
   108  		Protocol: url.Scheme,
   109  		Host:     url.Host,
   110  		Path:     "/cloud-credentials",
   111  		Headers:  map[string]string{},
   112  	}
   113  
   114  	connectTimeout := 5 * time.Second
   115  	readTimeout := 10 * time.Second
   116  
   117  	if provider.httpOptions != nil && provider.httpOptions.ConnectTimeout > 0 {
   118  		connectTimeout = time.Duration(provider.httpOptions.ConnectTimeout) * time.Millisecond
   119  	}
   120  	if provider.httpOptions != nil && provider.httpOptions.ReadTimeout > 0 {
   121  		readTimeout = time.Duration(provider.httpOptions.ReadTimeout) * time.Millisecond
   122  	}
   123  	if provider.httpOptions != nil && provider.httpOptions.Proxy != "" {
   124  		req.Proxy = provider.httpOptions.Proxy
   125  	}
   126  	req.ConnectTimeout = connectTimeout
   127  	req.ReadTimeout = readTimeout
   128  
   129  	body := cloudCredentialOptions{
   130  		AccountId:             provider.accountId,
   131  		AccessConfigurationId: provider.accessConfig,
   132  	}
   133  
   134  	bodyBytes, err := json.Marshal(body)
   135  	if err != nil {
   136  		return nil, fmt.Errorf("failed to marshal options: %w", err)
   137  	}
   138  
   139  	req.Body = bodyBytes
   140  
   141  	// set headers
   142  	req.Headers["Accept"] = "application/json"
   143  	req.Headers["Content-Type"] = "application/json"
   144  	req.Headers["Authorization"] = fmt.Sprintf("Bearer %s", provider.accessToken)
   145  	res, err := httpDo(req)
   146  	if err != nil {
   147  		return
   148  	}
   149  
   150  	if res.StatusCode != http.StatusOK {
   151  		message := "get session token from sso failed: "
   152  		err = errors.New(message + string(res.Body))
   153  		return
   154  	}
   155  	var data cloudCredentialResponse
   156  	err = json.Unmarshal(res.Body, &data)
   157  	if err != nil {
   158  		err = fmt.Errorf("get session token from sso failed, json.Unmarshal fail: %s", err.Error())
   159  		return
   160  	}
   161  	if data.CloudCredential == nil {
   162  		err = fmt.Errorf("get session token from sso failed, fail to get credentials")
   163  		return
   164  	}
   165  
   166  	if data.CloudCredential.AccessKeyId == "" || data.CloudCredential.AccessKeySecret == "" || data.CloudCredential.SecurityToken == "" {
   167  		err = fmt.Errorf("refresh session token err, fail to get credentials")
   168  		return
   169  	}
   170  
   171  	session = &sessionCredentials{
   172  		AccessKeyId:     data.CloudCredential.AccessKeyId,
   173  		AccessKeySecret: data.CloudCredential.AccessKeySecret,
   174  		SecurityToken:   data.CloudCredential.SecurityToken,
   175  		Expiration:      data.CloudCredential.Expiration,
   176  	}
   177  	return
   178  }
   179  
   180  func (provider *CloudSSOCredentialsProvider) needUpdateCredential() (result bool) {
   181  	if provider.expirationTimestamp == 0 {
   182  		return true
   183  	}
   184  
   185  	return provider.expirationTimestamp-time.Now().Unix() <= 180
   186  }
   187  
   188  func (provider *CloudSSOCredentialsProvider) GetCredentials() (cc *Credentials, err error) {
   189  	if provider.sessionCredentials == nil || provider.needUpdateCredential() {
   190  		sessionCredentials, err1 := provider.getCredentials()
   191  		if err1 != nil {
   192  			return nil, err1
   193  		}
   194  
   195  		provider.sessionCredentials = sessionCredentials
   196  		expirationTime, err2 := time.Parse("2006-01-02T15:04:05Z", sessionCredentials.Expiration)
   197  		if err2 != nil {
   198  			return nil, err2
   199  		}
   200  
   201  		provider.lastUpdateTimestamp = time.Now().Unix()
   202  		provider.expirationTimestamp = expirationTime.Unix()
   203  	}
   204  
   205  	cc = &Credentials{
   206  		AccessKeyId:     provider.sessionCredentials.AccessKeyId,
   207  		AccessKeySecret: provider.sessionCredentials.AccessKeySecret,
   208  		SecurityToken:   provider.sessionCredentials.SecurityToken,
   209  		ProviderName:    provider.GetProviderName(),
   210  	}
   211  	return
   212  }
   213  
   214  func (provider *CloudSSOCredentialsProvider) GetProviderName() string {
   215  	return "cloud_sso"
   216  }