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