diff options
author | Rutger Broekhoff | 2023-12-29 21:31:53 +0100 |
---|---|---|
committer | Rutger Broekhoff | 2023-12-29 21:31:53 +0100 |
commit | 404aeae4545d2426c089a5f8d5e82dae56f5212b (patch) | |
tree | 2d84e00af272b39fc04f3795ae06bc48970e57b5 /vendor/github.com/minio/minio-go/v7/pkg/credentials/iam_aws.go | |
parent | 209d8b0187ed025dec9ac149ebcced3462877bff (diff) | |
download | gitolfs3-404aeae4545d2426c089a5f8d5e82dae56f5212b.tar.gz gitolfs3-404aeae4545d2426c089a5f8d5e82dae56f5212b.zip |
Make Nix builds work
Diffstat (limited to 'vendor/github.com/minio/minio-go/v7/pkg/credentials/iam_aws.go')
-rw-r--r-- | vendor/github.com/minio/minio-go/v7/pkg/credentials/iam_aws.go | 433 |
1 files changed, 433 insertions, 0 deletions
diff --git a/vendor/github.com/minio/minio-go/v7/pkg/credentials/iam_aws.go b/vendor/github.com/minio/minio-go/v7/pkg/credentials/iam_aws.go new file mode 100644 index 0000000..c5153c4 --- /dev/null +++ b/vendor/github.com/minio/minio-go/v7/pkg/credentials/iam_aws.go | |||
@@ -0,0 +1,433 @@ | |||
1 | /* | ||
2 | * MinIO Go Library for Amazon S3 Compatible Cloud Storage | ||
3 | * Copyright 2017 MinIO, Inc. | ||
4 | * | ||
5 | * Licensed under the Apache License, Version 2.0 (the "License"); | ||
6 | * you may not use this file except in compliance with the License. | ||
7 | * You may obtain a copy of the License at | ||
8 | * | ||
9 | * http://www.apache.org/licenses/LICENSE-2.0 | ||
10 | * | ||
11 | * Unless required by applicable law or agreed to in writing, software | ||
12 | * distributed under the License is distributed on an "AS IS" BASIS, | ||
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
14 | * See the License for the specific language governing permissions and | ||
15 | * limitations under the License. | ||
16 | */ | ||
17 | |||
18 | package credentials | ||
19 | |||
20 | import ( | ||
21 | "bufio" | ||
22 | "context" | ||
23 | "errors" | ||
24 | "fmt" | ||
25 | "io" | ||
26 | "net" | ||
27 | "net/http" | ||
28 | "net/url" | ||
29 | "os" | ||
30 | "path" | ||
31 | "strings" | ||
32 | "time" | ||
33 | |||
34 | jsoniter "github.com/json-iterator/go" | ||
35 | ) | ||
36 | |||
37 | // DefaultExpiryWindow - Default expiry window. | ||
38 | // ExpiryWindow will allow the credentials to trigger refreshing | ||
39 | // prior to the credentials actually expiring. This is beneficial | ||
40 | // so race conditions with expiring credentials do not cause | ||
41 | // request to fail unexpectedly due to ExpiredTokenException exceptions. | ||
42 | // DefaultExpiryWindow can be used as parameter to (*Expiry).SetExpiration. | ||
43 | // When used the tokens refresh will be triggered when 80% of the elapsed | ||
44 | // time until the actual expiration time is passed. | ||
45 | const DefaultExpiryWindow = -1 | ||
46 | |||
47 | // A IAM retrieves credentials from the EC2 service, and keeps track if | ||
48 | // those credentials are expired. | ||
49 | type IAM struct { | ||
50 | Expiry | ||
51 | |||
52 | // Required http Client to use when connecting to IAM metadata service. | ||
53 | Client *http.Client | ||
54 | |||
55 | // Custom endpoint to fetch IAM role credentials. | ||
56 | Endpoint string | ||
57 | |||
58 | // Region configurable custom region for STS | ||
59 | Region string | ||
60 | |||
61 | // Support for container authorization token https://docs.aws.amazon.com/sdkref/latest/guide/feature-container-credentials.html | ||
62 | Container struct { | ||
63 | AuthorizationToken string | ||
64 | CredentialsFullURI string | ||
65 | CredentialsRelativeURI string | ||
66 | } | ||
67 | |||
68 | // EKS based k8s RBAC authorization - https://docs.aws.amazon.com/eks/latest/userguide/pod-configuration.html | ||
69 | EKSIdentity struct { | ||
70 | TokenFile string | ||
71 | RoleARN string | ||
72 | RoleSessionName string | ||
73 | } | ||
74 | } | ||
75 | |||
76 | // IAM Roles for Amazon EC2 | ||
77 | // http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html | ||
78 | const ( | ||
79 | DefaultIAMRoleEndpoint = "http://169.254.169.254" | ||
80 | DefaultECSRoleEndpoint = "http://169.254.170.2" | ||
81 | DefaultSTSRoleEndpoint = "https://sts.amazonaws.com" | ||
82 | DefaultIAMSecurityCredsPath = "/latest/meta-data/iam/security-credentials/" | ||
83 | TokenRequestTTLHeader = "X-aws-ec2-metadata-token-ttl-seconds" | ||
84 | TokenPath = "/latest/api/token" | ||
85 | TokenTTL = "21600" | ||
86 | TokenRequestHeader = "X-aws-ec2-metadata-token" | ||
87 | ) | ||
88 | |||
89 | // NewIAM returns a pointer to a new Credentials object wrapping the IAM. | ||
90 | func NewIAM(endpoint string) *Credentials { | ||
91 | return New(&IAM{ | ||
92 | Client: &http.Client{ | ||
93 | Transport: http.DefaultTransport, | ||
94 | }, | ||
95 | Endpoint: endpoint, | ||
96 | }) | ||
97 | } | ||
98 | |||
99 | // Retrieve retrieves credentials from the EC2 service. | ||
100 | // Error will be returned if the request fails, or unable to extract | ||
101 | // the desired | ||
102 | func (m *IAM) Retrieve() (Value, error) { | ||
103 | token := os.Getenv("AWS_CONTAINER_AUTHORIZATION_TOKEN") | ||
104 | if token == "" { | ||
105 | token = m.Container.AuthorizationToken | ||
106 | } | ||
107 | |||
108 | relativeURI := os.Getenv("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI") | ||
109 | if relativeURI == "" { | ||
110 | relativeURI = m.Container.CredentialsRelativeURI | ||
111 | } | ||
112 | |||
113 | fullURI := os.Getenv("AWS_CONTAINER_CREDENTIALS_FULL_URI") | ||
114 | if fullURI == "" { | ||
115 | fullURI = m.Container.CredentialsFullURI | ||
116 | } | ||
117 | |||
118 | identityFile := os.Getenv("AWS_WEB_IDENTITY_TOKEN_FILE") | ||
119 | if identityFile == "" { | ||
120 | identityFile = m.EKSIdentity.TokenFile | ||
121 | } | ||
122 | |||
123 | roleArn := os.Getenv("AWS_ROLE_ARN") | ||
124 | if roleArn == "" { | ||
125 | roleArn = m.EKSIdentity.RoleARN | ||
126 | } | ||
127 | |||
128 | roleSessionName := os.Getenv("AWS_ROLE_SESSION_NAME") | ||
129 | if roleSessionName == "" { | ||
130 | roleSessionName = m.EKSIdentity.RoleSessionName | ||
131 | } | ||
132 | |||
133 | region := os.Getenv("AWS_REGION") | ||
134 | if region == "" { | ||
135 | region = m.Region | ||
136 | } | ||
137 | |||
138 | var roleCreds ec2RoleCredRespBody | ||
139 | var err error | ||
140 | |||
141 | endpoint := m.Endpoint | ||
142 | switch { | ||
143 | case identityFile != "": | ||
144 | if len(endpoint) == 0 { | ||
145 | if region != "" { | ||
146 | if strings.HasPrefix(region, "cn-") { | ||
147 | endpoint = "https://sts." + region + ".amazonaws.com.cn" | ||
148 | } else { | ||
149 | endpoint = "https://sts." + region + ".amazonaws.com" | ||
150 | } | ||
151 | } else { | ||
152 | endpoint = DefaultSTSRoleEndpoint | ||
153 | } | ||
154 | } | ||
155 | |||
156 | creds := &STSWebIdentity{ | ||
157 | Client: m.Client, | ||
158 | STSEndpoint: endpoint, | ||
159 | GetWebIDTokenExpiry: func() (*WebIdentityToken, error) { | ||
160 | token, err := os.ReadFile(identityFile) | ||
161 | if err != nil { | ||
162 | return nil, err | ||
163 | } | ||
164 | |||
165 | return &WebIdentityToken{Token: string(token)}, nil | ||
166 | }, | ||
167 | RoleARN: roleArn, | ||
168 | roleSessionName: roleSessionName, | ||
169 | } | ||
170 | |||
171 | stsWebIdentityCreds, err := creds.Retrieve() | ||
172 | if err == nil { | ||
173 | m.SetExpiration(creds.Expiration(), DefaultExpiryWindow) | ||
174 | } | ||
175 | return stsWebIdentityCreds, err | ||
176 | |||
177 | case relativeURI != "": | ||
178 | if len(endpoint) == 0 { | ||
179 | endpoint = fmt.Sprintf("%s%s", DefaultECSRoleEndpoint, relativeURI) | ||
180 | } | ||
181 | |||
182 | roleCreds, err = getEcsTaskCredentials(m.Client, endpoint, token) | ||
183 | |||
184 | case fullURI != "": | ||
185 | if len(endpoint) == 0 { | ||
186 | endpoint = fullURI | ||
187 | var ok bool | ||
188 | if ok, err = isLoopback(endpoint); !ok { | ||
189 | if err == nil { | ||
190 | err = fmt.Errorf("uri host is not a loopback address: %s", endpoint) | ||
191 | } | ||
192 | break | ||
193 | } | ||
194 | } | ||
195 | |||
196 | roleCreds, err = getEcsTaskCredentials(m.Client, endpoint, token) | ||
197 | |||
198 | default: | ||
199 | roleCreds, err = getCredentials(m.Client, endpoint) | ||
200 | } | ||
201 | |||
202 | if err != nil { | ||
203 | return Value{}, err | ||
204 | } | ||
205 | // Expiry window is set to 10secs. | ||
206 | m.SetExpiration(roleCreds.Expiration, DefaultExpiryWindow) | ||
207 | |||
208 | return Value{ | ||
209 | AccessKeyID: roleCreds.AccessKeyID, | ||
210 | SecretAccessKey: roleCreds.SecretAccessKey, | ||
211 | SessionToken: roleCreds.Token, | ||
212 | SignerType: SignatureV4, | ||
213 | }, nil | ||
214 | } | ||
215 | |||
216 | // A ec2RoleCredRespBody provides the shape for unmarshaling credential | ||
217 | // request responses. | ||
218 | type ec2RoleCredRespBody struct { | ||
219 | // Success State | ||
220 | Expiration time.Time | ||
221 | AccessKeyID string | ||
222 | SecretAccessKey string | ||
223 | Token string | ||
224 | |||
225 | // Error state | ||
226 | Code string | ||
227 | Message string | ||
228 | |||
229 | // Unused params. | ||
230 | LastUpdated time.Time | ||
231 | Type string | ||
232 | } | ||
233 | |||
234 | // Get the final IAM role URL where the request will | ||
235 | // be sent to fetch the rolling access credentials. | ||
236 | // http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html | ||
237 | func getIAMRoleURL(endpoint string) (*url.URL, error) { | ||
238 | u, err := url.Parse(endpoint) | ||
239 | if err != nil { | ||
240 | return nil, err | ||
241 | } | ||
242 | u.Path = DefaultIAMSecurityCredsPath | ||
243 | return u, nil | ||
244 | } | ||
245 | |||
246 | // listRoleNames lists of credential role names associated | ||
247 | // with the current EC2 service. If there are no credentials, | ||
248 | // or there is an error making or receiving the request. | ||
249 | // http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html | ||
250 | func listRoleNames(client *http.Client, u *url.URL, token string) ([]string, error) { | ||
251 | req, err := http.NewRequest(http.MethodGet, u.String(), nil) | ||
252 | if err != nil { | ||
253 | return nil, err | ||
254 | } | ||
255 | if token != "" { | ||
256 | req.Header.Add(TokenRequestHeader, token) | ||
257 | } | ||
258 | resp, err := client.Do(req) | ||
259 | if err != nil { | ||
260 | return nil, err | ||
261 | } | ||
262 | defer resp.Body.Close() | ||
263 | if resp.StatusCode != http.StatusOK { | ||
264 | return nil, errors.New(resp.Status) | ||
265 | } | ||
266 | |||
267 | credsList := []string{} | ||
268 | s := bufio.NewScanner(resp.Body) | ||
269 | for s.Scan() { | ||
270 | credsList = append(credsList, s.Text()) | ||
271 | } | ||
272 | |||
273 | if err := s.Err(); err != nil { | ||
274 | return nil, err | ||
275 | } | ||
276 | |||
277 | return credsList, nil | ||
278 | } | ||
279 | |||
280 | func getEcsTaskCredentials(client *http.Client, endpoint, token string) (ec2RoleCredRespBody, error) { | ||
281 | req, err := http.NewRequest(http.MethodGet, endpoint, nil) | ||
282 | if err != nil { | ||
283 | return ec2RoleCredRespBody{}, err | ||
284 | } | ||
285 | |||
286 | if token != "" { | ||
287 | req.Header.Set("Authorization", token) | ||
288 | } | ||
289 | |||
290 | resp, err := client.Do(req) | ||
291 | if err != nil { | ||
292 | return ec2RoleCredRespBody{}, err | ||
293 | } | ||
294 | defer resp.Body.Close() | ||
295 | if resp.StatusCode != http.StatusOK { | ||
296 | return ec2RoleCredRespBody{}, errors.New(resp.Status) | ||
297 | } | ||
298 | |||
299 | respCreds := ec2RoleCredRespBody{} | ||
300 | if err := jsoniter.NewDecoder(resp.Body).Decode(&respCreds); err != nil { | ||
301 | return ec2RoleCredRespBody{}, err | ||
302 | } | ||
303 | |||
304 | return respCreds, nil | ||
305 | } | ||
306 | |||
307 | func fetchIMDSToken(client *http.Client, endpoint string) (string, error) { | ||
308 | ctx, cancel := context.WithTimeout(context.Background(), time.Second) | ||
309 | defer cancel() | ||
310 | |||
311 | req, err := http.NewRequestWithContext(ctx, http.MethodPut, endpoint+TokenPath, nil) | ||
312 | if err != nil { | ||
313 | return "", err | ||
314 | } | ||
315 | req.Header.Add(TokenRequestTTLHeader, TokenTTL) | ||
316 | resp, err := client.Do(req) | ||
317 | if err != nil { | ||
318 | return "", err | ||
319 | } | ||
320 | defer resp.Body.Close() | ||
321 | data, err := io.ReadAll(resp.Body) | ||
322 | if err != nil { | ||
323 | return "", err | ||
324 | } | ||
325 | if resp.StatusCode != http.StatusOK { | ||
326 | return "", errors.New(resp.Status) | ||
327 | } | ||
328 | return string(data), nil | ||
329 | } | ||
330 | |||
331 | // getCredentials - obtains the credentials from the IAM role name associated with | ||
332 | // the current EC2 service. | ||
333 | // | ||
334 | // If the credentials cannot be found, or there is an error | ||
335 | // reading the response an error will be returned. | ||
336 | func getCredentials(client *http.Client, endpoint string) (ec2RoleCredRespBody, error) { | ||
337 | if endpoint == "" { | ||
338 | endpoint = DefaultIAMRoleEndpoint | ||
339 | } | ||
340 | |||
341 | // https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-instance-metadata-service.html | ||
342 | token, err := fetchIMDSToken(client, endpoint) | ||
343 | if err != nil { | ||
344 | // Return only errors for valid situations, if the IMDSv2 is not enabled | ||
345 | // we will not be able to get the token, in such a situation we have | ||
346 | // to rely on IMDSv1 behavior as a fallback, this check ensures that. | ||
347 | // Refer https://github.com/minio/minio-go/issues/1866 | ||
348 | if !errors.Is(err, context.DeadlineExceeded) && !errors.Is(err, context.Canceled) { | ||
349 | return ec2RoleCredRespBody{}, err | ||
350 | } | ||
351 | } | ||
352 | |||
353 | // http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html | ||
354 | u, err := getIAMRoleURL(endpoint) | ||
355 | if err != nil { | ||
356 | return ec2RoleCredRespBody{}, err | ||
357 | } | ||
358 | |||
359 | // http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html | ||
360 | roleNames, err := listRoleNames(client, u, token) | ||
361 | if err != nil { | ||
362 | return ec2RoleCredRespBody{}, err | ||
363 | } | ||
364 | |||
365 | if len(roleNames) == 0 { | ||
366 | return ec2RoleCredRespBody{}, errors.New("No IAM roles attached to this EC2 service") | ||
367 | } | ||
368 | |||
369 | // http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html | ||
370 | // - An instance profile can contain only one IAM role. This limit cannot be increased. | ||
371 | roleName := roleNames[0] | ||
372 | |||
373 | // http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html | ||
374 | // The following command retrieves the security credentials for an | ||
375 | // IAM role named `s3access`. | ||
376 | // | ||
377 | // $ curl http://169.254.169.254/latest/meta-data/iam/security-credentials/s3access | ||
378 | // | ||
379 | u.Path = path.Join(u.Path, roleName) | ||
380 | req, err := http.NewRequest(http.MethodGet, u.String(), nil) | ||
381 | if err != nil { | ||
382 | return ec2RoleCredRespBody{}, err | ||
383 | } | ||
384 | if token != "" { | ||
385 | req.Header.Add(TokenRequestHeader, token) | ||
386 | } | ||
387 | |||
388 | resp, err := client.Do(req) | ||
389 | if err != nil { | ||
390 | return ec2RoleCredRespBody{}, err | ||
391 | } | ||
392 | defer resp.Body.Close() | ||
393 | if resp.StatusCode != http.StatusOK { | ||
394 | return ec2RoleCredRespBody{}, errors.New(resp.Status) | ||
395 | } | ||
396 | |||
397 | respCreds := ec2RoleCredRespBody{} | ||
398 | if err := jsoniter.NewDecoder(resp.Body).Decode(&respCreds); err != nil { | ||
399 | return ec2RoleCredRespBody{}, err | ||
400 | } | ||
401 | |||
402 | if respCreds.Code != "Success" { | ||
403 | // If an error code was returned something failed requesting the role. | ||
404 | return ec2RoleCredRespBody{}, errors.New(respCreds.Message) | ||
405 | } | ||
406 | |||
407 | return respCreds, nil | ||
408 | } | ||
409 | |||
410 | // isLoopback identifies if a uri's host is on a loopback address | ||
411 | func isLoopback(uri string) (bool, error) { | ||
412 | u, err := url.Parse(uri) | ||
413 | if err != nil { | ||
414 | return false, err | ||
415 | } | ||
416 | |||
417 | host := u.Hostname() | ||
418 | if len(host) == 0 { | ||
419 | return false, fmt.Errorf("can't parse host from uri: %s", uri) | ||
420 | } | ||
421 | |||
422 | ips, err := net.LookupHost(host) | ||
423 | if err != nil { | ||
424 | return false, err | ||
425 | } | ||
426 | for _, ip := range ips { | ||
427 | if !net.ParseIP(ip).IsLoopback() { | ||
428 | return false, nil | ||
429 | } | ||
430 | } | ||
431 | |||
432 | return true, nil | ||
433 | } | ||