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