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