cloud_sso.go raw

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