/src/gdal/port/cpl_aws.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /********************************************************************** |
2 | | * |
3 | | * Name: cpl_aws.cpp |
4 | | * Project: CPL - Common Portability Library |
5 | | * Purpose: Amazon Web Services routines |
6 | | * Author: Even Rouault <even.rouault at spatialys.com> |
7 | | * |
8 | | ********************************************************************** |
9 | | * Copyright (c) 2015, Even Rouault <even.rouault at spatialys.com> |
10 | | * |
11 | | * SPDX-License-Identifier: MIT |
12 | | ****************************************************************************/ |
13 | | |
14 | | //! @cond Doxygen_Suppress |
15 | | |
16 | | #include "cpl_aws.h" |
17 | | #include "cpl_json.h" |
18 | | #include "cpl_vsi_error.h" |
19 | | #include "cpl_sha1.h" |
20 | | #include "cpl_sha256.h" |
21 | | #include "cpl_time.h" |
22 | | #include "cpl_minixml.h" |
23 | | #include "cpl_multiproc.h" |
24 | | #include "cpl_http.h" |
25 | | #include <algorithm> |
26 | | |
27 | | // #define DEBUG_VERBOSE 1 |
28 | | |
29 | | #ifdef _WIN32 |
30 | | #if defined(HAVE_ATLBASE_H) |
31 | | bool CPLFetchWindowsProductUUID( |
32 | | std::string &osStr); // defined in cpl_aws_win32.cpp |
33 | | #endif |
34 | | const char *CPLGetWineVersion(); // defined in cpl_vsil_win32.cpp |
35 | | #endif |
36 | | |
37 | | #ifdef HAVE_CURL |
38 | | static CPLMutex *ghMutex = nullptr; |
39 | | static std::string gosIAMRole; |
40 | | static std::string gosGlobalAccessKeyId; |
41 | | static std::string gosGlobalSecretAccessKey; |
42 | | static std::string gosGlobalSessionToken; |
43 | | static GIntBig gnGlobalExpiration = 0; |
44 | | static std::string gosRegion; |
45 | | |
46 | | // The below variables are used for credentials retrieved through a STS |
47 | | // AssumedRole operation |
48 | | static std::string gosRoleArn; |
49 | | static std::string gosExternalId; |
50 | | static std::string gosMFASerial; |
51 | | static std::string gosRoleSessionName; |
52 | | static std::string gosSourceProfileAccessKeyId; |
53 | | static std::string gosSourceProfileSecretAccessKey; |
54 | | static std::string gosSourceProfileSessionToken; |
55 | | |
56 | | // The below variables are used for web identity settings in aws/config |
57 | | static std::string gosRoleArnWebIdentity; |
58 | | static std::string gosWebIdentityTokenFile; |
59 | | |
60 | | // The below variables are used for SSO authentication |
61 | | static std::string gosSSOStartURL; |
62 | | static std::string gosSSOAccountID; |
63 | | static std::string gosSSORoleName; |
64 | | |
65 | | constexpr const char *AWS_DEBUG_KEY = "AWS"; |
66 | | |
67 | | /************************************************************************/ |
68 | | /* CPLGetLowerCaseHex() */ |
69 | | /************************************************************************/ |
70 | | |
71 | | static std::string CPLGetLowerCaseHex(const GByte *pabyData, size_t nBytes) |
72 | | |
73 | 0 | { |
74 | 0 | std::string osRet; |
75 | 0 | osRet.resize(nBytes * 2); |
76 | |
|
77 | 0 | constexpr char achHex[] = "0123456789abcdef"; |
78 | |
|
79 | 0 | for (size_t i = 0; i < nBytes; ++i) |
80 | 0 | { |
81 | 0 | const int nLow = pabyData[i] & 0x0f; |
82 | 0 | const int nHigh = (pabyData[i] & 0xf0) >> 4; |
83 | |
|
84 | 0 | osRet[i * 2] = achHex[nHigh]; |
85 | 0 | osRet[i * 2 + 1] = achHex[nLow]; |
86 | 0 | } |
87 | |
|
88 | 0 | return osRet; |
89 | 0 | } |
90 | | |
91 | | /************************************************************************/ |
92 | | /* CPLGetLowerCaseHexSHA256() */ |
93 | | /************************************************************************/ |
94 | | |
95 | | std::string CPLGetLowerCaseHexSHA256(const void *pabyData, size_t nBytes) |
96 | 0 | { |
97 | 0 | GByte hash[CPL_SHA256_HASH_SIZE] = {}; |
98 | 0 | CPL_SHA256(static_cast<const GByte *>(pabyData), nBytes, hash); |
99 | 0 | return CPLGetLowerCaseHex(hash, CPL_SHA256_HASH_SIZE); |
100 | 0 | } |
101 | | |
102 | | /************************************************************************/ |
103 | | /* CPLGetLowerCaseHexSHA256() */ |
104 | | /************************************************************************/ |
105 | | |
106 | | std::string CPLGetLowerCaseHexSHA256(const std::string &osStr) |
107 | 0 | { |
108 | 0 | return CPLGetLowerCaseHexSHA256(osStr.c_str(), osStr.size()); |
109 | 0 | } |
110 | | |
111 | | /************************************************************************/ |
112 | | /* CPLAWSURLEncode() */ |
113 | | /************************************************************************/ |
114 | | |
115 | | std::string CPLAWSURLEncode(const std::string &osURL, bool bEncodeSlash) |
116 | 5.25k | { |
117 | 5.25k | std::string osRet; |
118 | 977k | for (size_t i = 0; i < osURL.size(); i++) |
119 | 972k | { |
120 | 972k | char ch = osURL[i]; |
121 | 972k | if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || |
122 | 972k | (ch >= '0' && ch <= '9') || ch == '_' || ch == '-' || ch == '~' || |
123 | 972k | ch == '.') |
124 | 636k | { |
125 | 636k | osRet += ch; |
126 | 636k | } |
127 | 336k | else if (ch == '/') |
128 | 35.3k | { |
129 | 35.3k | if (bEncodeSlash) |
130 | 2.89k | osRet += "%2F"; |
131 | 32.4k | else |
132 | 32.4k | osRet += ch; |
133 | 35.3k | } |
134 | 300k | else |
135 | 300k | { |
136 | 300k | osRet += CPLSPrintf("%%%02X", static_cast<unsigned char>(ch)); |
137 | 300k | } |
138 | 972k | } |
139 | 5.25k | return osRet; |
140 | 5.25k | } |
141 | | |
142 | | /************************************************************************/ |
143 | | /* CPLAWSGetHeaderVal() */ |
144 | | /************************************************************************/ |
145 | | |
146 | | std::string CPLAWSGetHeaderVal(const struct curl_slist *psExistingHeaders, |
147 | | const char *pszKey) |
148 | 0 | { |
149 | 0 | std::string osKey(pszKey); |
150 | 0 | osKey += ":"; |
151 | 0 | const struct curl_slist *psIter = psExistingHeaders; |
152 | 0 | for (; psIter != nullptr; psIter = psIter->next) |
153 | 0 | { |
154 | 0 | if (STARTS_WITH(psIter->data, osKey.c_str())) |
155 | 0 | return CPLString(psIter->data + osKey.size()).Trim(); |
156 | 0 | } |
157 | 0 | return std::string(); |
158 | 0 | } |
159 | | |
160 | | /************************************************************************/ |
161 | | /* CPLGetAWS_SIGN4_Signature() */ |
162 | | /************************************************************************/ |
163 | | |
164 | | // See: |
165 | | // http://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html |
166 | | std::string CPLGetAWS_SIGN4_Signature( |
167 | | const std::string &osSecretAccessKey, const std::string &osAccessToken, |
168 | | const std::string &osRegion, const std::string &osRequestPayer, |
169 | | const std::string &osService, const std::string &osVerb, |
170 | | const struct curl_slist *psExistingHeaders, const std::string &osHost, |
171 | | const std::string &osCanonicalURI, |
172 | | const std::string &osCanonicalQueryString, |
173 | | const std::string &osXAMZContentSHA256, bool bAddHeaderAMZContentSHA256, |
174 | | const std::string &osTimestamp, std::string &osSignedHeaders) |
175 | 0 | { |
176 | | /* -------------------------------------------------------------------- */ |
177 | | /* Compute canonical request string. */ |
178 | | /* -------------------------------------------------------------------- */ |
179 | 0 | std::string osCanonicalRequest = osVerb + "\n"; |
180 | |
|
181 | 0 | osCanonicalRequest += osCanonicalURI + "\n"; |
182 | |
|
183 | 0 | osCanonicalRequest += osCanonicalQueryString + "\n"; |
184 | |
|
185 | 0 | std::map<std::string, std::string> oSortedMapHeaders; |
186 | 0 | oSortedMapHeaders["host"] = osHost; |
187 | 0 | if (osXAMZContentSHA256 != "UNSIGNED-PAYLOAD" && bAddHeaderAMZContentSHA256) |
188 | 0 | { |
189 | 0 | oSortedMapHeaders["x-amz-content-sha256"] = osXAMZContentSHA256; |
190 | 0 | oSortedMapHeaders["x-amz-date"] = osTimestamp; |
191 | 0 | } |
192 | 0 | if (!osRequestPayer.empty()) |
193 | 0 | oSortedMapHeaders["x-amz-request-payer"] = osRequestPayer; |
194 | 0 | if (!osAccessToken.empty()) |
195 | 0 | oSortedMapHeaders["x-amz-security-token"] = osAccessToken; |
196 | 0 | std::string osCanonicalizedHeaders( |
197 | 0 | IVSIS3LikeHandleHelper::BuildCanonicalizedHeaders( |
198 | 0 | oSortedMapHeaders, psExistingHeaders, "x-amz-")); |
199 | |
|
200 | 0 | osCanonicalRequest += osCanonicalizedHeaders + "\n"; |
201 | |
|
202 | 0 | osSignedHeaders.clear(); |
203 | 0 | std::map<std::string, std::string>::const_iterator oIter = |
204 | 0 | oSortedMapHeaders.begin(); |
205 | 0 | for (; oIter != oSortedMapHeaders.end(); ++oIter) |
206 | 0 | { |
207 | 0 | if (!osSignedHeaders.empty()) |
208 | 0 | osSignedHeaders += ";"; |
209 | 0 | osSignedHeaders += oIter->first; |
210 | 0 | } |
211 | |
|
212 | 0 | osCanonicalRequest += osSignedHeaders + "\n"; |
213 | |
|
214 | 0 | osCanonicalRequest += osXAMZContentSHA256; |
215 | |
|
216 | | #ifdef DEBUG_VERBOSE |
217 | | CPLDebug(AWS_DEBUG_KEY, "osCanonicalRequest='%s'", |
218 | | osCanonicalRequest.c_str()); |
219 | | #endif |
220 | | |
221 | | /* -------------------------------------------------------------------- */ |
222 | | /* Compute StringToSign . */ |
223 | | /* -------------------------------------------------------------------- */ |
224 | 0 | std::string osStringToSign = "AWS4-HMAC-SHA256\n"; |
225 | 0 | osStringToSign += osTimestamp + "\n"; |
226 | |
|
227 | 0 | std::string osYYMMDD(osTimestamp); |
228 | 0 | osYYMMDD.resize(8); |
229 | |
|
230 | 0 | std::string osScope = osYYMMDD + "/"; |
231 | 0 | osScope += osRegion; |
232 | 0 | osScope += "/"; |
233 | 0 | osScope += osService; |
234 | 0 | osScope += "/aws4_request"; |
235 | 0 | osStringToSign += osScope + "\n"; |
236 | 0 | osStringToSign += CPLGetLowerCaseHexSHA256(osCanonicalRequest); |
237 | |
|
238 | | #ifdef DEBUG_VERBOSE |
239 | | CPLDebug(AWS_DEBUG_KEY, "osStringToSign='%s'", osStringToSign.c_str()); |
240 | | #endif |
241 | | |
242 | | /* -------------------------------------------------------------------- */ |
243 | | /* Compute signing key. */ |
244 | | /* -------------------------------------------------------------------- */ |
245 | 0 | GByte abySigningKeyIn[CPL_SHA256_HASH_SIZE] = {}; |
246 | 0 | GByte abySigningKeyOut[CPL_SHA256_HASH_SIZE] = {}; |
247 | |
|
248 | 0 | std::string osFirstKey(std::string("AWS4") + osSecretAccessKey); |
249 | 0 | CPL_HMAC_SHA256(osFirstKey.c_str(), osFirstKey.size(), osYYMMDD.c_str(), |
250 | 0 | osYYMMDD.size(), abySigningKeyOut); |
251 | 0 | memcpy(abySigningKeyIn, abySigningKeyOut, CPL_SHA256_HASH_SIZE); |
252 | |
|
253 | 0 | CPL_HMAC_SHA256(abySigningKeyIn, CPL_SHA256_HASH_SIZE, osRegion.c_str(), |
254 | 0 | osRegion.size(), abySigningKeyOut); |
255 | 0 | memcpy(abySigningKeyIn, abySigningKeyOut, CPL_SHA256_HASH_SIZE); |
256 | |
|
257 | 0 | CPL_HMAC_SHA256(abySigningKeyIn, CPL_SHA256_HASH_SIZE, osService.c_str(), |
258 | 0 | osService.size(), abySigningKeyOut); |
259 | 0 | memcpy(abySigningKeyIn, abySigningKeyOut, CPL_SHA256_HASH_SIZE); |
260 | |
|
261 | 0 | CPL_HMAC_SHA256(abySigningKeyIn, CPL_SHA256_HASH_SIZE, "aws4_request", |
262 | 0 | strlen("aws4_request"), abySigningKeyOut); |
263 | 0 | memcpy(abySigningKeyIn, abySigningKeyOut, CPL_SHA256_HASH_SIZE); |
264 | |
|
265 | | #ifdef DEBUG_VERBOSE |
266 | | std::string osSigningKey( |
267 | | CPLGetLowerCaseHex(abySigningKeyIn, CPL_SHA256_HASH_SIZE)); |
268 | | CPLDebug(AWS_DEBUG_KEY, "osSigningKey='%s'", osSigningKey.c_str()); |
269 | | #endif |
270 | | |
271 | | /* -------------------------------------------------------------------- */ |
272 | | /* Compute signature. */ |
273 | | /* -------------------------------------------------------------------- */ |
274 | 0 | GByte abySignature[CPL_SHA256_HASH_SIZE] = {}; |
275 | 0 | CPL_HMAC_SHA256(abySigningKeyIn, CPL_SHA256_HASH_SIZE, |
276 | 0 | osStringToSign.c_str(), osStringToSign.size(), |
277 | 0 | abySignature); |
278 | 0 | std::string osSignature( |
279 | 0 | CPLGetLowerCaseHex(abySignature, CPL_SHA256_HASH_SIZE)); |
280 | |
|
281 | | #ifdef DEBUG_VERBOSE |
282 | | CPLDebug(AWS_DEBUG_KEY, "osSignature='%s'", osSignature.c_str()); |
283 | | #endif |
284 | |
|
285 | 0 | return osSignature; |
286 | 0 | } |
287 | | |
288 | | /************************************************************************/ |
289 | | /* CPLGetAWS_SIGN4_Authorization() */ |
290 | | /************************************************************************/ |
291 | | |
292 | | std::string CPLGetAWS_SIGN4_Authorization( |
293 | | const std::string &osSecretAccessKey, const std::string &osAccessKeyId, |
294 | | const std::string &osAccessToken, const std::string &osRegion, |
295 | | const std::string &osRequestPayer, const std::string &osService, |
296 | | const std::string &osVerb, const struct curl_slist *psExistingHeaders, |
297 | | const std::string &osHost, const std::string &osCanonicalURI, |
298 | | const std::string &osCanonicalQueryString, |
299 | | const std::string &osXAMZContentSHA256, bool bAddHeaderAMZContentSHA256, |
300 | | const std::string &osTimestamp) |
301 | 0 | { |
302 | 0 | std::string osSignedHeaders; |
303 | 0 | std::string osSignature(CPLGetAWS_SIGN4_Signature( |
304 | 0 | osSecretAccessKey, osAccessToken, osRegion, osRequestPayer, osService, |
305 | 0 | osVerb, psExistingHeaders, osHost, osCanonicalURI, |
306 | 0 | osCanonicalQueryString, osXAMZContentSHA256, bAddHeaderAMZContentSHA256, |
307 | 0 | osTimestamp, osSignedHeaders)); |
308 | |
|
309 | 0 | std::string osYYMMDD(osTimestamp); |
310 | 0 | osYYMMDD.resize(8); |
311 | | |
312 | | /* -------------------------------------------------------------------- */ |
313 | | /* Build authorization header. */ |
314 | | /* -------------------------------------------------------------------- */ |
315 | 0 | std::string osAuthorization; |
316 | 0 | osAuthorization = "AWS4-HMAC-SHA256 Credential="; |
317 | 0 | osAuthorization += osAccessKeyId; |
318 | 0 | osAuthorization += "/"; |
319 | 0 | osAuthorization += osYYMMDD; |
320 | 0 | osAuthorization += "/"; |
321 | 0 | osAuthorization += osRegion; |
322 | 0 | osAuthorization += "/"; |
323 | 0 | osAuthorization += osService; |
324 | 0 | osAuthorization += "/"; |
325 | 0 | osAuthorization += "aws4_request"; |
326 | 0 | osAuthorization += ","; |
327 | 0 | osAuthorization += "SignedHeaders="; |
328 | 0 | osAuthorization += osSignedHeaders; |
329 | 0 | osAuthorization += ","; |
330 | 0 | osAuthorization += "Signature="; |
331 | 0 | osAuthorization += osSignature; |
332 | |
|
333 | | #ifdef DEBUG_VERBOSE |
334 | | CPLDebug(AWS_DEBUG_KEY, "osAuthorization='%s'", osAuthorization.c_str()); |
335 | | #endif |
336 | |
|
337 | 0 | return osAuthorization; |
338 | 0 | } |
339 | | |
340 | | /************************************************************************/ |
341 | | /* CPLGetAWS_SIGN4_Timestamp() */ |
342 | | /************************************************************************/ |
343 | | |
344 | | std::string CPLGetAWS_SIGN4_Timestamp(GIntBig timestamp) |
345 | 0 | { |
346 | 0 | struct tm brokenDown; |
347 | 0 | CPLUnixTimeToYMDHMS(timestamp, &brokenDown); |
348 | |
|
349 | 0 | char szTimeStamp[80] = {}; |
350 | 0 | snprintf(szTimeStamp, sizeof(szTimeStamp), "%04d%02d%02dT%02d%02d%02dZ", |
351 | 0 | brokenDown.tm_year + 1900, brokenDown.tm_mon + 1, |
352 | 0 | brokenDown.tm_mday, brokenDown.tm_hour, brokenDown.tm_min, |
353 | 0 | brokenDown.tm_sec); |
354 | 0 | return szTimeStamp; |
355 | 0 | } |
356 | | |
357 | | /************************************************************************/ |
358 | | /* VSIS3HandleHelper() */ |
359 | | /************************************************************************/ |
360 | | VSIS3HandleHelper::VSIS3HandleHelper( |
361 | | const std::string &osSecretAccessKey, const std::string &osAccessKeyId, |
362 | | const std::string &osSessionToken, const std::string &osEndpoint, |
363 | | const std::string &osRegion, const std::string &osRequestPayer, |
364 | | const std::string &osBucket, const std::string &osObjectKey, bool bUseHTTPS, |
365 | | bool bUseVirtualHosting, AWSCredentialsSource eCredentialsSource) |
366 | 0 | : m_osURL(BuildURL(osEndpoint, osBucket, osObjectKey, bUseHTTPS, |
367 | 0 | bUseVirtualHosting)), |
368 | 0 | m_osSecretAccessKey(osSecretAccessKey), m_osAccessKeyId(osAccessKeyId), |
369 | 0 | m_osSessionToken(osSessionToken), m_osEndpoint(osEndpoint), |
370 | 0 | m_osRegion(osRegion), m_osRequestPayer(osRequestPayer), |
371 | 0 | m_osBucket(osBucket), m_osObjectKey(osObjectKey), m_bUseHTTPS(bUseHTTPS), |
372 | 0 | m_bUseVirtualHosting(bUseVirtualHosting), |
373 | 0 | m_eCredentialsSource(eCredentialsSource) |
374 | 0 | { |
375 | 0 | VSIS3UpdateParams::UpdateHandleFromMap(this); |
376 | 0 | } |
377 | | |
378 | | /************************************************************************/ |
379 | | /* ~VSIS3HandleHelper() */ |
380 | | /************************************************************************/ |
381 | | |
382 | | VSIS3HandleHelper::~VSIS3HandleHelper() |
383 | 0 | { |
384 | 0 | for (size_t i = 0; i < m_osSecretAccessKey.size(); i++) |
385 | 0 | m_osSecretAccessKey[i] = 0; |
386 | 0 | } |
387 | | |
388 | | /************************************************************************/ |
389 | | /* BuildURL() */ |
390 | | /************************************************************************/ |
391 | | |
392 | | std::string VSIS3HandleHelper::BuildURL(const std::string &osEndpoint, |
393 | | const std::string &osBucket, |
394 | | const std::string &osObjectKey, |
395 | | bool bUseHTTPS, bool bUseVirtualHosting) |
396 | 0 | { |
397 | 0 | const char *pszProtocol = (bUseHTTPS) ? "https" : "http"; |
398 | 0 | if (osBucket.empty()) |
399 | 0 | return CPLSPrintf("%s://%s", pszProtocol, osEndpoint.c_str()); |
400 | 0 | else if (bUseVirtualHosting) |
401 | 0 | return CPLSPrintf("%s://%s.%s/%s", pszProtocol, osBucket.c_str(), |
402 | 0 | osEndpoint.c_str(), |
403 | 0 | CPLAWSURLEncode(osObjectKey, false).c_str()); |
404 | 0 | else |
405 | 0 | return CPLSPrintf("%s://%s/%s/%s", pszProtocol, osEndpoint.c_str(), |
406 | 0 | osBucket.c_str(), |
407 | 0 | CPLAWSURLEncode(osObjectKey, false).c_str()); |
408 | 0 | } |
409 | | |
410 | | /************************************************************************/ |
411 | | /* RebuildURL() */ |
412 | | /************************************************************************/ |
413 | | |
414 | | void VSIS3HandleHelper::RebuildURL() |
415 | 0 | { |
416 | 0 | m_osURL = BuildURL(m_osEndpoint, m_osBucket, m_osObjectKey, m_bUseHTTPS, |
417 | 0 | m_bUseVirtualHosting); |
418 | 0 | m_osURL += GetQueryString(false); |
419 | 0 | } |
420 | | |
421 | 3.40k | IVSIS3LikeHandleHelper::IVSIS3LikeHandleHelper() = default; |
422 | | |
423 | 3.40k | IVSIS3LikeHandleHelper::~IVSIS3LikeHandleHelper() = default; |
424 | | |
425 | | /************************************************************************/ |
426 | | /* GetBucketAndObjectKey() */ |
427 | | /************************************************************************/ |
428 | | |
429 | | bool IVSIS3LikeHandleHelper::GetBucketAndObjectKey(const char *pszURI, |
430 | | const char *pszFSPrefix, |
431 | | bool bAllowNoObject, |
432 | | std::string &osBucket, |
433 | | std::string &osObjectKey) |
434 | 0 | { |
435 | 0 | osBucket = pszURI; |
436 | 0 | if (osBucket.empty()) |
437 | 0 | { |
438 | 0 | return false; |
439 | 0 | } |
440 | 0 | size_t nPos = osBucket.find('/'); |
441 | 0 | if (nPos == std::string::npos) |
442 | 0 | { |
443 | 0 | if (bAllowNoObject) |
444 | 0 | { |
445 | 0 | osObjectKey = ""; |
446 | 0 | return true; |
447 | 0 | } |
448 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
449 | 0 | "Filename should be of the form %sbucket/key", pszFSPrefix); |
450 | 0 | return false; |
451 | 0 | } |
452 | 0 | osBucket.resize(nPos); |
453 | 0 | osObjectKey = pszURI + nPos + 1; |
454 | 0 | return true; |
455 | 0 | } |
456 | | |
457 | | /************************************************************************/ |
458 | | /* BuildCanonicalizedHeaders() */ |
459 | | /************************************************************************/ |
460 | | |
461 | | std::string IVSIS3LikeHandleHelper::BuildCanonicalizedHeaders( |
462 | | std::map<std::string, std::string> &oSortedMapHeaders, |
463 | | const struct curl_slist *psExistingHeaders, const char *pszHeaderPrefix) |
464 | 0 | { |
465 | 0 | const struct curl_slist *psIter = psExistingHeaders; |
466 | 0 | for (; psIter != nullptr; psIter = psIter->next) |
467 | 0 | { |
468 | 0 | if (STARTS_WITH_CI(psIter->data, pszHeaderPrefix) || |
469 | 0 | STARTS_WITH_CI(psIter->data, "Content-MD5")) |
470 | 0 | { |
471 | 0 | const char *pszColumn = strstr(psIter->data, ":"); |
472 | 0 | if (pszColumn) |
473 | 0 | { |
474 | 0 | CPLString osKey(psIter->data); |
475 | 0 | osKey.resize(pszColumn - psIter->data); |
476 | 0 | oSortedMapHeaders[osKey.tolower()] = |
477 | 0 | CPLString(pszColumn + strlen(":")).Trim(); |
478 | 0 | } |
479 | 0 | } |
480 | 0 | } |
481 | |
|
482 | 0 | std::string osCanonicalizedHeaders; |
483 | 0 | std::map<std::string, std::string>::const_iterator oIter = |
484 | 0 | oSortedMapHeaders.begin(); |
485 | 0 | for (; oIter != oSortedMapHeaders.end(); ++oIter) |
486 | 0 | { |
487 | 0 | osCanonicalizedHeaders += oIter->first + ":" + oIter->second + "\n"; |
488 | 0 | } |
489 | 0 | return osCanonicalizedHeaders; |
490 | 0 | } |
491 | | |
492 | | /************************************************************************/ |
493 | | /* GetRFC822DateTime() */ |
494 | | /************************************************************************/ |
495 | | |
496 | | std::string IVSIS3LikeHandleHelper::GetRFC822DateTime() |
497 | 0 | { |
498 | 0 | char szDate[64]; |
499 | 0 | time_t nNow = time(nullptr); |
500 | 0 | struct tm tm; |
501 | 0 | CPLUnixTimeToYMDHMS(nNow, &tm); |
502 | 0 | int nRet = CPLPrintTime(szDate, sizeof(szDate) - 1, |
503 | 0 | "%a, %d %b %Y %H:%M:%S GMT", &tm, "C"); |
504 | 0 | szDate[nRet] = 0; |
505 | 0 | return szDate; |
506 | 0 | } |
507 | | |
508 | | /************************************************************************/ |
509 | | /* Iso8601ToUnixTime() */ |
510 | | /************************************************************************/ |
511 | | |
512 | | static bool Iso8601ToUnixTime(const char *pszDT, GIntBig *pnUnixTime) |
513 | 0 | { |
514 | 0 | int nYear; |
515 | 0 | int nMonth; |
516 | 0 | int nDay; |
517 | 0 | int nHour; |
518 | 0 | int nMinute; |
519 | 0 | int nSecond; |
520 | 0 | if (sscanf(pszDT, "%04d-%02d-%02dT%02d:%02d:%02d", &nYear, &nMonth, &nDay, |
521 | 0 | &nHour, &nMinute, &nSecond) == 6) |
522 | 0 | { |
523 | 0 | struct tm brokendowntime; |
524 | 0 | brokendowntime.tm_year = nYear - 1900; |
525 | 0 | brokendowntime.tm_mon = nMonth - 1; |
526 | 0 | brokendowntime.tm_mday = nDay; |
527 | 0 | brokendowntime.tm_hour = nHour; |
528 | 0 | brokendowntime.tm_min = nMinute; |
529 | 0 | brokendowntime.tm_sec = nSecond; |
530 | 0 | *pnUnixTime = CPLYMDHMSToUnixTime(&brokendowntime); |
531 | 0 | return true; |
532 | 0 | } |
533 | 0 | return false; |
534 | 0 | } |
535 | | |
536 | | /************************************************************************/ |
537 | | /* IsMachinePotentiallyEC2Instance() */ |
538 | | /************************************************************************/ |
539 | | |
540 | | enum class EC2InstanceCertainty |
541 | | { |
542 | | YES, |
543 | | NO, |
544 | | MAYBE |
545 | | }; |
546 | | |
547 | | static EC2InstanceCertainty IsMachinePotentiallyEC2Instance() |
548 | 371 | { |
549 | 371 | #if defined(__linux) || defined(_WIN32) |
550 | 371 | const auto IsMachinePotentiallyEC2InstanceFromLinuxHost = []() |
551 | 371 | { |
552 | | // On the newer Nitro Hypervisor (C5, M5, H1, T3), use |
553 | | // /sys/devices/virtual/dmi/id/sys_vendor = 'Amazon EC2' instead. |
554 | | |
555 | | // On older Xen hypervisor EC2 instances, a /sys/hypervisor/uuid file |
556 | | // will exist with a string beginning with 'ec2'. |
557 | | |
558 | | // If the files exist but don't contain the correct content, then we're |
559 | | // not EC2 and do not attempt any network access |
560 | | |
561 | | // Check for Xen Hypervisor instances |
562 | | // This file doesn't exist on Nitro instances |
563 | 371 | VSILFILE *fp = VSIFOpenL("/sys/hypervisor/uuid", "rb"); |
564 | 371 | if (fp != nullptr) |
565 | 0 | { |
566 | 0 | char uuid[36 + 1] = {0}; |
567 | 0 | VSIFReadL(uuid, 1, sizeof(uuid) - 1, fp); |
568 | 0 | VSIFCloseL(fp); |
569 | 0 | return EQUALN(uuid, "ec2", 3) ? EC2InstanceCertainty::YES |
570 | 0 | : EC2InstanceCertainty::NO; |
571 | 0 | } |
572 | | |
573 | | // Check for Nitro Hypervisor instances |
574 | | // This file may exist on Xen instances with a value of 'Xen' |
575 | | // (but that doesn't mean we're on EC2) |
576 | 371 | fp = VSIFOpenL("/sys/devices/virtual/dmi/id/sys_vendor", "rb"); |
577 | 371 | if (fp != nullptr) |
578 | 371 | { |
579 | 371 | char buf[10 + 1] = {0}; |
580 | 371 | VSIFReadL(buf, 1, sizeof(buf) - 1, fp); |
581 | 371 | VSIFCloseL(fp); |
582 | 371 | return EQUALN(buf, "Amazon EC2", 10) ? EC2InstanceCertainty::YES |
583 | 371 | : EC2InstanceCertainty::NO; |
584 | 371 | } |
585 | | |
586 | | // Fallback: Check via the network |
587 | 0 | return EC2InstanceCertainty::MAYBE; |
588 | 371 | }; |
589 | 371 | #endif |
590 | | |
591 | 371 | #ifdef __linux |
592 | | // Optimization on Linux to avoid the network request |
593 | | // See |
594 | | // http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/identify_ec2_instances.html |
595 | | // Skip if either: |
596 | | // - CPL_AWS_AUTODETECT_EC2=NO |
597 | | // - CPL_AWS_CHECK_HYPERVISOR_UUID=NO (deprecated) |
598 | | |
599 | 371 | if (!CPLTestBool(CPLGetConfigOption("CPL_AWS_AUTODETECT_EC2", "YES"))) |
600 | 0 | { |
601 | 0 | return EC2InstanceCertainty::MAYBE; |
602 | 0 | } |
603 | 371 | else |
604 | 371 | { |
605 | 371 | const char *opt = |
606 | 371 | CPLGetConfigOption("CPL_AWS_CHECK_HYPERVISOR_UUID", ""); |
607 | 371 | if (opt[0]) |
608 | 0 | { |
609 | 0 | CPLDebug(AWS_DEBUG_KEY, |
610 | 0 | "CPL_AWS_CHECK_HYPERVISOR_UUID is deprecated. Use " |
611 | 0 | "CPL_AWS_AUTODETECT_EC2 instead"); |
612 | 0 | if (!CPLTestBool(opt)) |
613 | 0 | { |
614 | 0 | return EC2InstanceCertainty::MAYBE; |
615 | 0 | } |
616 | 0 | } |
617 | 371 | } |
618 | | |
619 | 371 | return IsMachinePotentiallyEC2InstanceFromLinuxHost(); |
620 | | |
621 | | #elif defined(_WIN32) |
622 | | if (!CPLTestBool(CPLGetConfigOption("CPL_AWS_AUTODETECT_EC2", "YES"))) |
623 | | { |
624 | | return EC2InstanceCertainty::MAYBE; |
625 | | } |
626 | | |
627 | | // Regular UUID is not valid for WINE, fetch from sysfs instead. |
628 | | if (CPLGetWineVersion() != nullptr) |
629 | | { |
630 | | return IsMachinePotentiallyEC2InstanceFromLinuxHost(); |
631 | | } |
632 | | else |
633 | | { |
634 | | #if defined(HAVE_ATLBASE_H) |
635 | | std::string osMachineUUID; |
636 | | if (CPLFetchWindowsProductUUID(osMachineUUID)) |
637 | | { |
638 | | if (osMachineUUID.length() >= 3 && |
639 | | EQUALN(osMachineUUID.c_str(), "EC2", 3)) |
640 | | { |
641 | | return EC2InstanceCertainty::YES; |
642 | | } |
643 | | else if (osMachineUUID.length() >= 8 && osMachineUUID[4] == '2' && |
644 | | osMachineUUID[6] == 'E' && osMachineUUID[7] == 'C') |
645 | | { |
646 | | return EC2InstanceCertainty::YES; |
647 | | } |
648 | | else |
649 | | { |
650 | | return EC2InstanceCertainty::NO; |
651 | | } |
652 | | } |
653 | | #endif |
654 | | } |
655 | | |
656 | | // Fallback: Check via the network |
657 | | return EC2InstanceCertainty::MAYBE; |
658 | | #else |
659 | | // At time of writing EC2 instances can be only Linux or Windows |
660 | | return EC2InstanceCertainty::NO; |
661 | | #endif |
662 | 371 | } |
663 | | |
664 | | /************************************************************************/ |
665 | | /* ReadAWSTokenFile() */ |
666 | | /************************************************************************/ |
667 | | |
668 | | static bool ReadAWSTokenFile(const std::string &osAWSTokenFile, |
669 | | std::string &awsToken) |
670 | 0 | { |
671 | 0 | GByte *pabyOut = nullptr; |
672 | 0 | if (!VSIIngestFile(nullptr, osAWSTokenFile.c_str(), &pabyOut, nullptr, -1)) |
673 | 0 | return false; |
674 | | |
675 | 0 | awsToken = reinterpret_cast<char *>(pabyOut); |
676 | 0 | VSIFree(pabyOut); |
677 | | // Remove trailing end-of-line character |
678 | 0 | if (!awsToken.empty() && awsToken.back() == '\n') |
679 | 0 | awsToken.pop_back(); |
680 | 0 | return !awsToken.empty(); |
681 | 0 | } |
682 | | |
683 | | /************************************************************************/ |
684 | | /* GetConfigurationFromAssumeRoleWithWebIdentity() */ |
685 | | /************************************************************************/ |
686 | | |
687 | | bool VSIS3HandleHelper::GetConfigurationFromAssumeRoleWithWebIdentity( |
688 | | bool bForceRefresh, const std::string &osPathForOption, |
689 | | const std::string &osRoleArnIn, const std::string &osWebIdentityTokenFileIn, |
690 | | std::string &osSecretAccessKey, std::string &osAccessKeyId, |
691 | | std::string &osSessionToken) |
692 | 371 | { |
693 | 371 | CPLMutexHolder oHolder(&ghMutex); |
694 | 371 | if (!bForceRefresh) |
695 | 371 | { |
696 | 371 | time_t nCurTime; |
697 | 371 | time(&nCurTime); |
698 | | // Try to reuse credentials if they are still valid, but |
699 | | // keep one minute of margin... |
700 | 371 | if (!gosGlobalAccessKeyId.empty() && nCurTime < gnGlobalExpiration - 60) |
701 | 0 | { |
702 | 0 | osAccessKeyId = gosGlobalAccessKeyId; |
703 | 0 | osSecretAccessKey = gosGlobalSecretAccessKey; |
704 | 0 | osSessionToken = gosGlobalSessionToken; |
705 | 0 | return true; |
706 | 0 | } |
707 | 371 | } |
708 | | |
709 | 371 | const std::string roleArn = |
710 | 371 | !osRoleArnIn.empty() ? osRoleArnIn |
711 | 371 | : VSIGetPathSpecificOption(osPathForOption.c_str(), |
712 | 371 | "AWS_ROLE_ARN", ""); |
713 | 371 | if (roleArn.empty()) |
714 | 371 | { |
715 | 371 | CPLDebug(AWS_DEBUG_KEY, |
716 | 371 | "AWS_ROLE_ARN configuration option not defined"); |
717 | 371 | return false; |
718 | 371 | } |
719 | | |
720 | 0 | const std::string webIdentityTokenFile = |
721 | 0 | !osWebIdentityTokenFileIn.empty() |
722 | 0 | ? osWebIdentityTokenFileIn |
723 | 0 | : VSIGetPathSpecificOption(osPathForOption.c_str(), |
724 | 0 | "AWS_WEB_IDENTITY_TOKEN_FILE", ""); |
725 | 0 | if (webIdentityTokenFile.empty()) |
726 | 0 | { |
727 | 0 | CPLDebug( |
728 | 0 | AWS_DEBUG_KEY, |
729 | 0 | "AWS_WEB_IDENTITY_TOKEN_FILE configuration option not defined"); |
730 | 0 | return false; |
731 | 0 | } |
732 | | |
733 | 0 | const std::string stsRegionalEndpoints = VSIGetPathSpecificOption( |
734 | 0 | osPathForOption.c_str(), "AWS_STS_REGIONAL_ENDPOINTS", "regional"); |
735 | |
|
736 | 0 | std::string osStsDefaultUrl; |
737 | 0 | if (stsRegionalEndpoints == "regional") |
738 | 0 | { |
739 | 0 | const std::string osRegion = VSIGetPathSpecificOption( |
740 | 0 | osPathForOption.c_str(), "AWS_REGION", "us-east-1"); |
741 | 0 | osStsDefaultUrl = "https://sts." + osRegion + ".amazonaws.com"; |
742 | 0 | } |
743 | 0 | else |
744 | 0 | { |
745 | 0 | osStsDefaultUrl = "https://sts.amazonaws.com"; |
746 | 0 | } |
747 | 0 | const std::string osStsRootUrl(VSIGetPathSpecificOption( |
748 | 0 | osPathForOption.c_str(), "CPL_AWS_STS_ROOT_URL", |
749 | 0 | osStsDefaultUrl.c_str())); |
750 | | |
751 | | // Get token from web identity token file |
752 | 0 | std::string webIdentityToken; |
753 | 0 | if (!ReadAWSTokenFile(webIdentityTokenFile, webIdentityToken)) |
754 | 0 | { |
755 | 0 | CPLDebug(AWS_DEBUG_KEY, "%s is empty", webIdentityTokenFile.c_str()); |
756 | 0 | return false; |
757 | 0 | } |
758 | | |
759 | | // Get credentials from sts AssumeRoleWithWebIdentity |
760 | 0 | std::string osExpiration; |
761 | 0 | { |
762 | 0 | const std::string osSTS_asuume_role_with_web_identity_URL = |
763 | 0 | osStsRootUrl + |
764 | 0 | "/?Action=AssumeRoleWithWebIdentity&RoleSessionName=gdal" |
765 | 0 | "&Version=2011-06-15&RoleArn=" + |
766 | 0 | CPLAWSURLEncode(roleArn) + |
767 | 0 | "&WebIdentityToken=" + CPLAWSURLEncode(webIdentityToken); |
768 | |
|
769 | 0 | CPLPushErrorHandler(CPLQuietErrorHandler); |
770 | |
|
771 | 0 | CPLHTTPResult *psResult = CPLHTTPFetch( |
772 | 0 | osSTS_asuume_role_with_web_identity_URL.c_str(), nullptr); |
773 | 0 | CPLPopErrorHandler(); |
774 | 0 | if (psResult) |
775 | 0 | { |
776 | 0 | if (psResult->nStatus == 0 && psResult->pabyData != nullptr) |
777 | 0 | { |
778 | 0 | CPLXMLTreeCloser oTree(CPLParseXMLString( |
779 | 0 | reinterpret_cast<char *>(psResult->pabyData))); |
780 | 0 | if (oTree) |
781 | 0 | { |
782 | 0 | const auto psCredentials = CPLGetXMLNode( |
783 | 0 | oTree.get(), |
784 | 0 | "=AssumeRoleWithWebIdentityResponse." |
785 | 0 | "AssumeRoleWithWebIdentityResult.Credentials"); |
786 | 0 | if (psCredentials) |
787 | 0 | { |
788 | 0 | osAccessKeyId = |
789 | 0 | CPLGetXMLValue(psCredentials, "AccessKeyId", ""); |
790 | 0 | osSecretAccessKey = CPLGetXMLValue( |
791 | 0 | psCredentials, "SecretAccessKey", ""); |
792 | 0 | osSessionToken = |
793 | 0 | CPLGetXMLValue(psCredentials, "SessionToken", ""); |
794 | 0 | osExpiration = |
795 | 0 | CPLGetXMLValue(psCredentials, "Expiration", ""); |
796 | 0 | } |
797 | 0 | } |
798 | 0 | } |
799 | 0 | CPLHTTPDestroyResult(psResult); |
800 | 0 | } |
801 | 0 | } |
802 | |
|
803 | 0 | GIntBig nExpirationUnix = 0; |
804 | 0 | if (!osAccessKeyId.empty() && !osSecretAccessKey.empty() && |
805 | 0 | !osSessionToken.empty() && |
806 | 0 | Iso8601ToUnixTime(osExpiration.c_str(), &nExpirationUnix)) |
807 | 0 | { |
808 | 0 | gosGlobalAccessKeyId = osAccessKeyId; |
809 | 0 | gosGlobalSecretAccessKey = osSecretAccessKey; |
810 | 0 | gosGlobalSessionToken = osSessionToken; |
811 | 0 | gnGlobalExpiration = nExpirationUnix; |
812 | 0 | CPLDebug(AWS_DEBUG_KEY, "Storing AIM credentials until %s", |
813 | 0 | osExpiration.c_str()); |
814 | 0 | } |
815 | 0 | return !osAccessKeyId.empty() && !osSecretAccessKey.empty() && |
816 | 0 | !osSessionToken.empty(); |
817 | 0 | } |
818 | | |
819 | | /************************************************************************/ |
820 | | /* GetConfigurationFromEC2() */ |
821 | | /************************************************************************/ |
822 | | |
823 | | bool VSIS3HandleHelper::GetConfigurationFromEC2( |
824 | | bool bForceRefresh, const std::string &osPathForOption, |
825 | | std::string &osSecretAccessKey, std::string &osAccessKeyId, |
826 | | std::string &osSessionToken) |
827 | 371 | { |
828 | 371 | CPLMutexHolder oHolder(&ghMutex); |
829 | 371 | if (!bForceRefresh) |
830 | 371 | { |
831 | 371 | time_t nCurTime; |
832 | 371 | time(&nCurTime); |
833 | | // Try to reuse credentials if they are still valid, but |
834 | | // keep one minute of margin... |
835 | 371 | if (!gosGlobalAccessKeyId.empty() && nCurTime < gnGlobalExpiration - 60) |
836 | 0 | { |
837 | 0 | osAccessKeyId = gosGlobalAccessKeyId; |
838 | 0 | osSecretAccessKey = gosGlobalSecretAccessKey; |
839 | 0 | osSessionToken = gosGlobalSessionToken; |
840 | 0 | return true; |
841 | 0 | } |
842 | 371 | } |
843 | | |
844 | 371 | std::string osURLRefreshCredentials; |
845 | 371 | const std::string osEC2DefaultURL("http://169.254.169.254"); |
846 | | // coverity[tainted_data] |
847 | 371 | const std::string osEC2RootURL(VSIGetPathSpecificOption( |
848 | 371 | osPathForOption.c_str(), "CPL_AWS_EC2_API_ROOT_URL", |
849 | 371 | osEC2DefaultURL.c_str())); |
850 | | // coverity[tainted_data] |
851 | 371 | std::string osECSFullURI(VSIGetPathSpecificOption( |
852 | 371 | osPathForOption.c_str(), "AWS_CONTAINER_CREDENTIALS_FULL_URI", "")); |
853 | | // coverity[tainted_data] |
854 | 371 | const std::string osECSRelativeURI( |
855 | 371 | osECSFullURI.empty() ? VSIGetPathSpecificOption( |
856 | 371 | osPathForOption.c_str(), |
857 | 371 | "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI", "") |
858 | 371 | : std::string()); |
859 | | // coverity[tainted_data] |
860 | 371 | const std::string osECSTokenFile( |
861 | 371 | (osECSFullURI.empty() && osECSRelativeURI.empty()) |
862 | 371 | ? std::string() |
863 | 371 | : VSIGetPathSpecificOption(osPathForOption.c_str(), |
864 | 0 | "AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE", |
865 | 0 | "")); |
866 | | |
867 | | // coverity[tainted_data] |
868 | 371 | std::string osECSTokenValue( |
869 | 371 | (osECSFullURI.empty() && osECSRelativeURI.empty() && |
870 | 371 | !osECSTokenFile.empty()) |
871 | 371 | ? std::string() |
872 | 371 | : VSIGetPathSpecificOption(osPathForOption.c_str(), |
873 | 371 | "AWS_CONTAINER_AUTHORIZATION_TOKEN", |
874 | 371 | "")); |
875 | | |
876 | 371 | std::string osECSToken; |
877 | 371 | if (!osECSTokenFile.empty()) |
878 | 0 | { |
879 | 0 | if (!ReadAWSTokenFile(osECSTokenFile, osECSToken)) |
880 | 0 | { |
881 | 0 | CPLDebug(AWS_DEBUG_KEY, "%s is empty", osECSTokenFile.c_str()); |
882 | 0 | } |
883 | 0 | } |
884 | 371 | else if (!osECSTokenValue.empty()) |
885 | 0 | { |
886 | 0 | osECSToken = std::move(osECSTokenValue); |
887 | 0 | } |
888 | | |
889 | 371 | std::string osToken; |
890 | 371 | if (!osECSFullURI.empty()) |
891 | 0 | { |
892 | | // Cf https://docs.aws.amazon.com/sdkref/latest/guide/feature-container-credentials.html |
893 | 0 | osURLRefreshCredentials = std::move(osECSFullURI); |
894 | 0 | } |
895 | 371 | else if (osEC2RootURL == osEC2DefaultURL && !osECSRelativeURI.empty()) |
896 | 0 | { |
897 | | // See |
898 | | // https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html |
899 | 0 | osURLRefreshCredentials = "http://169.254.170.2" + osECSRelativeURI; |
900 | 0 | } |
901 | 371 | else |
902 | 371 | { |
903 | 371 | const auto eIsEC2 = IsMachinePotentiallyEC2Instance(); |
904 | 371 | if (eIsEC2 == EC2InstanceCertainty::NO) |
905 | 371 | return false; |
906 | | |
907 | | // Use IMDSv2 protocol: |
908 | | // https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-instance-metadata-service.html |
909 | | |
910 | | // Retrieve IMDSv2 token |
911 | 0 | { |
912 | 0 | const std::string osEC2_IMDSv2_api_token_URL = |
913 | 0 | osEC2RootURL + "/latest/api/token"; |
914 | 0 | CPLStringList aosOptions; |
915 | 0 | aosOptions.SetNameValue("TIMEOUT", "1"); |
916 | 0 | aosOptions.SetNameValue("CUSTOMREQUEST", "PUT"); |
917 | 0 | aosOptions.SetNameValue("HEADERS", |
918 | 0 | "X-aws-ec2-metadata-token-ttl-seconds: 10"); |
919 | 0 | CPLPushErrorHandler(CPLQuietErrorHandler); |
920 | 0 | CPLHTTPResult *psResult = CPLHTTPFetch( |
921 | 0 | osEC2_IMDSv2_api_token_URL.c_str(), aosOptions.List()); |
922 | 0 | CPLPopErrorHandler(); |
923 | 0 | if (psResult) |
924 | 0 | { |
925 | 0 | if (psResult->nStatus == 0 && psResult->pabyData != nullptr) |
926 | 0 | { |
927 | 0 | osToken = reinterpret_cast<char *>(psResult->pabyData); |
928 | 0 | } |
929 | 0 | else |
930 | 0 | { |
931 | | // Failure: either we are not running on EC2 (or something |
932 | | // emulating it) or this doesn't implement yet IMDSv2. |
933 | | // Fallback to IMDSv1 |
934 | | |
935 | | // /latest/api/token doesn't work inside a Docker container |
936 | | // that has no host networking. Cf |
937 | | // https://community.grafana.com/t/imdsv2-is-not-working-from-docker/65944 |
938 | 0 | if (psResult->pszErrBuf != nullptr && |
939 | 0 | strstr(psResult->pszErrBuf, |
940 | 0 | "Operation timed out after") != nullptr) |
941 | 0 | { |
942 | 0 | aosOptions.Clear(); |
943 | 0 | aosOptions.SetNameValue("TIMEOUT", "1"); |
944 | 0 | CPLPushErrorHandler(CPLQuietErrorHandler); |
945 | 0 | CPLHTTPResult *psResult2 = CPLHTTPFetch( |
946 | 0 | (osEC2RootURL + "/latest/meta-data").c_str(), |
947 | 0 | aosOptions.List()); |
948 | 0 | CPLPopErrorHandler(); |
949 | 0 | if (psResult2) |
950 | 0 | { |
951 | 0 | if (psResult2->nStatus == 0 && |
952 | 0 | psResult2->pabyData != nullptr) |
953 | 0 | { |
954 | 0 | CPLDebug(AWS_DEBUG_KEY, |
955 | 0 | "/latest/api/token EC2 IMDSv2 request " |
956 | 0 | "timed out, but /latest/metadata " |
957 | 0 | "succeeded. " |
958 | 0 | "Trying with IMDSv1. " |
959 | 0 | "Consult " |
960 | 0 | "https://gdal.org/user/" |
961 | 0 | "virtual_file_systems.html#vsis3_imds " |
962 | 0 | "for IMDS related issues."); |
963 | 0 | } |
964 | 0 | CPLHTTPDestroyResult(psResult2); |
965 | 0 | } |
966 | 0 | } |
967 | 0 | } |
968 | 0 | CPLHTTPDestroyResult(psResult); |
969 | 0 | } |
970 | 0 | CPLErrorReset(); |
971 | 0 | } |
972 | | |
973 | | // If we don't know yet the IAM role, fetch it |
974 | 0 | const std::string osEC2CredentialsURL = |
975 | 0 | osEC2RootURL + "/latest/meta-data/iam/security-credentials/"; |
976 | 0 | if (gosIAMRole.empty()) |
977 | 0 | { |
978 | 0 | CPLStringList aosOptions; |
979 | 0 | aosOptions.SetNameValue("TIMEOUT", "1"); |
980 | 0 | if (!osToken.empty()) |
981 | 0 | { |
982 | 0 | aosOptions.SetNameValue( |
983 | 0 | "HEADERS", |
984 | 0 | ("X-aws-ec2-metadata-token: " + osToken).c_str()); |
985 | 0 | } |
986 | 0 | CPLPushErrorHandler(CPLQuietErrorHandler); |
987 | 0 | CPLHTTPResult *psResult = |
988 | 0 | CPLHTTPFetch(osEC2CredentialsURL.c_str(), aosOptions.List()); |
989 | 0 | CPLPopErrorHandler(); |
990 | 0 | if (psResult) |
991 | 0 | { |
992 | 0 | if (psResult->nStatus == 0 && psResult->pabyData != nullptr) |
993 | 0 | { |
994 | 0 | gosIAMRole = reinterpret_cast<char *>(psResult->pabyData); |
995 | 0 | } |
996 | 0 | CPLHTTPDestroyResult(psResult); |
997 | 0 | } |
998 | 0 | CPLErrorReset(); |
999 | 0 | if (gosIAMRole.empty()) |
1000 | 0 | { |
1001 | | // We didn't get the IAM role. We are definitely not running |
1002 | | // on (a correctly configured) EC2 or an emulation of it. |
1003 | |
|
1004 | 0 | if (eIsEC2 == EC2InstanceCertainty::YES) |
1005 | 0 | { |
1006 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
1007 | 0 | "EC2 IMDSv2 and IMDSv1 requests failed. Consult " |
1008 | 0 | "https://gdal.org/user/" |
1009 | 0 | "virtual_file_systems.html#vsis3_imds " |
1010 | 0 | "for IMDS related issues."); |
1011 | 0 | } |
1012 | |
|
1013 | 0 | return false; |
1014 | 0 | } |
1015 | 0 | } |
1016 | 0 | osURLRefreshCredentials = osEC2CredentialsURL + gosIAMRole; |
1017 | 0 | } |
1018 | | |
1019 | | // Now fetch the refreshed credentials |
1020 | 0 | CPLStringList oResponse; |
1021 | 0 | CPLStringList aosOptions; |
1022 | 0 | if (!osToken.empty()) |
1023 | 0 | { |
1024 | 0 | aosOptions.SetNameValue( |
1025 | 0 | "HEADERS", ("X-aws-ec2-metadata-token: " + osToken).c_str()); |
1026 | 0 | } |
1027 | 0 | else if (!osECSToken.empty()) |
1028 | 0 | { |
1029 | 0 | aosOptions.SetNameValue("HEADERS", |
1030 | 0 | ("Authorization: " + osECSToken).c_str()); |
1031 | 0 | } |
1032 | 0 | CPLHTTPResult *psResult = |
1033 | 0 | CPLHTTPFetch(osURLRefreshCredentials.c_str(), aosOptions.List()); |
1034 | 0 | if (psResult) |
1035 | 0 | { |
1036 | 0 | if (psResult->nStatus == 0 && psResult->pabyData != nullptr) |
1037 | 0 | { |
1038 | 0 | const std::string osJSon = |
1039 | 0 | reinterpret_cast<char *>(psResult->pabyData); |
1040 | 0 | oResponse = CPLParseKeyValueJson(osJSon.c_str()); |
1041 | 0 | } |
1042 | 0 | CPLHTTPDestroyResult(psResult); |
1043 | 0 | } |
1044 | 0 | CPLErrorReset(); |
1045 | 0 | osAccessKeyId = oResponse.FetchNameValueDef("AccessKeyId", ""); |
1046 | 0 | osSecretAccessKey = oResponse.FetchNameValueDef("SecretAccessKey", ""); |
1047 | 0 | osSessionToken = oResponse.FetchNameValueDef("Token", ""); |
1048 | 0 | const std::string osExpiration = |
1049 | 0 | oResponse.FetchNameValueDef("Expiration", ""); |
1050 | 0 | GIntBig nExpirationUnix = 0; |
1051 | 0 | if (!osAccessKeyId.empty() && !osSecretAccessKey.empty() && |
1052 | 0 | Iso8601ToUnixTime(osExpiration.c_str(), &nExpirationUnix)) |
1053 | 0 | { |
1054 | 0 | gosGlobalAccessKeyId = osAccessKeyId; |
1055 | 0 | gosGlobalSecretAccessKey = osSecretAccessKey; |
1056 | 0 | gosGlobalSessionToken = osSessionToken; |
1057 | 0 | gnGlobalExpiration = nExpirationUnix; |
1058 | 0 | CPLDebug(AWS_DEBUG_KEY, "Storing AIM credentials until %s", |
1059 | 0 | osExpiration.c_str()); |
1060 | 0 | } |
1061 | 0 | return !osAccessKeyId.empty() && !osSecretAccessKey.empty(); |
1062 | 371 | } |
1063 | | |
1064 | | /************************************************************************/ |
1065 | | /* UpdateAndWarnIfInconsistent() */ |
1066 | | /************************************************************************/ |
1067 | | |
1068 | | static void UpdateAndWarnIfInconsistent(const char *pszKeyword, |
1069 | | std::string &osVal, |
1070 | | const std::string &osNewVal, |
1071 | | const std::string &osCredentials, |
1072 | | const std::string &osConfig) |
1073 | 0 | { |
1074 | | // nominally defined in ~/.aws/credentials but can |
1075 | | // be set here too. If both values exist, credentials |
1076 | | // has the priority |
1077 | 0 | if (osVal.empty()) |
1078 | 0 | { |
1079 | 0 | osVal = osNewVal; |
1080 | 0 | } |
1081 | 0 | else if (osVal != osNewVal) |
1082 | 0 | { |
1083 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
1084 | 0 | "%s defined in both %s " |
1085 | 0 | "and %s. The one of %s will be used", |
1086 | 0 | pszKeyword, osCredentials.c_str(), osConfig.c_str(), |
1087 | 0 | osCredentials.c_str()); |
1088 | 0 | } |
1089 | 0 | } |
1090 | | |
1091 | | /************************************************************************/ |
1092 | | /* ReadAWSCredentials() */ |
1093 | | /************************************************************************/ |
1094 | | |
1095 | | static bool ReadAWSCredentials(const std::string &osProfile, |
1096 | | const std::string &osCredentials, |
1097 | | std::string &osSecretAccessKey, |
1098 | | std::string &osAccessKeyId, |
1099 | | std::string &osSessionToken) |
1100 | 371 | { |
1101 | 371 | osSecretAccessKey.clear(); |
1102 | 371 | osAccessKeyId.clear(); |
1103 | 371 | osSessionToken.clear(); |
1104 | | |
1105 | 371 | VSILFILE *fp = VSIFOpenL(osCredentials.c_str(), "rb"); |
1106 | 371 | if (fp != nullptr) |
1107 | 0 | { |
1108 | 0 | const char *pszLine; |
1109 | 0 | bool bInProfile = false; |
1110 | 0 | const std::string osBracketedProfile("[" + osProfile + "]"); |
1111 | 0 | while ((pszLine = CPLReadLineL(fp)) != nullptr) |
1112 | 0 | { |
1113 | 0 | if (pszLine[0] == '[') |
1114 | 0 | { |
1115 | 0 | if (bInProfile) |
1116 | 0 | break; |
1117 | 0 | if (std::string(pszLine) == osBracketedProfile) |
1118 | 0 | bInProfile = true; |
1119 | 0 | } |
1120 | 0 | else if (bInProfile) |
1121 | 0 | { |
1122 | 0 | char *pszKey = nullptr; |
1123 | 0 | const char *pszValue = CPLParseNameValue(pszLine, &pszKey); |
1124 | 0 | if (pszKey && pszValue) |
1125 | 0 | { |
1126 | 0 | if (EQUAL(pszKey, "aws_access_key_id")) |
1127 | 0 | osAccessKeyId = pszValue; |
1128 | 0 | else if (EQUAL(pszKey, "aws_secret_access_key")) |
1129 | 0 | osSecretAccessKey = pszValue; |
1130 | 0 | else if (EQUAL(pszKey, "aws_session_token")) |
1131 | 0 | osSessionToken = pszValue; |
1132 | 0 | } |
1133 | 0 | CPLFree(pszKey); |
1134 | 0 | } |
1135 | 0 | } |
1136 | 0 | VSIFCloseL(fp); |
1137 | 0 | } |
1138 | | |
1139 | 371 | return !osSecretAccessKey.empty() && !osAccessKeyId.empty(); |
1140 | 371 | } |
1141 | | |
1142 | | /************************************************************************/ |
1143 | | /* GetDirSeparator() */ |
1144 | | /************************************************************************/ |
1145 | | |
1146 | | static const char *GetDirSeparator() |
1147 | 1.11k | { |
1148 | | #ifdef _WIN32 |
1149 | | static const char SEP_STRING[] = "\\"; |
1150 | | #else |
1151 | 1.11k | static const char SEP_STRING[] = "/"; |
1152 | 1.11k | #endif |
1153 | 1.11k | return SEP_STRING; |
1154 | 1.11k | } |
1155 | | |
1156 | | /************************************************************************/ |
1157 | | /* GetAWSRootDirectory() */ |
1158 | | /************************************************************************/ |
1159 | | |
1160 | | static std::string GetAWSRootDirectory() |
1161 | 371 | { |
1162 | 371 | const char *pszAWSRootDir = CPLGetConfigOption("CPL_AWS_ROOT_DIR", nullptr); |
1163 | 371 | if (pszAWSRootDir) |
1164 | 0 | return pszAWSRootDir; |
1165 | | #ifdef _WIN32 |
1166 | | const char *pszHome = CPLGetConfigOption("USERPROFILE", nullptr); |
1167 | | #else |
1168 | 371 | const char *pszHome = CPLGetConfigOption("HOME", nullptr); |
1169 | 371 | #endif |
1170 | | |
1171 | 371 | return std::string(pszHome ? pszHome : "") |
1172 | 371 | .append(GetDirSeparator()) |
1173 | 371 | .append(".aws"); |
1174 | 371 | } |
1175 | | |
1176 | | /************************************************************************/ |
1177 | | /* GetConfigurationFromAWSConfigFiles() */ |
1178 | | /************************************************************************/ |
1179 | | |
1180 | | bool VSIS3HandleHelper::GetConfigurationFromAWSConfigFiles( |
1181 | | const std::string &osPathForOption, const char *pszProfile, |
1182 | | std::string &osSecretAccessKey, std::string &osAccessKeyId, |
1183 | | std::string &osSessionToken, std::string &osRegion, |
1184 | | std::string &osCredentials, std::string &osRoleArn, |
1185 | | std::string &osSourceProfile, std::string &osExternalId, |
1186 | | std::string &osMFASerial, std::string &osRoleSessionName, |
1187 | | std::string &osWebIdentityTokenFile, std::string &osSSOStartURL, |
1188 | | std::string &osSSOAccountID, std::string &osSSORoleName) |
1189 | 371 | { |
1190 | | // See http://docs.aws.amazon.com/cli/latest/userguide/cli-config-files.html |
1191 | | // If AWS_DEFAULT_PROFILE is set (obsolete, no longer documented), use it in |
1192 | | // priority Otherwise use AWS_PROFILE Otherwise fallback to "default" |
1193 | 371 | const char *pszProfileOri = pszProfile; |
1194 | 371 | if (pszProfile == nullptr) |
1195 | 371 | { |
1196 | 371 | pszProfile = VSIGetPathSpecificOption(osPathForOption.c_str(), |
1197 | 371 | "AWS_DEFAULT_PROFILE", ""); |
1198 | 371 | if (pszProfile[0] == '\0') |
1199 | 371 | pszProfile = VSIGetPathSpecificOption(osPathForOption.c_str(), |
1200 | 371 | "AWS_PROFILE", ""); |
1201 | 371 | } |
1202 | 371 | const std::string osProfile(pszProfile[0] != '\0' ? pszProfile : "default"); |
1203 | | |
1204 | 371 | std::string osDotAws(GetAWSRootDirectory()); |
1205 | | |
1206 | | // Read first ~/.aws/credential file |
1207 | | |
1208 | | // GDAL specific config option (mostly for testing purpose, but also |
1209 | | // used in production in some cases) |
1210 | 371 | const char *pszCredentials = VSIGetPathSpecificOption( |
1211 | 371 | osPathForOption.c_str(), "CPL_AWS_CREDENTIALS_FILE", nullptr); |
1212 | 371 | if (pszCredentials) |
1213 | 0 | { |
1214 | 0 | osCredentials = pszCredentials; |
1215 | 0 | } |
1216 | 371 | else |
1217 | 371 | { |
1218 | 371 | osCredentials = osDotAws; |
1219 | 371 | osCredentials += GetDirSeparator(); |
1220 | 371 | osCredentials += "credentials"; |
1221 | 371 | } |
1222 | | |
1223 | 371 | ReadAWSCredentials(osProfile, osCredentials, osSecretAccessKey, |
1224 | 371 | osAccessKeyId, osSessionToken); |
1225 | | |
1226 | | // And then ~/.aws/config file (unless AWS_CONFIG_FILE is defined) |
1227 | 371 | const char *pszAWSConfigFileEnv = VSIGetPathSpecificOption( |
1228 | 371 | osPathForOption.c_str(), "AWS_CONFIG_FILE", nullptr); |
1229 | 371 | std::string osConfig; |
1230 | 371 | if (pszAWSConfigFileEnv && pszAWSConfigFileEnv[0]) |
1231 | 0 | { |
1232 | 0 | osConfig = pszAWSConfigFileEnv; |
1233 | 0 | } |
1234 | 371 | else |
1235 | 371 | { |
1236 | 371 | osConfig = std::move(osDotAws); |
1237 | 371 | osConfig += GetDirSeparator(); |
1238 | 371 | osConfig += "config"; |
1239 | 371 | } |
1240 | | |
1241 | 371 | VSILFILE *fp = VSIFOpenL(osConfig.c_str(), "rb"); |
1242 | 371 | if (fp != nullptr) |
1243 | 0 | { |
1244 | | // Start by reading sso-session's |
1245 | 0 | const char *pszLine; |
1246 | 0 | std::map<std::string, std::map<std::string, std::string>> |
1247 | 0 | oMapSSOSessions; |
1248 | 0 | std::string osSSOSession; |
1249 | 0 | while ((pszLine = CPLReadLineL(fp)) != nullptr) |
1250 | 0 | { |
1251 | 0 | if (STARTS_WITH(pszLine, "[sso-session ") && |
1252 | 0 | pszLine[strlen(pszLine) - 1] == ']') |
1253 | 0 | { |
1254 | 0 | osSSOSession = pszLine + strlen("[sso-session "); |
1255 | 0 | osSSOSession.pop_back(); |
1256 | 0 | } |
1257 | 0 | else if (pszLine[0] == '[') |
1258 | 0 | { |
1259 | 0 | osSSOSession.clear(); |
1260 | 0 | } |
1261 | 0 | else if (!osSSOSession.empty()) |
1262 | 0 | { |
1263 | 0 | char *pszKey = nullptr; |
1264 | 0 | const char *pszValue = CPLParseNameValue(pszLine, &pszKey); |
1265 | 0 | if (pszKey && pszValue) |
1266 | 0 | { |
1267 | | // CPLDebugOnly(AWS_DEBUG_KEY, "oMapSSOSessions[%s][%s] = %s", |
1268 | | // osSSOSession.c_str(), pszKey, pszValue); |
1269 | 0 | oMapSSOSessions[osSSOSession][pszKey] = pszValue; |
1270 | 0 | } |
1271 | 0 | CPLFree(pszKey); |
1272 | 0 | } |
1273 | 0 | } |
1274 | 0 | osSSOSession.clear(); |
1275 | |
|
1276 | 0 | bool bInProfile = false; |
1277 | 0 | const std::string osBracketedProfile("[" + osProfile + "]"); |
1278 | 0 | const std::string osBracketedProfileProfile("[profile " + osProfile + |
1279 | 0 | "]"); |
1280 | |
|
1281 | 0 | VSIFSeekL(fp, 0, SEEK_SET); |
1282 | |
|
1283 | 0 | while ((pszLine = CPLReadLineL(fp)) != nullptr) |
1284 | 0 | { |
1285 | 0 | if (pszLine[0] == '[') |
1286 | 0 | { |
1287 | 0 | if (bInProfile) |
1288 | 0 | break; |
1289 | | // In config file, the section name is nominally [profile foo] |
1290 | | // for the non default profile. |
1291 | 0 | if (std::string(pszLine) == osBracketedProfile || |
1292 | 0 | std::string(pszLine) == osBracketedProfileProfile) |
1293 | 0 | { |
1294 | 0 | bInProfile = true; |
1295 | 0 | } |
1296 | 0 | } |
1297 | 0 | else if (bInProfile) |
1298 | 0 | { |
1299 | 0 | char *pszKey = nullptr; |
1300 | 0 | const char *pszValue = CPLParseNameValue(pszLine, &pszKey); |
1301 | 0 | if (pszKey && pszValue) |
1302 | 0 | { |
1303 | 0 | if (EQUAL(pszKey, "aws_access_key_id")) |
1304 | 0 | { |
1305 | 0 | UpdateAndWarnIfInconsistent(pszKey, osAccessKeyId, |
1306 | 0 | pszValue, osCredentials, |
1307 | 0 | osConfig); |
1308 | 0 | } |
1309 | 0 | else if (EQUAL(pszKey, "aws_secret_access_key")) |
1310 | 0 | { |
1311 | 0 | UpdateAndWarnIfInconsistent(pszKey, osSecretAccessKey, |
1312 | 0 | pszValue, osCredentials, |
1313 | 0 | osConfig); |
1314 | 0 | } |
1315 | 0 | else if (EQUAL(pszKey, "aws_session_token")) |
1316 | 0 | { |
1317 | 0 | UpdateAndWarnIfInconsistent(pszKey, osSessionToken, |
1318 | 0 | pszValue, osCredentials, |
1319 | 0 | osConfig); |
1320 | 0 | } |
1321 | 0 | else if (EQUAL(pszKey, "region")) |
1322 | 0 | { |
1323 | 0 | osRegion = pszValue; |
1324 | 0 | } |
1325 | 0 | else if (strcmp(pszKey, "role_arn") == 0) |
1326 | 0 | { |
1327 | 0 | osRoleArn = pszValue; |
1328 | 0 | } |
1329 | 0 | else if (strcmp(pszKey, "source_profile") == 0) |
1330 | 0 | { |
1331 | 0 | osSourceProfile = pszValue; |
1332 | 0 | } |
1333 | 0 | else if (strcmp(pszKey, "external_id") == 0) |
1334 | 0 | { |
1335 | 0 | osExternalId = pszValue; |
1336 | 0 | } |
1337 | 0 | else if (strcmp(pszKey, "mfa_serial") == 0) |
1338 | 0 | { |
1339 | 0 | osMFASerial = pszValue; |
1340 | 0 | } |
1341 | 0 | else if (strcmp(pszKey, "role_session_name") == 0) |
1342 | 0 | { |
1343 | 0 | osRoleSessionName = pszValue; |
1344 | 0 | } |
1345 | 0 | else if (strcmp(pszKey, "web_identity_token_file") == 0) |
1346 | 0 | { |
1347 | 0 | osWebIdentityTokenFile = pszValue; |
1348 | 0 | } |
1349 | 0 | else if (strcmp(pszKey, "sso_session") == 0) |
1350 | 0 | { |
1351 | 0 | osSSOSession = pszValue; |
1352 | 0 | } |
1353 | 0 | else if (strcmp(pszKey, "sso_start_url") == 0) |
1354 | 0 | { |
1355 | 0 | osSSOStartURL = pszValue; |
1356 | 0 | } |
1357 | 0 | else if (strcmp(pszKey, "sso_account_id") == 0) |
1358 | 0 | { |
1359 | 0 | osSSOAccountID = pszValue; |
1360 | 0 | } |
1361 | 0 | else if (strcmp(pszKey, "sso_role_name") == 0) |
1362 | 0 | { |
1363 | 0 | osSSORoleName = pszValue; |
1364 | 0 | } |
1365 | 0 | } |
1366 | 0 | CPLFree(pszKey); |
1367 | 0 | } |
1368 | 0 | } |
1369 | 0 | VSIFCloseL(fp); |
1370 | |
|
1371 | 0 | if (!osSSOSession.empty()) |
1372 | 0 | { |
1373 | 0 | if (osSSOStartURL.empty()) |
1374 | 0 | osSSOStartURL = oMapSSOSessions[osSSOSession]["sso_start_url"]; |
1375 | 0 | } |
1376 | 0 | } |
1377 | 371 | else if (pszAWSConfigFileEnv != nullptr) |
1378 | 0 | { |
1379 | 0 | if (pszAWSConfigFileEnv[0] != '\0') |
1380 | 0 | { |
1381 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
1382 | 0 | "%s does not exist or cannot be open", |
1383 | 0 | pszAWSConfigFileEnv); |
1384 | 0 | } |
1385 | 0 | } |
1386 | | |
1387 | 371 | return (!osAccessKeyId.empty() && !osSecretAccessKey.empty()) || |
1388 | 371 | (!osRoleArn.empty() && !osSourceProfile.empty()) || |
1389 | 371 | (pszProfileOri != nullptr && !osRoleArn.empty() && |
1390 | 371 | !osWebIdentityTokenFile.empty()) || |
1391 | 371 | (!osSSOStartURL.empty() && !osSSOAccountID.empty() && |
1392 | 371 | !osSSORoleName.empty()); |
1393 | 371 | } |
1394 | | |
1395 | | /************************************************************************/ |
1396 | | /* GetTemporaryCredentialsForRole() */ |
1397 | | /************************************************************************/ |
1398 | | |
1399 | | // Issue a STS AssumedRole operation to get temporary credentials for an assumed |
1400 | | // role. |
1401 | | static bool GetTemporaryCredentialsForRole( |
1402 | | const std::string &osRoleArn, const std::string &osExternalId, |
1403 | | const std::string &osMFASerial, const std::string &osRoleSessionName, |
1404 | | const std::string &osSecretAccessKey, const std::string &osAccessKeyId, |
1405 | | const std::string &osSessionToken, std::string &osTempSecretAccessKey, |
1406 | | std::string &osTempAccessKeyId, std::string &osTempSessionToken, |
1407 | | std::string &osExpiration) |
1408 | 0 | { |
1409 | 0 | std::string osXAMZDate = CPLGetConfigOption("AWS_TIMESTAMP", ""); |
1410 | 0 | if (osXAMZDate.empty()) |
1411 | 0 | osXAMZDate = CPLGetAWS_SIGN4_Timestamp(time(nullptr)); |
1412 | 0 | std::string osDate(osXAMZDate); |
1413 | 0 | osDate.resize(8); |
1414 | |
|
1415 | 0 | const std::string osVerb("GET"); |
1416 | 0 | const std::string osService("sts"); |
1417 | 0 | const std::string osRegion( |
1418 | 0 | CPLGetConfigOption("AWS_STS_REGION", "us-east-1")); |
1419 | 0 | const std::string osHost( |
1420 | 0 | CPLGetConfigOption("AWS_STS_ENDPOINT", "sts.amazonaws.com")); |
1421 | |
|
1422 | 0 | std::map<std::string, std::string> oMap; |
1423 | 0 | oMap["Version"] = "2011-06-15"; |
1424 | 0 | oMap["Action"] = "AssumeRole"; |
1425 | 0 | oMap["RoleArn"] = osRoleArn; |
1426 | 0 | oMap["RoleSessionName"] = |
1427 | 0 | !osRoleSessionName.empty() |
1428 | 0 | ? osRoleSessionName.c_str() |
1429 | 0 | : CPLGetConfigOption("AWS_ROLE_SESSION_NAME", "GDAL-session"); |
1430 | 0 | if (!osExternalId.empty()) |
1431 | 0 | oMap["ExternalId"] = osExternalId; |
1432 | 0 | if (!osMFASerial.empty()) |
1433 | 0 | oMap["SerialNumber"] = osMFASerial; |
1434 | |
|
1435 | 0 | std::string osQueryString; |
1436 | 0 | for (const auto &kv : oMap) |
1437 | 0 | { |
1438 | 0 | if (osQueryString.empty()) |
1439 | 0 | osQueryString += "?"; |
1440 | 0 | else |
1441 | 0 | osQueryString += "&"; |
1442 | 0 | osQueryString += kv.first; |
1443 | 0 | osQueryString += "="; |
1444 | 0 | osQueryString += CPLAWSURLEncode(kv.second); |
1445 | 0 | } |
1446 | 0 | std::string osCanonicalQueryString(osQueryString.substr(1)); |
1447 | |
|
1448 | 0 | const std::string osAuthorization = CPLGetAWS_SIGN4_Authorization( |
1449 | 0 | osSecretAccessKey, osAccessKeyId, osSessionToken, osRegion, |
1450 | 0 | std::string(), // m_osRequestPayer, |
1451 | 0 | osService, osVerb, |
1452 | 0 | nullptr, // psExistingHeaders, |
1453 | 0 | osHost, "/", osCanonicalQueryString, |
1454 | 0 | CPLGetLowerCaseHexSHA256(std::string()), |
1455 | 0 | false, // bAddHeaderAMZContentSHA256 |
1456 | 0 | osXAMZDate); |
1457 | |
|
1458 | 0 | bool bRet = false; |
1459 | 0 | const bool bUseHTTPS = CPLTestBool(CPLGetConfigOption("AWS_HTTPS", "YES")); |
1460 | |
|
1461 | 0 | CPLStringList aosOptions; |
1462 | 0 | std::string headers; |
1463 | 0 | if (!osSessionToken.empty()) |
1464 | 0 | headers += "X-Amz-Security-Token: " + osSessionToken + "\r\n"; |
1465 | 0 | headers += "X-Amz-Date: " + osXAMZDate + "\r\n"; |
1466 | 0 | headers += "Authorization: " + osAuthorization; |
1467 | 0 | aosOptions.AddNameValue("HEADERS", headers.c_str()); |
1468 | |
|
1469 | 0 | const std::string osURL = |
1470 | 0 | (bUseHTTPS ? "https://" : "http://") + osHost + "/" + osQueryString; |
1471 | 0 | CPLHTTPResult *psResult = CPLHTTPFetch(osURL.c_str(), aosOptions.List()); |
1472 | 0 | if (psResult) |
1473 | 0 | { |
1474 | 0 | if (psResult->nStatus == 0 && psResult->pabyData != nullptr) |
1475 | 0 | { |
1476 | 0 | CPLXMLTreeCloser oTree(CPLParseXMLString( |
1477 | 0 | reinterpret_cast<char *>(psResult->pabyData))); |
1478 | 0 | if (oTree) |
1479 | 0 | { |
1480 | 0 | const auto psCredentials = CPLGetXMLNode( |
1481 | 0 | oTree.get(), |
1482 | 0 | "=AssumeRoleResponse.AssumeRoleResult.Credentials"); |
1483 | 0 | if (psCredentials) |
1484 | 0 | { |
1485 | 0 | osTempAccessKeyId = |
1486 | 0 | CPLGetXMLValue(psCredentials, "AccessKeyId", ""); |
1487 | 0 | osTempSecretAccessKey = |
1488 | 0 | CPLGetXMLValue(psCredentials, "SecretAccessKey", ""); |
1489 | 0 | osTempSessionToken = |
1490 | 0 | CPLGetXMLValue(psCredentials, "SessionToken", ""); |
1491 | 0 | osExpiration = |
1492 | 0 | CPLGetXMLValue(psCredentials, "Expiration", ""); |
1493 | 0 | bRet = true; |
1494 | 0 | } |
1495 | 0 | else |
1496 | 0 | { |
1497 | 0 | CPLDebug(AWS_DEBUG_KEY, "%s", |
1498 | 0 | reinterpret_cast<char *>(psResult->pabyData)); |
1499 | 0 | } |
1500 | 0 | } |
1501 | 0 | } |
1502 | 0 | CPLHTTPDestroyResult(psResult); |
1503 | 0 | } |
1504 | 0 | return bRet; |
1505 | 0 | } |
1506 | | |
1507 | | /************************************************************************/ |
1508 | | /* GetTemporaryCredentialsForSSO() */ |
1509 | | /************************************************************************/ |
1510 | | |
1511 | | // Issue a GetRoleCredentials request |
1512 | | static bool GetTemporaryCredentialsForSSO(const std::string &osSSOStartURL, |
1513 | | const std::string &osSSOAccountID, |
1514 | | const std::string &osSSORoleName, |
1515 | | std::string &osTempSecretAccessKey, |
1516 | | std::string &osTempAccessKeyId, |
1517 | | std::string &osTempSessionToken, |
1518 | | std::string &osExpirationEpochInMS) |
1519 | 0 | { |
1520 | 0 | std::string osSSOFilename = GetAWSRootDirectory(); |
1521 | 0 | osSSOFilename += GetDirSeparator(); |
1522 | 0 | osSSOFilename += "sso"; |
1523 | 0 | osSSOFilename += GetDirSeparator(); |
1524 | 0 | osSSOFilename += "cache"; |
1525 | 0 | osSSOFilename += GetDirSeparator(); |
1526 | |
|
1527 | 0 | GByte hash[CPL_SHA1_HASH_SIZE]; |
1528 | 0 | CPL_SHA1(osSSOStartURL.data(), osSSOStartURL.size(), hash); |
1529 | 0 | osSSOFilename += CPLGetLowerCaseHex(hash, sizeof(hash)); |
1530 | 0 | osSSOFilename += ".json"; |
1531 | |
|
1532 | 0 | CPLJSONDocument oDoc; |
1533 | 0 | if (!oDoc.Load(osSSOFilename)) |
1534 | 0 | { |
1535 | 0 | CPLError(CE_Failure, CPLE_AppDefined, "Cannot find file %s", |
1536 | 0 | osSSOFilename.c_str()); |
1537 | 0 | return false; |
1538 | 0 | } |
1539 | | |
1540 | 0 | const auto oRoot = oDoc.GetRoot(); |
1541 | 0 | const auto osGotStartURL = oRoot.GetString("startUrl"); |
1542 | 0 | if (osGotStartURL != osSSOStartURL) |
1543 | 0 | { |
1544 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
1545 | 0 | "startUrl in %s = '%s', but expected '%s'.", |
1546 | 0 | osSSOFilename.c_str(), osGotStartURL.c_str(), |
1547 | 0 | osSSOStartURL.c_str()); |
1548 | 0 | return false; |
1549 | 0 | } |
1550 | 0 | const std::string osAccessToken = oRoot.GetString("accessToken"); |
1551 | 0 | if (osAccessToken.empty()) |
1552 | 0 | { |
1553 | 0 | CPLError(CE_Failure, CPLE_AppDefined, "Missing accessToken in %s", |
1554 | 0 | osSSOFilename.c_str()); |
1555 | 0 | return false; |
1556 | 0 | } |
1557 | | |
1558 | 0 | const std::string osExpiresAt = oRoot.GetString("expiresAt"); |
1559 | 0 | if (!osExpiresAt.empty()) |
1560 | 0 | { |
1561 | 0 | GIntBig nExpirationUnix = 0; |
1562 | 0 | if (Iso8601ToUnixTime(osExpiresAt.c_str(), &nExpirationUnix) && |
1563 | 0 | time(nullptr) > nExpirationUnix) |
1564 | 0 | { |
1565 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
1566 | 0 | "accessToken in %s is no longer valid since %s. You may " |
1567 | 0 | "need to sign again using aws cli", |
1568 | 0 | osSSOFilename.c_str(), osExpiresAt.c_str()); |
1569 | 0 | return false; |
1570 | 0 | } |
1571 | 0 | } |
1572 | | |
1573 | 0 | std::string osResourceAndQueryString = "/federation/credentials?role_name="; |
1574 | 0 | osResourceAndQueryString += osSSORoleName; |
1575 | 0 | osResourceAndQueryString += "&account_id="; |
1576 | 0 | osResourceAndQueryString += osSSOAccountID; |
1577 | |
|
1578 | 0 | CPLStringList aosOptions; |
1579 | 0 | std::string headers; |
1580 | 0 | headers += "x-amz-sso_bearer_token: " + osAccessToken; |
1581 | 0 | aosOptions.AddNameValue("HEADERS", headers.c_str()); |
1582 | |
|
1583 | 0 | const bool bUseHTTPS = CPLTestBool(CPLGetConfigOption("AWS_HTTPS", "YES")); |
1584 | 0 | const std::string osHost(CPLGetConfigOption( |
1585 | 0 | "CPL_AWS_SSO_ENDPOINT", "portal.sso.us-east-1.amazonaws.com")); |
1586 | |
|
1587 | 0 | const std::string osURL = (bUseHTTPS ? "https://" : "http://") + osHost + |
1588 | 0 | osResourceAndQueryString; |
1589 | 0 | CPLHTTPResult *psResult = CPLHTTPFetch(osURL.c_str(), aosOptions.List()); |
1590 | 0 | bool bRet = false; |
1591 | 0 | if (psResult) |
1592 | 0 | { |
1593 | 0 | if (psResult->nStatus == 0 && psResult->pabyData != nullptr && |
1594 | 0 | oDoc.LoadMemory(reinterpret_cast<char *>(psResult->pabyData))) |
1595 | 0 | { |
1596 | 0 | auto oRoleCredentials = oDoc.GetRoot().GetObj("roleCredentials"); |
1597 | 0 | osTempAccessKeyId = oRoleCredentials.GetString("accessKeyId"); |
1598 | 0 | osTempSecretAccessKey = |
1599 | 0 | oRoleCredentials.GetString("secretAccessKey"); |
1600 | 0 | osTempSessionToken = oRoleCredentials.GetString("sessionToken"); |
1601 | 0 | osExpirationEpochInMS = oRoleCredentials.GetString("expiration"); |
1602 | 0 | bRet = |
1603 | 0 | !osTempAccessKeyId.empty() && !osTempSecretAccessKey.empty() && |
1604 | 0 | !osTempSessionToken.empty() && !osExpirationEpochInMS.empty(); |
1605 | 0 | } |
1606 | 0 | CPLHTTPDestroyResult(psResult); |
1607 | 0 | } |
1608 | 0 | if (!bRet) |
1609 | 0 | { |
1610 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
1611 | 0 | "Did not manage to get temporary credentials for SSO " |
1612 | 0 | "authentication"); |
1613 | 0 | } |
1614 | 0 | return bRet; |
1615 | 0 | } |
1616 | | |
1617 | | /************************************************************************/ |
1618 | | /* GetOrRefreshTemporaryCredentialsForRole() */ |
1619 | | /************************************************************************/ |
1620 | | |
1621 | | bool VSIS3HandleHelper::GetOrRefreshTemporaryCredentialsForRole( |
1622 | | bool bForceRefresh, std::string &osSecretAccessKey, |
1623 | | std::string &osAccessKeyId, std::string &osSessionToken, |
1624 | | std::string &osRegion) |
1625 | 0 | { |
1626 | 0 | CPLMutexHolder oHolder(&ghMutex); |
1627 | 0 | if (!bForceRefresh) |
1628 | 0 | { |
1629 | 0 | time_t nCurTime; |
1630 | 0 | time(&nCurTime); |
1631 | | // Try to reuse credentials if they are still valid, but |
1632 | | // keep one minute of margin... |
1633 | 0 | if (!gosGlobalAccessKeyId.empty() && nCurTime < gnGlobalExpiration - 60) |
1634 | 0 | { |
1635 | 0 | osAccessKeyId = gosGlobalAccessKeyId; |
1636 | 0 | osSecretAccessKey = gosGlobalSecretAccessKey; |
1637 | 0 | osSessionToken = gosGlobalSessionToken; |
1638 | 0 | osRegion = gosRegion; |
1639 | 0 | return true; |
1640 | 0 | } |
1641 | 0 | } |
1642 | | |
1643 | 0 | if (!gosRoleArnWebIdentity.empty()) |
1644 | 0 | { |
1645 | 0 | if (GetConfigurationFromAssumeRoleWithWebIdentity( |
1646 | 0 | bForceRefresh, std::string(), gosRoleArnWebIdentity, |
1647 | 0 | gosWebIdentityTokenFile, osSecretAccessKey, osAccessKeyId, |
1648 | 0 | osSessionToken)) |
1649 | 0 | { |
1650 | 0 | gosSourceProfileSecretAccessKey = osSecretAccessKey; |
1651 | 0 | gosSourceProfileAccessKeyId = osAccessKeyId; |
1652 | 0 | gosSourceProfileSessionToken = osSessionToken; |
1653 | 0 | } |
1654 | 0 | else |
1655 | 0 | { |
1656 | 0 | return false; |
1657 | 0 | } |
1658 | 0 | } |
1659 | | |
1660 | 0 | if (!gosRoleArn.empty()) |
1661 | 0 | { |
1662 | 0 | std::string osExpiration; |
1663 | 0 | gosGlobalSecretAccessKey.clear(); |
1664 | 0 | gosGlobalAccessKeyId.clear(); |
1665 | 0 | gosGlobalSessionToken.clear(); |
1666 | 0 | if (GetTemporaryCredentialsForRole( |
1667 | 0 | gosRoleArn, gosExternalId, gosMFASerial, gosRoleSessionName, |
1668 | 0 | gosSourceProfileSecretAccessKey, gosSourceProfileAccessKeyId, |
1669 | 0 | gosSourceProfileSessionToken, gosGlobalSecretAccessKey, |
1670 | 0 | gosGlobalAccessKeyId, gosGlobalSessionToken, osExpiration)) |
1671 | 0 | { |
1672 | 0 | Iso8601ToUnixTime(osExpiration.c_str(), &gnGlobalExpiration); |
1673 | 0 | osAccessKeyId = gosGlobalAccessKeyId; |
1674 | 0 | osSecretAccessKey = gosGlobalSecretAccessKey; |
1675 | 0 | osSessionToken = gosGlobalSessionToken; |
1676 | 0 | osRegion = gosRegion; |
1677 | 0 | return true; |
1678 | 0 | } |
1679 | 0 | } |
1680 | | |
1681 | 0 | return false; |
1682 | 0 | } |
1683 | | |
1684 | | /************************************************************************/ |
1685 | | /* GetOrRefreshTemporaryCredentialsForSSO() */ |
1686 | | /************************************************************************/ |
1687 | | |
1688 | | bool VSIS3HandleHelper::GetOrRefreshTemporaryCredentialsForSSO( |
1689 | | bool bForceRefresh, std::string &osSecretAccessKey, |
1690 | | std::string &osAccessKeyId, std::string &osSessionToken, |
1691 | | std::string &osRegion) |
1692 | 0 | { |
1693 | 0 | CPLMutexHolder oHolder(&ghMutex); |
1694 | 0 | if (!bForceRefresh) |
1695 | 0 | { |
1696 | 0 | time_t nCurTime; |
1697 | 0 | time(&nCurTime); |
1698 | | // Try to reuse credentials if they are still valid, but |
1699 | | // keep one minute of margin... |
1700 | 0 | if (!gosGlobalAccessKeyId.empty() && nCurTime < gnGlobalExpiration - 60) |
1701 | 0 | { |
1702 | 0 | osAccessKeyId = gosGlobalAccessKeyId; |
1703 | 0 | osSecretAccessKey = gosGlobalSecretAccessKey; |
1704 | 0 | osSessionToken = gosGlobalSessionToken; |
1705 | 0 | osRegion = gosRegion; |
1706 | 0 | return true; |
1707 | 0 | } |
1708 | 0 | } |
1709 | | |
1710 | 0 | if (!gosSSOStartURL.empty()) |
1711 | 0 | { |
1712 | 0 | std::string osExpirationEpochInMS; |
1713 | 0 | gosGlobalSecretAccessKey.clear(); |
1714 | 0 | gosGlobalAccessKeyId.clear(); |
1715 | 0 | gosGlobalSessionToken.clear(); |
1716 | 0 | if (GetTemporaryCredentialsForSSO( |
1717 | 0 | gosSSOStartURL, gosSSOAccountID, gosSSORoleName, |
1718 | 0 | gosGlobalSecretAccessKey, gosGlobalAccessKeyId, |
1719 | 0 | gosGlobalSessionToken, osExpirationEpochInMS)) |
1720 | 0 | { |
1721 | 0 | gnGlobalExpiration = |
1722 | 0 | CPLAtoGIntBig(osExpirationEpochInMS.c_str()) / 1000; |
1723 | 0 | osAccessKeyId = gosGlobalAccessKeyId; |
1724 | 0 | osSecretAccessKey = gosGlobalSecretAccessKey; |
1725 | 0 | osSessionToken = gosGlobalSessionToken; |
1726 | 0 | osRegion = gosRegion; |
1727 | 0 | return true; |
1728 | 0 | } |
1729 | 0 | } |
1730 | | |
1731 | 0 | return false; |
1732 | 0 | } |
1733 | | |
1734 | | /************************************************************************/ |
1735 | | /* GetConfiguration() */ |
1736 | | /************************************************************************/ |
1737 | | |
1738 | | bool VSIS3HandleHelper::GetConfiguration( |
1739 | | const std::string &osPathForOption, CSLConstList papszOptions, |
1740 | | std::string &osSecretAccessKey, std::string &osAccessKeyId, |
1741 | | std::string &osSessionToken, std::string &osRegion, |
1742 | | AWSCredentialsSource &eCredentialsSource) |
1743 | 371 | { |
1744 | 371 | eCredentialsSource = AWSCredentialsSource::REGULAR; |
1745 | | |
1746 | | // AWS_REGION is GDAL specific. Later overloaded by standard |
1747 | | // AWS_DEFAULT_REGION |
1748 | 371 | osRegion = CSLFetchNameValueDef( |
1749 | 371 | papszOptions, "AWS_REGION", |
1750 | 371 | VSIGetPathSpecificOption(osPathForOption.c_str(), "AWS_REGION", |
1751 | 371 | "us-east-1")); |
1752 | | |
1753 | 371 | if (CPLTestBool(VSIGetPathSpecificOption(osPathForOption.c_str(), |
1754 | 371 | "AWS_NO_SIGN_REQUEST", "NO"))) |
1755 | 0 | { |
1756 | 0 | osSecretAccessKey.clear(); |
1757 | 0 | osAccessKeyId.clear(); |
1758 | 0 | osSessionToken.clear(); |
1759 | 0 | return true; |
1760 | 0 | } |
1761 | | |
1762 | 371 | osSecretAccessKey = CSLFetchNameValueDef( |
1763 | 371 | papszOptions, "AWS_SECRET_ACCESS_KEY", |
1764 | 371 | VSIGetPathSpecificOption(osPathForOption.c_str(), |
1765 | 371 | "AWS_SECRET_ACCESS_KEY", "")); |
1766 | 371 | if (!osSecretAccessKey.empty()) |
1767 | 0 | { |
1768 | 0 | osAccessKeyId = CSLFetchNameValueDef( |
1769 | 0 | papszOptions, "AWS_ACCESS_KEY_ID", |
1770 | 0 | VSIGetPathSpecificOption(osPathForOption.c_str(), |
1771 | 0 | "AWS_ACCESS_KEY_ID", "")); |
1772 | 0 | if (osAccessKeyId.empty()) |
1773 | 0 | { |
1774 | 0 | VSIError(VSIE_InvalidCredentials, |
1775 | 0 | "AWS_ACCESS_KEY_ID configuration option not defined"); |
1776 | 0 | return false; |
1777 | 0 | } |
1778 | | |
1779 | 0 | osSessionToken = CSLFetchNameValueDef( |
1780 | 0 | papszOptions, "AWS_SESSION_TOKEN", |
1781 | 0 | VSIGetPathSpecificOption(osPathForOption.c_str(), |
1782 | 0 | "AWS_SESSION_TOKEN", "")); |
1783 | 0 | return true; |
1784 | 0 | } |
1785 | | |
1786 | | // Next try to see if we have a current assumed role |
1787 | 371 | bool bAssumedRole = false; |
1788 | 371 | bool bSSO = false; |
1789 | 371 | { |
1790 | 371 | CPLMutexHolder oHolder(&ghMutex); |
1791 | 371 | bAssumedRole = !gosRoleArn.empty(); |
1792 | 371 | bSSO = !gosSSOStartURL.empty(); |
1793 | 371 | } |
1794 | 371 | if (bAssumedRole && GetOrRefreshTemporaryCredentialsForRole( |
1795 | 0 | /* bForceRefresh = */ false, osSecretAccessKey, |
1796 | 0 | osAccessKeyId, osSessionToken, osRegion)) |
1797 | 0 | { |
1798 | 0 | eCredentialsSource = AWSCredentialsSource::ASSUMED_ROLE; |
1799 | 0 | return true; |
1800 | 0 | } |
1801 | 371 | else if (bSSO && GetOrRefreshTemporaryCredentialsForSSO( |
1802 | 0 | /* bForceRefresh = */ false, osSecretAccessKey, |
1803 | 0 | osAccessKeyId, osSessionToken, osRegion)) |
1804 | 0 | { |
1805 | 0 | eCredentialsSource = AWSCredentialsSource::SSO; |
1806 | 0 | return true; |
1807 | 0 | } |
1808 | | |
1809 | | // Next try reading from ~/.aws/credentials and ~/.aws/config |
1810 | 371 | std::string osCredentials; |
1811 | 371 | std::string osRoleArn; |
1812 | 371 | std::string osSourceProfile; |
1813 | 371 | std::string osExternalId; |
1814 | 371 | std::string osMFASerial; |
1815 | 371 | std::string osRoleSessionName; |
1816 | 371 | std::string osWebIdentityTokenFile; |
1817 | 371 | std::string osSSOStartURL; |
1818 | 371 | std::string osSSOAccountID; |
1819 | 371 | std::string osSSORoleName; |
1820 | | // coverity[tainted_data] |
1821 | 371 | if (GetConfigurationFromAWSConfigFiles( |
1822 | 371 | osPathForOption, |
1823 | 371 | /* pszProfile = */ nullptr, osSecretAccessKey, osAccessKeyId, |
1824 | 371 | osSessionToken, osRegion, osCredentials, osRoleArn, osSourceProfile, |
1825 | 371 | osExternalId, osMFASerial, osRoleSessionName, |
1826 | 371 | osWebIdentityTokenFile, osSSOStartURL, osSSOAccountID, |
1827 | 371 | osSSORoleName)) |
1828 | 0 | { |
1829 | 0 | if (osSecretAccessKey.empty() && !osRoleArn.empty()) |
1830 | 0 | { |
1831 | | // Check if the default profile is pointing to another profile |
1832 | | // that has a role_arn and web_identity_token_file settings. |
1833 | 0 | if (!osSourceProfile.empty()) |
1834 | 0 | { |
1835 | 0 | std::string osSecretAccessKeySP; |
1836 | 0 | std::string osAccessKeyIdSP; |
1837 | 0 | std::string osSessionTokenSP; |
1838 | 0 | std::string osRegionSP; |
1839 | 0 | std::string osCredentialsSP; |
1840 | 0 | std::string osRoleArnSP; |
1841 | 0 | std::string osSourceProfileSP; |
1842 | 0 | std::string osExternalIdSP; |
1843 | 0 | std::string osMFASerialSP; |
1844 | 0 | std::string osRoleSessionNameSP; |
1845 | 0 | std::string osSSOStartURLSP; |
1846 | 0 | std::string osSSOAccountIDSP; |
1847 | 0 | std::string osSSORoleNameSP; |
1848 | 0 | if (GetConfigurationFromAWSConfigFiles( |
1849 | 0 | osPathForOption, osSourceProfile.c_str(), |
1850 | 0 | osSecretAccessKeySP, osAccessKeyIdSP, osSessionTokenSP, |
1851 | 0 | osRegionSP, osCredentialsSP, osRoleArnSP, |
1852 | 0 | osSourceProfileSP, osExternalIdSP, osMFASerialSP, |
1853 | 0 | osRoleSessionNameSP, osWebIdentityTokenFile, |
1854 | 0 | osSSOStartURLSP, osSSOAccountIDSP, osSSORoleNameSP)) |
1855 | 0 | { |
1856 | 0 | if (GetConfigurationFromAssumeRoleWithWebIdentity( |
1857 | 0 | /* bForceRefresh = */ false, osPathForOption, |
1858 | 0 | osRoleArnSP, osWebIdentityTokenFile, |
1859 | 0 | osSecretAccessKey, osAccessKeyId, osSessionToken)) |
1860 | 0 | { |
1861 | 0 | CPLMutexHolder oHolder(&ghMutex); |
1862 | 0 | gosRoleArnWebIdentity = std::move(osRoleArnSP); |
1863 | 0 | gosWebIdentityTokenFile = |
1864 | 0 | std::move(osWebIdentityTokenFile); |
1865 | 0 | } |
1866 | 0 | } |
1867 | 0 | } |
1868 | |
|
1869 | 0 | if (gosRoleArnWebIdentity.empty()) |
1870 | 0 | { |
1871 | | // Get the credentials for the source profile, that will be |
1872 | | // used to sign the STS AssumedRole request. |
1873 | 0 | if (!ReadAWSCredentials(osSourceProfile, osCredentials, |
1874 | 0 | osSecretAccessKey, osAccessKeyId, |
1875 | 0 | osSessionToken)) |
1876 | 0 | { |
1877 | 0 | VSIError( |
1878 | 0 | VSIE_InvalidCredentials, |
1879 | 0 | "Cannot retrieve credentials for source profile %s", |
1880 | 0 | osSourceProfile.c_str()); |
1881 | 0 | return false; |
1882 | 0 | } |
1883 | 0 | } |
1884 | | |
1885 | 0 | std::string osTempSecretAccessKey; |
1886 | 0 | std::string osTempAccessKeyId; |
1887 | 0 | std::string osTempSessionToken; |
1888 | 0 | std::string osExpiration; |
1889 | 0 | if (GetTemporaryCredentialsForRole( |
1890 | 0 | osRoleArn, osExternalId, osMFASerial, osRoleSessionName, |
1891 | 0 | osSecretAccessKey, osAccessKeyId, osSessionToken, |
1892 | 0 | osTempSecretAccessKey, osTempAccessKeyId, |
1893 | 0 | osTempSessionToken, osExpiration)) |
1894 | 0 | { |
1895 | 0 | CPLDebug(AWS_DEBUG_KEY, "Using assumed role %s", |
1896 | 0 | osRoleArn.c_str()); |
1897 | 0 | { |
1898 | | // Store global variables to be able to reuse the |
1899 | | // temporary credentials |
1900 | 0 | CPLMutexHolder oHolder(&ghMutex); |
1901 | 0 | Iso8601ToUnixTime(osExpiration.c_str(), |
1902 | 0 | &gnGlobalExpiration); |
1903 | 0 | gosRoleArn = std::move(osRoleArn); |
1904 | 0 | gosExternalId = std::move(osExternalId); |
1905 | 0 | gosMFASerial = std::move(osMFASerial); |
1906 | 0 | gosRoleSessionName = std::move(osRoleSessionName); |
1907 | 0 | gosSourceProfileSecretAccessKey = |
1908 | 0 | std::move(osSecretAccessKey); |
1909 | 0 | gosSourceProfileAccessKeyId = std::move(osAccessKeyId); |
1910 | 0 | gosSourceProfileSessionToken = std::move(osSessionToken); |
1911 | 0 | gosGlobalAccessKeyId = osTempAccessKeyId; |
1912 | 0 | gosGlobalSecretAccessKey = osTempSecretAccessKey; |
1913 | 0 | gosGlobalSessionToken = osTempSessionToken; |
1914 | 0 | gosRegion = osRegion; |
1915 | 0 | } |
1916 | 0 | osSecretAccessKey = std::move(osTempSecretAccessKey); |
1917 | 0 | osAccessKeyId = std::move(osTempAccessKeyId); |
1918 | 0 | osSessionToken = std::move(osTempSessionToken); |
1919 | 0 | eCredentialsSource = AWSCredentialsSource::ASSUMED_ROLE; |
1920 | 0 | return true; |
1921 | 0 | } |
1922 | 0 | return false; |
1923 | 0 | } |
1924 | | |
1925 | 0 | if (!osSSOStartURL.empty()) |
1926 | 0 | { |
1927 | 0 | std::string osTempSecretAccessKey; |
1928 | 0 | std::string osTempAccessKeyId; |
1929 | 0 | std::string osTempSessionToken; |
1930 | 0 | std::string osExpirationEpochInMS; |
1931 | 0 | if (GetTemporaryCredentialsForSSO( |
1932 | 0 | osSSOStartURL, osSSOAccountID, osSSORoleName, |
1933 | 0 | osTempSecretAccessKey, osTempAccessKeyId, |
1934 | 0 | osTempSessionToken, osExpirationEpochInMS)) |
1935 | 0 | { |
1936 | 0 | CPLDebug(AWS_DEBUG_KEY, "Using SSO %s", osSSOStartURL.c_str()); |
1937 | 0 | { |
1938 | | // Store global variables to be able to reuse the |
1939 | | // temporary credentials |
1940 | 0 | CPLMutexHolder oHolder(&ghMutex); |
1941 | 0 | gnGlobalExpiration = |
1942 | 0 | CPLAtoGIntBig(osExpirationEpochInMS.c_str()) / 1000; |
1943 | 0 | gosSSOStartURL = std::move(osSSOStartURL); |
1944 | 0 | gosSSOAccountID = std::move(osSSOAccountID); |
1945 | 0 | gosSSORoleName = std::move(osSSORoleName); |
1946 | 0 | gosGlobalAccessKeyId = osTempAccessKeyId; |
1947 | 0 | gosGlobalSecretAccessKey = osTempSecretAccessKey; |
1948 | 0 | gosGlobalSessionToken = osTempSessionToken; |
1949 | 0 | gosRegion = osRegion; |
1950 | 0 | } |
1951 | 0 | osSecretAccessKey = std::move(osTempSecretAccessKey); |
1952 | 0 | osAccessKeyId = std::move(osTempAccessKeyId); |
1953 | 0 | osSessionToken = std::move(osTempSessionToken); |
1954 | 0 | eCredentialsSource = AWSCredentialsSource::SSO; |
1955 | 0 | return true; |
1956 | 0 | } |
1957 | 0 | return false; |
1958 | 0 | } |
1959 | | |
1960 | 0 | return true; |
1961 | 0 | } |
1962 | | |
1963 | 371 | if (CPLTestBool(CPLGetConfigOption("CPL_AWS_WEB_IDENTITY_ENABLE", "YES"))) |
1964 | 371 | { |
1965 | | // WebIdentity method: use Web Identity Token |
1966 | 371 | if (GetConfigurationFromAssumeRoleWithWebIdentity( |
1967 | 371 | /* bForceRefresh = */ false, osPathForOption, |
1968 | 371 | /* osRoleArnIn = */ std::string(), |
1969 | 371 | /* osWebIdentityTokenFileIn = */ std::string(), |
1970 | 371 | osSecretAccessKey, osAccessKeyId, osSessionToken)) |
1971 | 0 | { |
1972 | 0 | eCredentialsSource = AWSCredentialsSource::WEB_IDENTITY; |
1973 | 0 | return true; |
1974 | 0 | } |
1975 | 371 | } |
1976 | | |
1977 | | // Last method: use IAM role security credentials on EC2 instances |
1978 | 371 | if (GetConfigurationFromEC2(/* bForceRefresh = */ false, osPathForOption, |
1979 | 371 | osSecretAccessKey, osAccessKeyId, |
1980 | 371 | osSessionToken)) |
1981 | 0 | { |
1982 | 0 | eCredentialsSource = AWSCredentialsSource::EC2; |
1983 | 0 | return true; |
1984 | 0 | } |
1985 | | |
1986 | 371 | CPLString osMsg; |
1987 | 371 | osMsg.Printf( |
1988 | 371 | "No valid AWS credentials found. " |
1989 | 371 | "For authenticated requests, you need to set " |
1990 | 371 | "AWS_SECRET_ACCESS_KEY, AWS_ACCESS_KEY_ID or other configuration " |
1991 | 371 | "options, or create a %s file. Consult " |
1992 | 371 | "https://gdal.org/en/stable/user/" |
1993 | 371 | "virtual_file_systems.html#vsis3-aws-s3-files for more details. " |
1994 | 371 | "For unauthenticated requests on public resources, set the " |
1995 | 371 | "AWS_NO_SIGN_REQUEST configuration option to YES.", |
1996 | 371 | osCredentials.c_str()); |
1997 | 371 | CPLDebug(AWS_DEBUG_KEY, "%s", osMsg.c_str()); |
1998 | 371 | VSIError(VSIE_InvalidCredentials, "%s", osMsg.c_str()); |
1999 | | |
2000 | 371 | return false; |
2001 | 371 | } |
2002 | | |
2003 | | /************************************************************************/ |
2004 | | /* CleanMutex() */ |
2005 | | /************************************************************************/ |
2006 | | |
2007 | | void VSIS3HandleHelper::CleanMutex() |
2008 | 0 | { |
2009 | 0 | if (ghMutex != nullptr) |
2010 | 0 | CPLDestroyMutex(ghMutex); |
2011 | 0 | ghMutex = nullptr; |
2012 | 0 | } |
2013 | | |
2014 | | /************************************************************************/ |
2015 | | /* ClearCache() */ |
2016 | | /************************************************************************/ |
2017 | | |
2018 | | void VSIS3HandleHelper::ClearCache() |
2019 | 0 | { |
2020 | 0 | CPLMutexHolder oHolder(&ghMutex); |
2021 | |
|
2022 | 0 | gosIAMRole.clear(); |
2023 | 0 | gosGlobalAccessKeyId.clear(); |
2024 | 0 | gosGlobalSecretAccessKey.clear(); |
2025 | 0 | gosGlobalSessionToken.clear(); |
2026 | 0 | gnGlobalExpiration = 0; |
2027 | 0 | gosRoleArn.clear(); |
2028 | 0 | gosExternalId.clear(); |
2029 | 0 | gosMFASerial.clear(); |
2030 | 0 | gosRoleSessionName.clear(); |
2031 | 0 | gosSourceProfileAccessKeyId.clear(); |
2032 | 0 | gosSourceProfileSecretAccessKey.clear(); |
2033 | 0 | gosSourceProfileSessionToken.clear(); |
2034 | 0 | gosRegion.clear(); |
2035 | 0 | gosRoleArnWebIdentity.clear(); |
2036 | 0 | gosWebIdentityTokenFile.clear(); |
2037 | 0 | gosSSOStartURL.clear(); |
2038 | 0 | gosSSOAccountID.clear(); |
2039 | 0 | gosSSORoleName.clear(); |
2040 | 0 | } |
2041 | | |
2042 | | /************************************************************************/ |
2043 | | /* BuildFromURI() */ |
2044 | | /************************************************************************/ |
2045 | | |
2046 | | VSIS3HandleHelper *VSIS3HandleHelper::BuildFromURI(const char *pszURI, |
2047 | | const char *pszFSPrefix, |
2048 | | bool bAllowNoObject, |
2049 | | CSLConstList papszOptions) |
2050 | 371 | { |
2051 | 371 | std::string osPathForOption("/vsis3/"); |
2052 | 371 | if (pszURI) |
2053 | 371 | osPathForOption += pszURI; |
2054 | | |
2055 | 371 | std::string osSecretAccessKey; |
2056 | 371 | std::string osAccessKeyId; |
2057 | 371 | std::string osSessionToken; |
2058 | 371 | std::string osRegion; |
2059 | 371 | AWSCredentialsSource eCredentialsSource = AWSCredentialsSource::REGULAR; |
2060 | 371 | if (!GetConfiguration(osPathForOption, papszOptions, osSecretAccessKey, |
2061 | 371 | osAccessKeyId, osSessionToken, osRegion, |
2062 | 371 | eCredentialsSource)) |
2063 | 371 | { |
2064 | 371 | return nullptr; |
2065 | 371 | } |
2066 | | |
2067 | | // According to |
2068 | | // http://docs.aws.amazon.com/cli/latest/userguide/cli-environment.html " |
2069 | | // This variable overrides the default region of the in-use profile, if |
2070 | | // set." |
2071 | 0 | std::string osDefaultRegion = CSLFetchNameValueDef( |
2072 | 0 | papszOptions, "AWS_DEFAULT_REGION", |
2073 | 0 | VSIGetPathSpecificOption(osPathForOption.c_str(), "AWS_DEFAULT_REGION", |
2074 | 0 | "")); |
2075 | 0 | if (!osDefaultRegion.empty()) |
2076 | 0 | { |
2077 | 0 | osRegion = std::move(osDefaultRegion); |
2078 | 0 | } |
2079 | |
|
2080 | 0 | std::string osEndpoint = VSIGetPathSpecificOption( |
2081 | 0 | osPathForOption.c_str(), "AWS_S3_ENDPOINT", "s3.amazonaws.com"); |
2082 | 0 | bool bForceHTTP = false; |
2083 | 0 | bool bForceHTTPS = false; |
2084 | 0 | if (STARTS_WITH(osEndpoint.c_str(), "http://")) |
2085 | 0 | { |
2086 | 0 | bForceHTTP = true; |
2087 | 0 | osEndpoint = osEndpoint.substr(strlen("http://")); |
2088 | 0 | } |
2089 | 0 | else if (STARTS_WITH(osEndpoint.c_str(), "https://")) |
2090 | 0 | { |
2091 | 0 | bForceHTTPS = true; |
2092 | 0 | osEndpoint = osEndpoint.substr(strlen("https://")); |
2093 | 0 | } |
2094 | 0 | if (!osEndpoint.empty() && osEndpoint.back() == '/') |
2095 | 0 | osEndpoint.pop_back(); |
2096 | |
|
2097 | 0 | if (!osRegion.empty() && osEndpoint == "s3.amazonaws.com") |
2098 | 0 | { |
2099 | 0 | osEndpoint = "s3." + osRegion + ".amazonaws.com"; |
2100 | 0 | } |
2101 | 0 | const std::string osRequestPayer = VSIGetPathSpecificOption( |
2102 | 0 | osPathForOption.c_str(), "AWS_REQUEST_PAYER", ""); |
2103 | 0 | std::string osBucket; |
2104 | 0 | std::string osObjectKey; |
2105 | 0 | if (pszURI != nullptr && pszURI[0] != '\0' && |
2106 | 0 | !GetBucketAndObjectKey(pszURI, pszFSPrefix, bAllowNoObject, osBucket, |
2107 | 0 | osObjectKey)) |
2108 | 0 | { |
2109 | 0 | return nullptr; |
2110 | 0 | } |
2111 | 0 | const bool bUseHTTPS = |
2112 | 0 | bForceHTTPS || |
2113 | 0 | (!bForceHTTP && CPLTestBool(VSIGetPathSpecificOption( |
2114 | 0 | osPathForOption.c_str(), "AWS_HTTPS", "YES"))); |
2115 | 0 | const bool bIsValidNameForVirtualHosting = |
2116 | 0 | osBucket.find('.') == std::string::npos; |
2117 | 0 | const bool bUseVirtualHosting = CPLTestBool(CSLFetchNameValueDef( |
2118 | 0 | papszOptions, "AWS_VIRTUAL_HOSTING", |
2119 | 0 | VSIGetPathSpecificOption(osPathForOption.c_str(), "AWS_VIRTUAL_HOSTING", |
2120 | 0 | bIsValidNameForVirtualHosting ? "TRUE" |
2121 | 0 | : "FALSE"))); |
2122 | 0 | return new VSIS3HandleHelper( |
2123 | 0 | osSecretAccessKey, osAccessKeyId, osSessionToken, osEndpoint, osRegion, |
2124 | 0 | osRequestPayer, osBucket, osObjectKey, bUseHTTPS, bUseVirtualHosting, |
2125 | 0 | eCredentialsSource); |
2126 | 0 | } |
2127 | | |
2128 | | /************************************************************************/ |
2129 | | /* GetQueryString() */ |
2130 | | /************************************************************************/ |
2131 | | |
2132 | | std::string |
2133 | | IVSIS3LikeHandleHelper::GetQueryString(bool bAddEmptyValueAfterEqual) const |
2134 | 903 | { |
2135 | 903 | std::string osQueryString; |
2136 | 903 | std::map<std::string, std::string>::const_iterator oIter = |
2137 | 903 | m_oMapQueryParameters.begin(); |
2138 | 1.85k | for (; oIter != m_oMapQueryParameters.end(); ++oIter) |
2139 | 955 | { |
2140 | 955 | if (oIter == m_oMapQueryParameters.begin()) |
2141 | 505 | osQueryString += "?"; |
2142 | 450 | else |
2143 | 450 | osQueryString += "&"; |
2144 | 955 | osQueryString += oIter->first; |
2145 | 955 | if (!oIter->second.empty() || bAddEmptyValueAfterEqual) |
2146 | 955 | { |
2147 | 955 | osQueryString += "="; |
2148 | 955 | osQueryString += CPLAWSURLEncode(oIter->second); |
2149 | 955 | } |
2150 | 955 | } |
2151 | 903 | return osQueryString; |
2152 | 903 | } |
2153 | | |
2154 | | /************************************************************************/ |
2155 | | /* ResetQueryParameters() */ |
2156 | | /************************************************************************/ |
2157 | | |
2158 | | void IVSIS3LikeHandleHelper::ResetQueryParameters() |
2159 | 398 | { |
2160 | 398 | m_oMapQueryParameters.clear(); |
2161 | 398 | RebuildURL(); |
2162 | 398 | } |
2163 | | |
2164 | | /************************************************************************/ |
2165 | | /* AddQueryParameter() */ |
2166 | | /************************************************************************/ |
2167 | | |
2168 | | void IVSIS3LikeHandleHelper::AddQueryParameter(const std::string &osKey, |
2169 | | const std::string &osValue) |
2170 | 505 | { |
2171 | 505 | m_oMapQueryParameters[osKey] = osValue; |
2172 | 505 | RebuildURL(); |
2173 | 505 | } |
2174 | | |
2175 | | /************************************************************************/ |
2176 | | /* GetURLNoKVP() */ |
2177 | | /************************************************************************/ |
2178 | | |
2179 | | std::string IVSIS3LikeHandleHelper::GetURLNoKVP() const |
2180 | 0 | { |
2181 | 0 | std::string osURL(GetURL()); |
2182 | 0 | const auto nPos = osURL.find('?'); |
2183 | 0 | if (nPos != std::string::npos) |
2184 | 0 | osURL.resize(nPos); |
2185 | 0 | return osURL; |
2186 | 0 | } |
2187 | | |
2188 | | /************************************************************************/ |
2189 | | /* RefreshCredentials() */ |
2190 | | /************************************************************************/ |
2191 | | |
2192 | | void VSIS3HandleHelper::RefreshCredentials(const std::string &osPathForOption, |
2193 | | bool bForceRefresh) const |
2194 | 0 | { |
2195 | 0 | if (m_eCredentialsSource == AWSCredentialsSource::EC2) |
2196 | 0 | { |
2197 | 0 | std::string osSecretAccessKey, osAccessKeyId, osSessionToken; |
2198 | 0 | if (GetConfigurationFromEC2(bForceRefresh, osPathForOption.c_str(), |
2199 | 0 | osSecretAccessKey, osAccessKeyId, |
2200 | 0 | osSessionToken)) |
2201 | 0 | { |
2202 | 0 | m_osSecretAccessKey = std::move(osSecretAccessKey); |
2203 | 0 | m_osAccessKeyId = std::move(osAccessKeyId); |
2204 | 0 | m_osSessionToken = std::move(osSessionToken); |
2205 | 0 | } |
2206 | 0 | } |
2207 | 0 | else if (m_eCredentialsSource == AWSCredentialsSource::ASSUMED_ROLE) |
2208 | 0 | { |
2209 | 0 | std::string osSecretAccessKey, osAccessKeyId, osSessionToken; |
2210 | 0 | std::string osRegion; |
2211 | 0 | if (GetOrRefreshTemporaryCredentialsForRole( |
2212 | 0 | bForceRefresh, osSecretAccessKey, osAccessKeyId, osSessionToken, |
2213 | 0 | osRegion)) |
2214 | 0 | { |
2215 | 0 | m_osSecretAccessKey = std::move(osSecretAccessKey); |
2216 | 0 | m_osAccessKeyId = std::move(osAccessKeyId); |
2217 | 0 | m_osSessionToken = std::move(osSessionToken); |
2218 | 0 | } |
2219 | 0 | } |
2220 | 0 | else if (m_eCredentialsSource == AWSCredentialsSource::WEB_IDENTITY) |
2221 | 0 | { |
2222 | 0 | std::string osSecretAccessKey, osAccessKeyId, osSessionToken; |
2223 | 0 | if (GetConfigurationFromAssumeRoleWithWebIdentity( |
2224 | 0 | bForceRefresh, osPathForOption.c_str(), std::string(), |
2225 | 0 | std::string(), osSecretAccessKey, osAccessKeyId, |
2226 | 0 | osSessionToken)) |
2227 | 0 | { |
2228 | 0 | m_osSecretAccessKey = std::move(osSecretAccessKey); |
2229 | 0 | m_osAccessKeyId = std::move(osAccessKeyId); |
2230 | 0 | m_osSessionToken = std::move(osSessionToken); |
2231 | 0 | } |
2232 | 0 | } |
2233 | 0 | else if (m_eCredentialsSource == AWSCredentialsSource::SSO) |
2234 | 0 | { |
2235 | 0 | std::string osSecretAccessKey, osAccessKeyId, osSessionToken; |
2236 | 0 | std::string osRegion; |
2237 | 0 | if (GetOrRefreshTemporaryCredentialsForSSO( |
2238 | 0 | bForceRefresh, osSecretAccessKey, osAccessKeyId, osSessionToken, |
2239 | 0 | osRegion)) |
2240 | 0 | { |
2241 | 0 | m_osSecretAccessKey = std::move(osSecretAccessKey); |
2242 | 0 | m_osAccessKeyId = std::move(osAccessKeyId); |
2243 | 0 | m_osSessionToken = std::move(osSessionToken); |
2244 | 0 | } |
2245 | 0 | } |
2246 | 0 | } |
2247 | | |
2248 | | /************************************************************************/ |
2249 | | /* GetCurlHeaders() */ |
2250 | | /************************************************************************/ |
2251 | | |
2252 | | struct curl_slist *VSIS3HandleHelper::GetCurlHeaders( |
2253 | | const std::string &osVerb, const struct curl_slist *psExistingHeaders, |
2254 | | const void *pabyDataContent, size_t nBytesContent) const |
2255 | 0 | { |
2256 | 0 | std::string osPathForOption("/vsis3/"); |
2257 | 0 | osPathForOption += m_osBucket; |
2258 | 0 | osPathForOption += '/'; |
2259 | 0 | osPathForOption += m_osObjectKey; |
2260 | |
|
2261 | 0 | RefreshCredentials(osPathForOption, /* bForceRefresh = */ false); |
2262 | |
|
2263 | 0 | std::string osXAMZDate = |
2264 | 0 | VSIGetPathSpecificOption(osPathForOption.c_str(), "AWS_TIMESTAMP", ""); |
2265 | 0 | if (osXAMZDate.empty()) |
2266 | 0 | osXAMZDate = CPLGetAWS_SIGN4_Timestamp(time(nullptr)); |
2267 | |
|
2268 | 0 | const std::string osXAMZContentSHA256 = |
2269 | 0 | CPLGetLowerCaseHexSHA256(pabyDataContent, nBytesContent); |
2270 | |
|
2271 | 0 | std::string osCanonicalQueryString(GetQueryString(true)); |
2272 | 0 | if (!osCanonicalQueryString.empty()) |
2273 | 0 | osCanonicalQueryString = osCanonicalQueryString.substr(1); |
2274 | |
|
2275 | 0 | const std::string osHost(m_bUseVirtualHosting && !m_osBucket.empty() |
2276 | 0 | ? std::string(m_osBucket + "." + m_osEndpoint) |
2277 | 0 | : m_osEndpoint); |
2278 | 0 | const std::string osAuthorization = |
2279 | 0 | m_osSecretAccessKey.empty() |
2280 | 0 | ? std::string() |
2281 | 0 | : CPLGetAWS_SIGN4_Authorization( |
2282 | 0 | m_osSecretAccessKey, m_osAccessKeyId, m_osSessionToken, |
2283 | 0 | m_osRegion, m_osRequestPayer, "s3", osVerb, psExistingHeaders, |
2284 | 0 | osHost, |
2285 | 0 | m_bUseVirtualHosting |
2286 | 0 | ? CPLAWSURLEncode("/" + m_osObjectKey, false).c_str() |
2287 | 0 | : CPLAWSURLEncode("/" + m_osBucket + "/" + m_osObjectKey, |
2288 | 0 | false) |
2289 | 0 | .c_str(), |
2290 | 0 | osCanonicalQueryString, osXAMZContentSHA256, |
2291 | 0 | true, // bAddHeaderAMZContentSHA256 |
2292 | 0 | osXAMZDate); |
2293 | |
|
2294 | 0 | struct curl_slist *headers = nullptr; |
2295 | 0 | headers = curl_slist_append( |
2296 | 0 | headers, CPLSPrintf("x-amz-date: %s", osXAMZDate.c_str())); |
2297 | 0 | headers = |
2298 | 0 | curl_slist_append(headers, CPLSPrintf("x-amz-content-sha256: %s", |
2299 | 0 | osXAMZContentSHA256.c_str())); |
2300 | 0 | if (!m_osSessionToken.empty()) |
2301 | 0 | headers = |
2302 | 0 | curl_slist_append(headers, CPLSPrintf("X-Amz-Security-Token: %s", |
2303 | 0 | m_osSessionToken.c_str())); |
2304 | 0 | if (!m_osRequestPayer.empty()) |
2305 | 0 | headers = |
2306 | 0 | curl_slist_append(headers, CPLSPrintf("x-amz-request-payer: %s", |
2307 | 0 | m_osRequestPayer.c_str())); |
2308 | 0 | if (!osAuthorization.empty()) |
2309 | 0 | { |
2310 | 0 | headers = curl_slist_append( |
2311 | 0 | headers, CPLSPrintf("Authorization: %s", osAuthorization.c_str())); |
2312 | 0 | } |
2313 | 0 | return headers; |
2314 | 0 | } |
2315 | | |
2316 | | /************************************************************************/ |
2317 | | /* CanRestartOnError() */ |
2318 | | /************************************************************************/ |
2319 | | |
2320 | | bool VSIS3HandleHelper::CanRestartOnError(const char *pszErrorMsg, |
2321 | | const char *pszHeaders, |
2322 | | bool bSetError) |
2323 | 0 | { |
2324 | | #ifdef DEBUG_VERBOSE |
2325 | | CPLDebug(AWS_DEBUG_KEY, "%s", pszErrorMsg); |
2326 | | CPLDebug(AWS_DEBUG_KEY, "%s", pszHeaders ? pszHeaders : ""); |
2327 | | #endif |
2328 | |
|
2329 | 0 | if (!STARTS_WITH(pszErrorMsg, "<?xml") && |
2330 | 0 | !STARTS_WITH(pszErrorMsg, "<Error>")) |
2331 | 0 | { |
2332 | 0 | if (bSetError) |
2333 | 0 | { |
2334 | 0 | VSIError(VSIE_ObjectStorageGenericError, "Invalid AWS response: %s", |
2335 | 0 | pszErrorMsg); |
2336 | 0 | } |
2337 | 0 | return false; |
2338 | 0 | } |
2339 | | |
2340 | 0 | CPLXMLNode *psTree = CPLParseXMLString(pszErrorMsg); |
2341 | 0 | if (psTree == nullptr) |
2342 | 0 | { |
2343 | 0 | if (bSetError) |
2344 | 0 | { |
2345 | 0 | VSIError(VSIE_ObjectStorageGenericError, |
2346 | 0 | "Malformed AWS XML response: %s", pszErrorMsg); |
2347 | 0 | } |
2348 | 0 | return false; |
2349 | 0 | } |
2350 | | |
2351 | 0 | const char *pszCode = CPLGetXMLValue(psTree, "=Error.Code", nullptr); |
2352 | 0 | if (pszCode == nullptr) |
2353 | 0 | { |
2354 | 0 | CPLDestroyXMLNode(psTree); |
2355 | 0 | if (bSetError) |
2356 | 0 | { |
2357 | 0 | VSIError(VSIE_ObjectStorageGenericError, |
2358 | 0 | "Malformed AWS XML response: %s", pszErrorMsg); |
2359 | 0 | } |
2360 | 0 | return false; |
2361 | 0 | } |
2362 | | |
2363 | 0 | if (EQUAL(pszCode, "AuthorizationHeaderMalformed")) |
2364 | 0 | { |
2365 | 0 | const char *pszRegion = |
2366 | 0 | CPLGetXMLValue(psTree, "=Error.Region", nullptr); |
2367 | 0 | if (pszRegion == nullptr) |
2368 | 0 | { |
2369 | 0 | CPLDestroyXMLNode(psTree); |
2370 | 0 | if (bSetError) |
2371 | 0 | { |
2372 | 0 | VSIError(VSIE_ObjectStorageGenericError, |
2373 | 0 | "Malformed AWS XML response: %s", pszErrorMsg); |
2374 | 0 | } |
2375 | 0 | return false; |
2376 | 0 | } |
2377 | 0 | SetRegion(pszRegion); |
2378 | 0 | CPLDebug(AWS_DEBUG_KEY, "Switching to region %s", m_osRegion.c_str()); |
2379 | 0 | CPLDestroyXMLNode(psTree); |
2380 | |
|
2381 | 0 | VSIS3UpdateParams::UpdateMapFromHandle(this); |
2382 | |
|
2383 | 0 | return true; |
2384 | 0 | } |
2385 | | |
2386 | 0 | if (EQUAL(pszCode, "PermanentRedirect") || |
2387 | 0 | EQUAL(pszCode, "TemporaryRedirect")) |
2388 | 0 | { |
2389 | 0 | const bool bIsTemporaryRedirect = EQUAL(pszCode, "TemporaryRedirect"); |
2390 | 0 | const char *pszEndpoint = |
2391 | 0 | CPLGetXMLValue(psTree, "=Error.Endpoint", nullptr); |
2392 | 0 | if (pszEndpoint == nullptr || |
2393 | 0 | (m_bUseVirtualHosting && (strncmp(pszEndpoint, m_osBucket.c_str(), |
2394 | 0 | m_osBucket.size()) != 0 || |
2395 | 0 | pszEndpoint[m_osBucket.size()] != '.'))) |
2396 | 0 | { |
2397 | 0 | CPLDestroyXMLNode(psTree); |
2398 | 0 | if (bSetError) |
2399 | 0 | { |
2400 | 0 | VSIError(VSIE_ObjectStorageGenericError, |
2401 | 0 | "Malformed AWS XML response: %s", pszErrorMsg); |
2402 | 0 | } |
2403 | 0 | return false; |
2404 | 0 | } |
2405 | 0 | if (!m_bUseVirtualHosting && |
2406 | 0 | strncmp(pszEndpoint, m_osBucket.c_str(), m_osBucket.size()) == 0 && |
2407 | 0 | pszEndpoint[m_osBucket.size()] == '.') |
2408 | 0 | { |
2409 | | /* If we have a body with |
2410 | | <Error><Code>PermanentRedirect</Code><Message>The bucket you are |
2411 | | attempting to access must be addressed using the specified endpoint. |
2412 | | Please send all future requests to this |
2413 | | endpoint.</Message><Bucket>bucket.with.dot</Bucket><Endpoint>bucket.with.dot.s3.amazonaws.com</Endpoint></Error> |
2414 | | and headers like |
2415 | | x-amz-bucket-region: eu-west-1 |
2416 | | and the bucket name has dot in it, |
2417 | | then we must use s3.$(x-amz-bucket-region).amazon.com as endpoint. |
2418 | | See #7154 */ |
2419 | 0 | const char *pszRegionPtr = |
2420 | 0 | (pszHeaders != nullptr) |
2421 | 0 | ? strstr(pszHeaders, "x-amz-bucket-region: ") |
2422 | 0 | : nullptr; |
2423 | 0 | if (strchr(m_osBucket.c_str(), '.') != nullptr && |
2424 | 0 | pszRegionPtr != nullptr) |
2425 | 0 | { |
2426 | 0 | std::string osRegion(pszRegionPtr + |
2427 | 0 | strlen("x-amz-bucket-region: ")); |
2428 | 0 | size_t nPos = osRegion.find('\r'); |
2429 | 0 | if (nPos != std::string::npos) |
2430 | 0 | osRegion.resize(nPos); |
2431 | 0 | SetEndpoint( |
2432 | 0 | CPLSPrintf("s3.%s.amazonaws.com", osRegion.c_str())); |
2433 | 0 | SetRegion(osRegion.c_str()); |
2434 | 0 | CPLDebug(AWS_DEBUG_KEY, "Switching to endpoint %s", |
2435 | 0 | m_osEndpoint.c_str()); |
2436 | 0 | CPLDebug(AWS_DEBUG_KEY, "Switching to region %s", |
2437 | 0 | m_osRegion.c_str()); |
2438 | 0 | CPLDestroyXMLNode(psTree); |
2439 | 0 | if (!bIsTemporaryRedirect) |
2440 | 0 | VSIS3UpdateParams::UpdateMapFromHandle(this); |
2441 | 0 | return true; |
2442 | 0 | } |
2443 | | |
2444 | 0 | m_bUseVirtualHosting = true; |
2445 | 0 | CPLDebug(AWS_DEBUG_KEY, "Switching to virtual hosting"); |
2446 | 0 | } |
2447 | 0 | SetEndpoint(m_bUseVirtualHosting ? pszEndpoint + m_osBucket.size() + 1 |
2448 | 0 | : pszEndpoint); |
2449 | 0 | CPLDebug(AWS_DEBUG_KEY, "Switching to endpoint %s", |
2450 | 0 | m_osEndpoint.c_str()); |
2451 | 0 | CPLDestroyXMLNode(psTree); |
2452 | |
|
2453 | 0 | if (!bIsTemporaryRedirect) |
2454 | 0 | VSIS3UpdateParams::UpdateMapFromHandle(this); |
2455 | |
|
2456 | 0 | return true; |
2457 | 0 | } |
2458 | | |
2459 | 0 | if (bSetError) |
2460 | 0 | { |
2461 | | // Translate AWS errors into VSI errors. |
2462 | 0 | const char *pszMessage = |
2463 | 0 | CPLGetXMLValue(psTree, "=Error.Message", nullptr); |
2464 | |
|
2465 | 0 | if (pszMessage == nullptr) |
2466 | 0 | { |
2467 | 0 | VSIError(VSIE_ObjectStorageGenericError, "%s", pszErrorMsg); |
2468 | 0 | } |
2469 | 0 | else if (EQUAL(pszCode, "AccessDenied")) |
2470 | 0 | { |
2471 | 0 | VSIError(VSIE_AccessDenied, "%s", pszMessage); |
2472 | 0 | } |
2473 | 0 | else if (EQUAL(pszCode, "NoSuchBucket")) |
2474 | 0 | { |
2475 | 0 | VSIError(VSIE_BucketNotFound, "%s", pszMessage); |
2476 | 0 | } |
2477 | 0 | else if (EQUAL(pszCode, "NoSuchKey")) |
2478 | 0 | { |
2479 | 0 | VSIError(VSIE_ObjectNotFound, "%s", pszMessage); |
2480 | 0 | } |
2481 | 0 | else if (EQUAL(pszCode, "SignatureDoesNotMatch")) |
2482 | 0 | { |
2483 | 0 | VSIError(VSIE_SignatureDoesNotMatch, "%s", pszMessage); |
2484 | 0 | } |
2485 | 0 | else |
2486 | 0 | { |
2487 | 0 | VSIError(VSIE_ObjectStorageGenericError, "%s", pszMessage); |
2488 | 0 | } |
2489 | 0 | } |
2490 | |
|
2491 | 0 | CPLDestroyXMLNode(psTree); |
2492 | |
|
2493 | 0 | return false; |
2494 | 0 | } |
2495 | | |
2496 | | /************************************************************************/ |
2497 | | /* SetEndpoint() */ |
2498 | | /************************************************************************/ |
2499 | | |
2500 | | void VSIS3HandleHelper::SetEndpoint(const std::string &osStr) |
2501 | 0 | { |
2502 | 0 | m_osEndpoint = osStr; |
2503 | 0 | RebuildURL(); |
2504 | 0 | } |
2505 | | |
2506 | | /************************************************************************/ |
2507 | | /* SetRegion() */ |
2508 | | /************************************************************************/ |
2509 | | |
2510 | | void VSIS3HandleHelper::SetRegion(const std::string &osStr) |
2511 | 0 | { |
2512 | 0 | m_osRegion = osStr; |
2513 | 0 | } |
2514 | | |
2515 | | /************************************************************************/ |
2516 | | /* SetRequestPayer() */ |
2517 | | /************************************************************************/ |
2518 | | |
2519 | | void VSIS3HandleHelper::SetRequestPayer(const std::string &osStr) |
2520 | 0 | { |
2521 | 0 | m_osRequestPayer = osStr; |
2522 | 0 | } |
2523 | | |
2524 | | /************************************************************************/ |
2525 | | /* SetVirtualHosting() */ |
2526 | | /************************************************************************/ |
2527 | | |
2528 | | void VSIS3HandleHelper::SetVirtualHosting(bool b) |
2529 | 0 | { |
2530 | 0 | m_bUseVirtualHosting = b; |
2531 | 0 | RebuildURL(); |
2532 | 0 | } |
2533 | | |
2534 | | /************************************************************************/ |
2535 | | /* GetSignedURL() */ |
2536 | | /************************************************************************/ |
2537 | | |
2538 | | std::string VSIS3HandleHelper::GetSignedURL(CSLConstList papszOptions) |
2539 | 0 | { |
2540 | 0 | std::string osPathForOption("/vsis3/"); |
2541 | 0 | osPathForOption += m_osBucket; |
2542 | 0 | osPathForOption += '/'; |
2543 | 0 | osPathForOption += m_osObjectKey; |
2544 | |
|
2545 | 0 | std::string osXAMZDate = CSLFetchNameValueDef( |
2546 | 0 | papszOptions, "START_DATE", |
2547 | 0 | VSIGetPathSpecificOption(osPathForOption.c_str(), "AWS_TIMESTAMP", "")); |
2548 | 0 | if (osXAMZDate.empty()) |
2549 | 0 | osXAMZDate = CPLGetAWS_SIGN4_Timestamp(time(nullptr)); |
2550 | 0 | std::string osDate(osXAMZDate); |
2551 | 0 | osDate.resize(8); |
2552 | |
|
2553 | 0 | std::string osXAMZExpires = |
2554 | 0 | CSLFetchNameValueDef(papszOptions, "EXPIRATION_DELAY", "3600"); |
2555 | |
|
2556 | 0 | if (m_eCredentialsSource != AWSCredentialsSource::REGULAR) |
2557 | 0 | { |
2558 | | // For credentials that have an expiration, we must check their |
2559 | | // expiration compared to the expiration of the signed URL, since |
2560 | | // if the effective expiration is min(desired_expiration, |
2561 | | // credential_expiration) Cf |
2562 | | // https://aws.amazon.com/premiumsupport/knowledge-center/presigned-url-s3-bucket-expiration |
2563 | 0 | int nYear, nMonth, nDay, nHour = 0, nMin = 0, nSec = 0; |
2564 | 0 | if (sscanf(osXAMZDate.c_str(), "%04d%02d%02dT%02d%02d%02dZ", &nYear, |
2565 | 0 | &nMonth, &nDay, &nHour, &nMin, &nSec) < 3) |
2566 | 0 | { |
2567 | 0 | CPLError(CE_Failure, CPLE_AppDefined, "Bad format for START_DATE"); |
2568 | 0 | return std::string(); |
2569 | 0 | } |
2570 | 0 | struct tm brokendowntime; |
2571 | 0 | brokendowntime.tm_year = nYear - 1900; |
2572 | 0 | brokendowntime.tm_mon = nMonth - 1; |
2573 | 0 | brokendowntime.tm_mday = nDay; |
2574 | 0 | brokendowntime.tm_hour = nHour; |
2575 | 0 | brokendowntime.tm_min = nMin; |
2576 | 0 | brokendowntime.tm_sec = nSec; |
2577 | 0 | const GIntBig nStartDate = CPLYMDHMSToUnixTime(&brokendowntime); |
2578 | |
|
2579 | 0 | { |
2580 | 0 | CPLMutexHolder oHolder(&ghMutex); |
2581 | | |
2582 | | // Try to reuse credentials if they will still be valid after the |
2583 | | // desired end of the validity of the signed URL, |
2584 | | // with one minute of margin |
2585 | 0 | if (nStartDate + CPLAtoGIntBig(osXAMZExpires.c_str()) >= |
2586 | 0 | gnGlobalExpiration - 60) |
2587 | 0 | { |
2588 | 0 | RefreshCredentials(osPathForOption, /* bForceRefresh = */ true); |
2589 | 0 | } |
2590 | 0 | } |
2591 | 0 | } |
2592 | | |
2593 | 0 | std::string osVerb(CSLFetchNameValueDef(papszOptions, "VERB", "GET")); |
2594 | |
|
2595 | 0 | ResetQueryParameters(); |
2596 | 0 | AddQueryParameter("X-Amz-Algorithm", "AWS4-HMAC-SHA256"); |
2597 | 0 | AddQueryParameter("X-Amz-Credential", m_osAccessKeyId + "/" + osDate + "/" + |
2598 | 0 | m_osRegion + "/s3/aws4_request"); |
2599 | 0 | AddQueryParameter("X-Amz-Date", osXAMZDate); |
2600 | 0 | AddQueryParameter("X-Amz-Expires", osXAMZExpires); |
2601 | 0 | if (!m_osSessionToken.empty()) |
2602 | 0 | AddQueryParameter("X-Amz-Security-Token", m_osSessionToken); |
2603 | 0 | AddQueryParameter("X-Amz-SignedHeaders", "host"); |
2604 | |
|
2605 | 0 | std::string osCanonicalQueryString(GetQueryString(true).substr(1)); |
2606 | |
|
2607 | 0 | const std::string osHost(m_bUseVirtualHosting && !m_osBucket.empty() |
2608 | 0 | ? std::string(m_osBucket + "." + m_osEndpoint) |
2609 | 0 | : m_osEndpoint); |
2610 | 0 | std::string osSignedHeaders; |
2611 | 0 | const std::string osSignature = CPLGetAWS_SIGN4_Signature( |
2612 | 0 | m_osSecretAccessKey, |
2613 | 0 | std::string(), // sessionToken set to empty as we include it in query |
2614 | | // parameters |
2615 | 0 | m_osRegion, m_osRequestPayer, "s3", osVerb, |
2616 | 0 | nullptr, /* existing headers */ |
2617 | 0 | osHost, |
2618 | 0 | m_bUseVirtualHosting |
2619 | 0 | ? CPLAWSURLEncode("/" + m_osObjectKey, false).c_str() |
2620 | 0 | : CPLAWSURLEncode("/" + m_osBucket + "/" + m_osObjectKey, false) |
2621 | 0 | .c_str(), |
2622 | 0 | osCanonicalQueryString, "UNSIGNED-PAYLOAD", |
2623 | 0 | false, // bAddHeaderAMZContentSHA256 |
2624 | 0 | osXAMZDate, osSignedHeaders); |
2625 | |
|
2626 | 0 | AddQueryParameter("X-Amz-Signature", osSignature); |
2627 | 0 | return m_osURL; |
2628 | 0 | } |
2629 | | |
2630 | | /************************************************************************/ |
2631 | | /* UpdateMapFromHandle() */ |
2632 | | /************************************************************************/ |
2633 | | |
2634 | | std::mutex VSIS3UpdateParams::gsMutex{}; |
2635 | | |
2636 | | std::map<std::string, VSIS3UpdateParams> |
2637 | | VSIS3UpdateParams::goMapBucketsToS3Params{}; |
2638 | | |
2639 | | void VSIS3UpdateParams::UpdateMapFromHandle(VSIS3HandleHelper *poS3HandleHelper) |
2640 | 0 | { |
2641 | 0 | std::lock_guard<std::mutex> guard(gsMutex); |
2642 | |
|
2643 | 0 | goMapBucketsToS3Params[poS3HandleHelper->GetBucket()] = |
2644 | 0 | VSIS3UpdateParams(poS3HandleHelper); |
2645 | 0 | } |
2646 | | |
2647 | | /************************************************************************/ |
2648 | | /* UpdateHandleFromMap() */ |
2649 | | /************************************************************************/ |
2650 | | |
2651 | | void VSIS3UpdateParams::UpdateHandleFromMap(VSIS3HandleHelper *poS3HandleHelper) |
2652 | 0 | { |
2653 | 0 | std::lock_guard<std::mutex> guard(gsMutex); |
2654 | |
|
2655 | 0 | std::map<std::string, VSIS3UpdateParams>::iterator oIter = |
2656 | 0 | goMapBucketsToS3Params.find(poS3HandleHelper->GetBucket()); |
2657 | 0 | if (oIter != goMapBucketsToS3Params.end()) |
2658 | 0 | { |
2659 | 0 | oIter->second.UpdateHandlerHelper(poS3HandleHelper); |
2660 | 0 | } |
2661 | 0 | } |
2662 | | |
2663 | | /************************************************************************/ |
2664 | | /* ClearCache() */ |
2665 | | /************************************************************************/ |
2666 | | |
2667 | | void VSIS3UpdateParams::ClearCache() |
2668 | 0 | { |
2669 | 0 | std::lock_guard<std::mutex> guard(gsMutex); |
2670 | |
|
2671 | 0 | goMapBucketsToS3Params.clear(); |
2672 | 0 | } |
2673 | | |
2674 | | #endif |
2675 | | |
2676 | | //! @endcond |