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