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