cloud_sso.go 6.3 KB

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