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