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