ecs_ram_role.go raw
1 package providers
2
3 import (
4 "encoding/json"
5 "errors"
6 "fmt"
7 "os"
8 "strconv"
9 "strings"
10 "time"
11
12 httputil "github.com/aliyun/credentials-go/credentials/internal/http"
13 )
14
15 type ECSRAMRoleCredentialsProvider struct {
16 roleName string
17 disableIMDSv1 bool
18 // for sts
19 session *sessionCredentials
20 expirationTimestamp int64
21 // for http options
22 httpOptions *HttpOptions
23 }
24
25 type ECSRAMRoleCredentialsProviderBuilder struct {
26 provider *ECSRAMRoleCredentialsProvider
27 }
28
29 func NewECSRAMRoleCredentialsProviderBuilder() *ECSRAMRoleCredentialsProviderBuilder {
30 return &ECSRAMRoleCredentialsProviderBuilder{
31 provider: &ECSRAMRoleCredentialsProvider{},
32 }
33 }
34
35 func (builder *ECSRAMRoleCredentialsProviderBuilder) WithRoleName(roleName string) *ECSRAMRoleCredentialsProviderBuilder {
36 builder.provider.roleName = roleName
37 return builder
38 }
39
40 func (builder *ECSRAMRoleCredentialsProviderBuilder) WithDisableIMDSv1(disableIMDSv1 bool) *ECSRAMRoleCredentialsProviderBuilder {
41 builder.provider.disableIMDSv1 = disableIMDSv1
42 return builder
43 }
44
45 func (builder *ECSRAMRoleCredentialsProviderBuilder) WithHttpOptions(httpOptions *HttpOptions) *ECSRAMRoleCredentialsProviderBuilder {
46 builder.provider.httpOptions = httpOptions
47 return builder
48 }
49
50 const defaultMetadataTokenDuration = 21600 // 6 hours
51
52 func (builder *ECSRAMRoleCredentialsProviderBuilder) Build() (provider *ECSRAMRoleCredentialsProvider, err error) {
53
54 if strings.ToLower(os.Getenv("ALIBABA_CLOUD_ECS_METADATA_DISABLED")) == "true" {
55 err = errors.New("IMDS credentials is disabled")
56 return
57 }
58
59 // 设置 roleName 默认值
60 if builder.provider.roleName == "" {
61 builder.provider.roleName = os.Getenv("ALIBABA_CLOUD_ECS_METADATA")
62 }
63
64 if !builder.provider.disableIMDSv1 {
65 builder.provider.disableIMDSv1 = strings.ToLower(os.Getenv("ALIBABA_CLOUD_IMDSV1_DISABLED")) == "true"
66 }
67
68 provider = builder.provider
69 return
70 }
71
72 type ecsRAMRoleResponse struct {
73 Code *string `json:"Code"`
74 AccessKeyId *string `json:"AccessKeyId"`
75 AccessKeySecret *string `json:"AccessKeySecret"`
76 SecurityToken *string `json:"SecurityToken"`
77 LastUpdated *string `json:"LastUpdated"`
78 Expiration *string `json:"Expiration"`
79 }
80
81 func (provider *ECSRAMRoleCredentialsProvider) needUpdateCredential() bool {
82 if provider.expirationTimestamp == 0 {
83 return true
84 }
85
86 return provider.expirationTimestamp-time.Now().Unix() <= 180
87 }
88
89 func (provider *ECSRAMRoleCredentialsProvider) getRoleName() (roleName string, err error) {
90 req := &httputil.Request{
91 Method: "GET",
92 Protocol: "http",
93 Host: "100.100.100.200",
94 Path: "/latest/meta-data/ram/security-credentials/",
95 Headers: map[string]string{},
96 }
97
98 connectTimeout := 1 * time.Second
99 readTimeout := 1 * time.Second
100
101 if provider.httpOptions != nil && provider.httpOptions.ConnectTimeout > 0 {
102 connectTimeout = time.Duration(provider.httpOptions.ConnectTimeout) * time.Millisecond
103 }
104 if provider.httpOptions != nil && provider.httpOptions.ReadTimeout > 0 {
105 readTimeout = time.Duration(provider.httpOptions.ReadTimeout) * time.Millisecond
106 }
107 if provider.httpOptions != nil && provider.httpOptions.Proxy != "" {
108 req.Proxy = provider.httpOptions.Proxy
109 }
110 req.ConnectTimeout = connectTimeout
111 req.ReadTimeout = readTimeout
112
113 metadataToken, err := provider.getMetadataToken()
114 if err != nil {
115 return "", err
116 }
117 if metadataToken != "" {
118 req.Headers["x-aliyun-ecs-metadata-token"] = metadataToken
119 }
120
121 res, err := httpDo(req)
122 if err != nil {
123 err = fmt.Errorf("get role name failed: %s", err.Error())
124 return
125 }
126
127 if res.StatusCode != 200 {
128 err = fmt.Errorf("get role name failed: %s %d", req.BuildRequestURL(), res.StatusCode)
129 return
130 }
131
132 roleName = strings.TrimSpace(string(res.Body))
133 return
134 }
135
136 func (provider *ECSRAMRoleCredentialsProvider) getCredentials() (session *sessionCredentials, err error) {
137 roleName := provider.roleName
138 if roleName == "" {
139 roleName, err = provider.getRoleName()
140 if err != nil {
141 return
142 }
143 }
144
145 req := &httputil.Request{
146 Method: "GET",
147 Protocol: "http",
148 Host: "100.100.100.200",
149 Path: "/latest/meta-data/ram/security-credentials/" + roleName,
150 Headers: map[string]string{},
151 }
152
153 connectTimeout := 1 * time.Second
154 readTimeout := 1 * time.Second
155
156 if provider.httpOptions != nil && provider.httpOptions.ConnectTimeout > 0 {
157 connectTimeout = time.Duration(provider.httpOptions.ConnectTimeout) * time.Millisecond
158 }
159 if provider.httpOptions != nil && provider.httpOptions.ReadTimeout > 0 {
160 readTimeout = time.Duration(provider.httpOptions.ReadTimeout) * time.Millisecond
161 }
162 if provider.httpOptions != nil && provider.httpOptions.Proxy != "" {
163 req.Proxy = provider.httpOptions.Proxy
164 }
165 req.ConnectTimeout = connectTimeout
166 req.ReadTimeout = readTimeout
167
168 metadataToken, err := provider.getMetadataToken()
169 if err != nil {
170 return nil, err
171 }
172 if metadataToken != "" {
173 req.Headers["x-aliyun-ecs-metadata-token"] = metadataToken
174 }
175
176 res, err := httpDo(req)
177 if err != nil {
178 err = fmt.Errorf("refresh Ecs sts token err: %s", err.Error())
179 return
180 }
181
182 if res.StatusCode != 200 {
183 err = fmt.Errorf("refresh Ecs sts token err, httpStatus: %d, message = %s", res.StatusCode, string(res.Body))
184 return
185 }
186
187 var data ecsRAMRoleResponse
188 err = json.Unmarshal(res.Body, &data)
189 if err != nil {
190 err = fmt.Errorf("refresh Ecs sts token err, json.Unmarshal fail: %s", err.Error())
191 return
192 }
193
194 if data.AccessKeyId == nil || data.AccessKeySecret == nil || data.SecurityToken == nil {
195 err = fmt.Errorf("refresh Ecs sts token err, fail to get credentials")
196 return
197 }
198
199 if *data.Code != "Success" {
200 err = fmt.Errorf("refresh Ecs sts token err, Code is not Success")
201 return
202 }
203
204 session = &sessionCredentials{
205 AccessKeyId: *data.AccessKeyId,
206 AccessKeySecret: *data.AccessKeySecret,
207 SecurityToken: *data.SecurityToken,
208 Expiration: *data.Expiration,
209 }
210 return
211 }
212
213 func (provider *ECSRAMRoleCredentialsProvider) GetCredentials() (cc *Credentials, err error) {
214 if provider.session == nil || provider.needUpdateCredential() {
215 session, err1 := provider.getCredentials()
216 if err1 != nil {
217 return nil, err1
218 }
219
220 provider.session = session
221 expirationTime, err2 := time.Parse("2006-01-02T15:04:05Z", session.Expiration)
222 if err2 != nil {
223 return nil, err2
224 }
225 provider.expirationTimestamp = expirationTime.Unix()
226 }
227
228 cc = &Credentials{
229 AccessKeyId: provider.session.AccessKeyId,
230 AccessKeySecret: provider.session.AccessKeySecret,
231 SecurityToken: provider.session.SecurityToken,
232 ProviderName: provider.GetProviderName(),
233 }
234 return
235 }
236
237 func (provider *ECSRAMRoleCredentialsProvider) GetProviderName() string {
238 return "ecs_ram_role"
239 }
240
241 func (provider *ECSRAMRoleCredentialsProvider) getMetadataToken() (metadataToken string, err error) {
242 // PUT http://100.100.100.200/latest/api/token
243 req := &httputil.Request{
244 Method: "PUT",
245 Protocol: "http",
246 Host: "100.100.100.200",
247 Path: "/latest/api/token",
248 Headers: map[string]string{
249 "X-aliyun-ecs-metadata-token-ttl-seconds": strconv.Itoa(defaultMetadataTokenDuration),
250 },
251 }
252
253 connectTimeout := 1 * time.Second
254 readTimeout := 1 * time.Second
255
256 if provider.httpOptions != nil && provider.httpOptions.ConnectTimeout > 0 {
257 connectTimeout = time.Duration(provider.httpOptions.ConnectTimeout) * time.Millisecond
258 }
259 if provider.httpOptions != nil && provider.httpOptions.ReadTimeout > 0 {
260 readTimeout = time.Duration(provider.httpOptions.ReadTimeout) * time.Millisecond
261 }
262 if provider.httpOptions != nil && provider.httpOptions.Proxy != "" {
263 req.Proxy = provider.httpOptions.Proxy
264 }
265 req.ConnectTimeout = connectTimeout
266 req.ReadTimeout = readTimeout
267
268 res, _err := httpDo(req)
269 if _err != nil {
270 if provider.disableIMDSv1 {
271 err = fmt.Errorf("get metadata token failed: %s", _err.Error())
272 }
273 return
274 }
275 if res.StatusCode != 200 {
276 if provider.disableIMDSv1 {
277 err = fmt.Errorf("refresh Ecs sts token err, httpStatus: %d, message = %s", res.StatusCode, string(res.Body))
278 }
279 return
280 }
281 metadataToken = string(res.Body)
282 return
283 }
284