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 1075 : static std::string CPLGetLowerCaseHex(const GByte *pabyData, size_t nBytes)
79 :
80 : {
81 1075 : std::string osRet;
82 1075 : osRet.resize(nBytes * 2);
83 :
84 1075 : constexpr char achHex[] = "0123456789abcdef";
85 :
86 35463 : for (size_t i = 0; i < nBytes; ++i)
87 : {
88 34388 : const int nLow = pabyData[i] & 0x0f;
89 34388 : const int nHigh = (pabyData[i] & 0xf0) >> 4;
90 :
91 34388 : osRet[i * 2] = achHex[nHigh];
92 34388 : osRet[i * 2 + 1] = achHex[nLow];
93 : }
94 :
95 2150 : return osRet;
96 : }
97 :
98 : /************************************************************************/
99 : /* CPLGetLowerCaseHexSHA256() */
100 : /************************************************************************/
101 :
102 723 : std::string CPLGetLowerCaseHexSHA256(const void *pabyData, size_t nBytes)
103 : {
104 723 : GByte hash[CPL_SHA256_HASH_SIZE] = {};
105 723 : CPL_SHA256(static_cast<const GByte *>(pabyData), nBytes, hash);
106 1446 : return CPLGetLowerCaseHex(hash, CPL_SHA256_HASH_SIZE);
107 : }
108 :
109 : /************************************************************************/
110 : /* CPLGetLowerCaseHexSHA256() */
111 : /************************************************************************/
112 :
113 356 : std::string CPLGetLowerCaseHexSHA256(const std::string &osStr)
114 : {
115 356 : return CPLGetLowerCaseHexSHA256(osStr.c_str(), osStr.size());
116 : }
117 :
118 : /************************************************************************/
119 : /* CPLAWSURLEncode() */
120 : /************************************************************************/
121 :
122 6867 : std::string CPLAWSURLEncode(const std::string &osURL, bool bEncodeSlash)
123 : {
124 6867 : std::string osRet;
125 12104600 : for (size_t i = 0; i < osURL.size(); i++)
126 : {
127 12097700 : char ch = osURL[i];
128 12097700 : if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') ||
129 42786 : (ch >= '0' && ch <= '9') || ch == '_' || ch == '-' || ch == '~' ||
130 : ch == '.')
131 : {
132 12093700 : osRet += ch;
133 : }
134 4056 : else if (ch == '/')
135 : {
136 3700 : if (bEncodeSlash)
137 670 : osRet += "%2F";
138 : else
139 3030 : osRet += ch;
140 : }
141 : else
142 : {
143 356 : osRet += CPLSPrintf("%%%02X", static_cast<unsigned char>(ch));
144 : }
145 : }
146 6867 : 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 351 : 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 702 : std::string osCanonicalRequest = osVerb + "\n";
186 :
187 351 : osCanonicalRequest += osCanonicalURI + "\n";
188 :
189 351 : osCanonicalRequest += osCanonicalQueryString + "\n";
190 :
191 702 : std::map<std::string, std::string> oSortedMapHeaders;
192 351 : oSortedMapHeaders["host"] = osHost;
193 351 : if (osXAMZContentSHA256 != "UNSIGNED-PAYLOAD" && bAddHeaderAMZContentSHA256)
194 : {
195 341 : oSortedMapHeaders["x-amz-content-sha256"] = osXAMZContentSHA256;
196 341 : oSortedMapHeaders["x-amz-date"] = osTimestamp;
197 : }
198 : std::string osCanonicalizedHeaders(
199 : IVSIS3LikeHandleHelper::BuildCanonicalizedHeaders(oSortedMapHeaders,
200 702 : psHeaders, "x-amz-"));
201 :
202 351 : osCanonicalRequest += osCanonicalizedHeaders + "\n";
203 :
204 351 : osSignedHeaders.clear();
205 : std::map<std::string, std::string>::const_iterator oIter =
206 351 : oSortedMapHeaders.begin();
207 1421 : for (; oIter != oSortedMapHeaders.end(); ++oIter)
208 : {
209 1070 : if (!osSignedHeaders.empty())
210 719 : osSignedHeaders += ";";
211 1070 : osSignedHeaders += oIter->first;
212 : }
213 :
214 351 : osCanonicalRequest += osSignedHeaders + "\n";
215 :
216 351 : 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 702 : std::string osStringToSign = "AWS4-HMAC-SHA256\n";
227 351 : osStringToSign += osTimestamp + "\n";
228 :
229 702 : std::string osYYMMDD(osTimestamp);
230 351 : osYYMMDD.resize(8);
231 :
232 702 : std::string osScope = osYYMMDD + "/";
233 351 : osScope += osRegion;
234 351 : osScope += "/";
235 351 : osScope += osService;
236 351 : osScope += "/aws4_request";
237 351 : osStringToSign += osScope + "\n";
238 351 : 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 351 : GByte abySigningKeyIn[CPL_SHA256_HASH_SIZE] = {};
248 351 : GByte abySigningKeyOut[CPL_SHA256_HASH_SIZE] = {};
249 :
250 1053 : std::string osFirstKey(std::string("AWS4") + osSecretAccessKey);
251 351 : CPL_HMAC_SHA256(osFirstKey.c_str(), osFirstKey.size(), osYYMMDD.c_str(),
252 : osYYMMDD.size(), abySigningKeyOut);
253 351 : memcpy(abySigningKeyIn, abySigningKeyOut, CPL_SHA256_HASH_SIZE);
254 :
255 351 : CPL_HMAC_SHA256(abySigningKeyIn, CPL_SHA256_HASH_SIZE, osRegion.c_str(),
256 : osRegion.size(), abySigningKeyOut);
257 351 : memcpy(abySigningKeyIn, abySigningKeyOut, CPL_SHA256_HASH_SIZE);
258 :
259 351 : CPL_HMAC_SHA256(abySigningKeyIn, CPL_SHA256_HASH_SIZE, osService.c_str(),
260 : osService.size(), abySigningKeyOut);
261 351 : memcpy(abySigningKeyIn, abySigningKeyOut, CPL_SHA256_HASH_SIZE);
262 :
263 351 : CPL_HMAC_SHA256(abySigningKeyIn, CPL_SHA256_HASH_SIZE, "aws4_request",
264 : strlen("aws4_request"), abySigningKeyOut);
265 351 : 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 351 : GByte abySignature[CPL_SHA256_HASH_SIZE] = {};
277 702 : CPL_HMAC_SHA256(abySigningKeyIn, CPL_SHA256_HASH_SIZE,
278 351 : osStringToSign.c_str(), osStringToSign.size(),
279 : abySignature);
280 : std::string osSignature(
281 351 : 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 351 : psHeaders = curl_slist_append(
288 : psHeaders, CPLSPrintf("x-amz-date: %s", osTimestamp.c_str()));
289 351 : psHeaders =
290 351 : curl_slist_append(psHeaders, CPLSPrintf("x-amz-content-sha256: %s",
291 : osXAMZContentSHA256.c_str()));
292 702 : return osSignature;
293 : }
294 :
295 : /************************************************************************/
296 : /* CPLGetAWS_SIGN4_Authorization() */
297 : /************************************************************************/
298 :
299 346 : 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 692 : std::string osSignedHeaders;
309 : std::string osSignature(CPLGetAWS_SIGN4_Signature(
310 : osSecretAccessKey, osRegion, osService, osVerb, psHeaders, osHost,
311 : osCanonicalURI, osCanonicalQueryString, osXAMZContentSHA256,
312 692 : bAddHeaderAMZContentSHA256, osTimestamp, osSignedHeaders));
313 :
314 692 : std::string osYYMMDD(osTimestamp);
315 346 : osYYMMDD.resize(8);
316 :
317 : /* -------------------------------------------------------------------- */
318 : /* Build authorization header. */
319 : /* -------------------------------------------------------------------- */
320 346 : std::string osAuthorization;
321 346 : osAuthorization = "AWS4-HMAC-SHA256 Credential=";
322 346 : osAuthorization += osAccessKeyId;
323 346 : osAuthorization += "/";
324 346 : osAuthorization += osYYMMDD;
325 346 : osAuthorization += "/";
326 346 : osAuthorization += osRegion;
327 346 : osAuthorization += "/";
328 346 : osAuthorization += osService;
329 346 : osAuthorization += "/";
330 346 : osAuthorization += "aws4_request";
331 346 : osAuthorization += ",";
332 346 : osAuthorization += "SignedHeaders=";
333 346 : osAuthorization += osSignedHeaders;
334 346 : osAuthorization += ",";
335 346 : osAuthorization += "Signature=";
336 346 : osAuthorization += osSignature;
337 :
338 : #ifdef DEBUG_VERBOSE
339 : CPLDebug(AWS_DEBUG_KEY, "osAuthorization='%s'", osAuthorization.c_str());
340 : #endif
341 :
342 692 : return osAuthorization;
343 : }
344 :
345 : /************************************************************************/
346 : /* CPLGetAWS_SIGN4_Timestamp() */
347 : /************************************************************************/
348 :
349 16 : std::string CPLGetAWS_SIGN4_Timestamp(GIntBig timestamp)
350 : {
351 : struct tm brokenDown;
352 16 : CPLUnixTimeToYMDHMS(timestamp, &brokenDown);
353 :
354 16 : char szTimeStamp[80] = {};
355 16 : snprintf(szTimeStamp, sizeof(szTimeStamp), "%04d%02d%02dT%02d%02d%02dZ",
356 16 : brokenDown.tm_year + 1900, brokenDown.tm_mon + 1,
357 : brokenDown.tm_mday, brokenDown.tm_hour, brokenDown.tm_min,
358 : brokenDown.tm_sec);
359 16 : return szTimeStamp;
360 : }
361 :
362 : /************************************************************************/
363 : /* VSIS3HandleHelper() */
364 : /************************************************************************/
365 602 : 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 602 : 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 602 : m_eCredentialsSource(eCredentialsSource)
383 : {
384 602 : VSIS3UpdateParams::UpdateHandleFromMap(this);
385 602 : }
386 :
387 : /************************************************************************/
388 : /* ~VSIS3HandleHelper() */
389 : /************************************************************************/
390 :
391 1204 : VSIS3HandleHelper::~VSIS3HandleHelper()
392 : {
393 12393 : for (size_t i = 0; i < m_osSecretAccessKey.size(); i++)
394 11791 : m_osSecretAccessKey[i] = 0;
395 1204 : }
396 :
397 : /************************************************************************/
398 : /* BuildURL() */
399 : /************************************************************************/
400 :
401 1302 : 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 1302 : const char *pszProtocol = (bUseHTTPS) ? "https" : "http";
407 1302 : if (osBucket.empty())
408 20 : return CPLSPrintf("%s://%s", pszProtocol, osEndpoint.c_str());
409 1282 : else if (bUseVirtualHosting)
410 : return CPLSPrintf("%s://%s.%s/%s", pszProtocol, osBucket.c_str(),
411 : osEndpoint.c_str(),
412 80 : CPLAWSURLEncode(osObjectKey, false).c_str());
413 : else
414 : return CPLSPrintf("%s://%s/%s/%s", pszProtocol, osEndpoint.c_str(),
415 : osBucket.c_str(),
416 2484 : CPLAWSURLEncode(osObjectKey, false).c_str());
417 : }
418 :
419 : /************************************************************************/
420 : /* RebuildURL() */
421 : /************************************************************************/
422 :
423 700 : void VSIS3HandleHelper::RebuildURL()
424 : {
425 700 : m_osURL = BuildURL(m_osEndpoint, m_osBucket, m_osObjectKey, m_bUseHTTPS,
426 700 : m_bUseVirtualHosting);
427 700 : m_osURL += GetQueryString(false);
428 700 : }
429 :
430 : IVSIS3LikeHandleHelper::IVSIS3LikeHandleHelper() = default;
431 :
432 : IVSIS3LikeHandleHelper::~IVSIS3LikeHandleHelper() = default;
433 :
434 : /************************************************************************/
435 : /* GetBucketAndObjectKey() */
436 : /************************************************************************/
437 :
438 679 : bool IVSIS3LikeHandleHelper::GetBucketAndObjectKey(const char *pszURI,
439 : const char *pszFSPrefix,
440 : bool bAllowNoObject,
441 : std::string &osBucket,
442 : std::string &osObjectKey)
443 : {
444 679 : osBucket = pszURI;
445 679 : if (osBucket.empty())
446 : {
447 0 : return false;
448 : }
449 679 : size_t nPos = osBucket.find('/');
450 679 : if (nPos == std::string::npos)
451 : {
452 122 : if (bAllowNoObject)
453 : {
454 120 : osObjectKey = "";
455 120 : 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 557 : osBucket.resize(nPos);
462 557 : osObjectKey = pszURI + nPos + 1;
463 557 : return true;
464 : }
465 :
466 : /************************************************************************/
467 : /* BuildCanonicalizedHeaders() */
468 : /************************************************************************/
469 :
470 669 : std::string IVSIS3LikeHandleHelper::BuildCanonicalizedHeaders(
471 : std::map<std::string, std::string> &oSortedMapHeaders,
472 : const struct curl_slist *psExistingHeaders, const char *pszHeaderPrefix)
473 : {
474 669 : const struct curl_slist *psIter = psExistingHeaders;
475 1073 : for (; psIter != nullptr; psIter = psIter->next)
476 : {
477 404 : if (STARTS_WITH_CI(psIter->data, pszHeaderPrefix) ||
478 339 : 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 669 : std::string osCanonicalizedHeaders;
492 : std::map<std::string, std::string>::const_iterator oIter =
493 669 : oSortedMapHeaders.begin();
494 2194 : for (; oIter != oSortedMapHeaders.end(); ++oIter)
495 : {
496 1525 : osCanonicalizedHeaders += oIter->first + ":" + oIter->second + "\n";
497 : }
498 1338 : return osCanonicalizedHeaders;
499 : }
500 :
501 : /************************************************************************/
502 : /* GetRFC822DateTime() */
503 : /************************************************************************/
504 :
505 32 : std::string IVSIS3LikeHandleHelper::GetRFC822DateTime()
506 : {
507 : char szDate[64];
508 32 : time_t nNow = time(nullptr);
509 : struct tm tm;
510 32 : CPLUnixTimeToYMDHMS(nNow, &tm);
511 32 : int nRet = CPLPrintTime(szDate, sizeof(szDate) - 1,
512 : "%a, %d %b %Y %H:%M:%S GMT", &tm, "C");
513 32 : szDate[nRet] = 0;
514 32 : 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 29 : static EC2InstanceCertainty IsMachinePotentiallyEC2Instance()
557 : {
558 : #if defined(__linux) || defined(_WIN32)
559 23 : 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 23 : VSILFILE *fp = VSIFOpenL("/sys/hypervisor/uuid", "rb");
573 23 : 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 23 : fp = VSIFOpenL("/sys/devices/virtual/dmi/id/sys_vendor", "rb");
586 23 : if (fp != nullptr)
587 : {
588 23 : char buf[10 + 1] = {0};
589 23 : VSIFReadL(buf, 1, sizeof(buf) - 1, fp);
590 23 : VSIFCloseL(fp);
591 23 : return EQUALN(buf, "Amazon EC2", 10) ? EC2InstanceCertainty::YES
592 23 : : 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 29 : if (!CPLTestBool(CPLGetConfigOption("CPL_AWS_AUTODETECT_EC2", "YES")))
609 : {
610 6 : return EC2InstanceCertainty::MAYBE;
611 : }
612 : else
613 : {
614 : const char *opt =
615 23 : CPLGetConfigOption("CPL_AWS_CHECK_HYPERVISOR_UUID", "");
616 23 : 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 23 : 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 36 : 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 72 : CPLMutexHolder oHolder(&ghMutex);
703 36 : if (!bForceRefresh &&
704 36 : 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 35 : !osRoleArnIn.empty() ? osRoleArnIn
721 : : VSIGetPathSpecificOption(osPathForOption.c_str(),
722 70 : "AWS_ROLE_ARN", "");
723 35 : if (roleArn.empty())
724 : {
725 31 : CPLDebug(AWS_DEBUG_KEY,
726 : "AWS_ROLE_ARN configuration option not defined");
727 31 : 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 47 : bool VSIS3HandleHelper::GetConfigurationFromEC2(
835 : bool bForceRefresh, const std::string &osPathForOption,
836 : std::string &osSecretAccessKey, std::string &osAccessKeyId,
837 : std::string &osSessionToken)
838 : {
839 94 : CPLMutexHolder oHolder(&ghMutex);
840 47 : 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 64 : std::string osURLRefreshCredentials;
856 64 : 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 64 : osEC2DefaultURL.c_str()));
861 : // coverity[tainted_data]
862 : std::string osECSFullURI(VSIGetPathSpecificOption(
863 64 : osPathForOption.c_str(), "AWS_CONTAINER_CREDENTIALS_FULL_URI", ""));
864 : // coverity[tainted_data]
865 : const std::string osECSRelativeURI(
866 32 : osECSFullURI.empty() ? VSIGetPathSpecificOption(
867 : osPathForOption.c_str(),
868 : "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI", "")
869 64 : : std::string());
870 : // coverity[tainted_data]
871 : const std::string osECSTokenFile(
872 29 : (osECSFullURI.empty() && osECSRelativeURI.empty())
873 32 : ? std::string()
874 : : VSIGetPathSpecificOption(osPathForOption.c_str(),
875 : "AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE",
876 96 : ""));
877 :
878 : // coverity[tainted_data]
879 : std::string osECSTokenValue(
880 29 : (osECSFullURI.empty() && osECSRelativeURI.empty() &&
881 29 : !osECSTokenFile.empty())
882 32 : ? std::string()
883 : : VSIGetPathSpecificOption(osPathForOption.c_str(),
884 : "AWS_CONTAINER_AUTHORIZATION_TOKEN",
885 96 : ""));
886 :
887 64 : std::string osECSToken;
888 32 : 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 31 : else if (!osECSTokenValue.empty())
896 : {
897 1 : osECSToken = std::move(osECSTokenValue);
898 : }
899 :
900 64 : std::string osToken;
901 32 : 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 29 : 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 29 : const auto eIsEC2 = IsMachinePotentiallyEC2Instance();
915 29 : if (eIsEC2 == EC2InstanceCertainty::NO)
916 23 : 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 57 : 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 57 : osSecretAccessKey.clear();
1114 57 : osAccessKeyId.clear();
1115 57 : osSessionToken.clear();
1116 :
1117 57 : VSILFILE *fp = VSIFOpenL(osCredentials.c_str(), "rb");
1118 57 : 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 57 : return !osSecretAccessKey.empty() && !osAccessKeyId.empty();
1152 : }
1153 :
1154 : /************************************************************************/
1155 : /* GetDirSeparator() */
1156 : /************************************************************************/
1157 :
1158 122 : 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 122 : return SEP_STRING;
1166 : }
1167 :
1168 : /************************************************************************/
1169 : /* GetAWSRootDirectory() */
1170 : /************************************************************************/
1171 :
1172 57 : static std::string GetAWSRootDirectory()
1173 : {
1174 57 : const char *pszAWSRootDir = CPLGetConfigOption("CPL_AWS_ROOT_DIR", nullptr);
1175 57 : if (pszAWSRootDir)
1176 2 : return pszAWSRootDir;
1177 : #ifdef _WIN32
1178 : const char *pszHome = CPLGetConfigOption("USERPROFILE", nullptr);
1179 : #else
1180 55 : const char *pszHome = CPLGetConfigOption("HOME", nullptr);
1181 : #endif
1182 :
1183 110 : return std::string(pszHome ? pszHome : "")
1184 55 : .append(GetDirSeparator())
1185 55 : .append(".aws");
1186 : }
1187 :
1188 : /************************************************************************/
1189 : /* GetConfigurationFromAWSConfigFiles() */
1190 : /************************************************************************/
1191 :
1192 56 : 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 56 : const char *pszProfileOri = pszProfile;
1207 56 : if (pszProfile == nullptr)
1208 : {
1209 54 : pszProfile = VSIGetPathSpecificOption(osPathForOption.c_str(),
1210 : "AWS_DEFAULT_PROFILE", "");
1211 54 : if (pszProfile[0] == '\0')
1212 54 : pszProfile = VSIGetPathSpecificOption(osPathForOption.c_str(),
1213 : "AWS_PROFILE", "");
1214 : }
1215 112 : const std::string osProfile(pszProfile[0] != '\0' ? pszProfile : "default");
1216 :
1217 112 : 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 56 : const char *pszCredentials = VSIGetPathSpecificOption(
1224 : osPathForOption.c_str(), "CPL_AWS_CREDENTIALS_FILE", nullptr);
1225 56 : if (pszCredentials)
1226 : {
1227 35 : osCredentials = pszCredentials;
1228 : }
1229 : else
1230 : {
1231 21 : osCredentials = osDotAws;
1232 21 : osCredentials += GetDirSeparator();
1233 21 : osCredentials += "credentials";
1234 : }
1235 :
1236 56 : ReadAWSCredentials(osProfile, osCredentials, osSecretAccessKey,
1237 : osAccessKeyId, osSessionToken);
1238 :
1239 : // And then ~/.aws/config file (unless AWS_CONFIG_FILE is defined)
1240 56 : const char *pszAWSConfigFileEnv = VSIGetPathSpecificOption(
1241 : osPathForOption.c_str(), "AWS_CONFIG_FILE", nullptr);
1242 56 : std::string osConfig;
1243 56 : if (pszAWSConfigFileEnv && pszAWSConfigFileEnv[0])
1244 : {
1245 13 : osConfig = pszAWSConfigFileEnv;
1246 : }
1247 : else
1248 : {
1249 43 : osConfig = std::move(osDotAws);
1250 43 : osConfig += GetDirSeparator();
1251 43 : osConfig += "config";
1252 : }
1253 :
1254 56 : VSILFILE *fp = VSIFOpenL(osConfig.c_str(), "rb");
1255 56 : 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 41 : 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 62 : return (!osAccessKeyId.empty() && !osSecretAccessKey.empty()) ||
1404 50 : (!osRoleArn.empty() && !osSourceProfile.empty()) ||
1405 1 : (pszProfileOri != nullptr && !osRoleArn.empty() &&
1406 1 : !osWebIdentityTokenFile.empty()) ||
1407 47 : (!osSSOStartURL.empty() && !osSSOAccountID.empty() &&
1408 112 : !osSSORoleName.empty()) ||
1409 158 : !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 632 : 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 632 : 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 632 : "us-east-1"));
1951 :
1952 632 : if (CPLTestBool(VSIGetPathSpecificOption(osPathForOption.c_str(),
1953 : "AWS_NO_SIGN_REQUEST", "NO")))
1954 : {
1955 41 : eCredentialsSource = AWSCredentialsSource::NO_SIGN_REQUEST;
1956 41 : osSecretAccessKey.clear();
1957 41 : osAccessKeyId.clear();
1958 41 : osSessionToken.clear();
1959 41 : return true;
1960 : }
1961 :
1962 : osSecretAccessKey = CSLFetchNameValueDef(
1963 : papszOptions, "AWS_SECRET_ACCESS_KEY",
1964 : VSIGetPathSpecificOption(osPathForOption.c_str(),
1965 591 : "AWS_SECRET_ACCESS_KEY", ""));
1966 591 : if (!osSecretAccessKey.empty())
1967 : {
1968 : osAccessKeyId = CSLFetchNameValueDef(
1969 : papszOptions, "AWS_ACCESS_KEY_ID",
1970 : VSIGetPathSpecificOption(osPathForOption.c_str(),
1971 534 : "AWS_ACCESS_KEY_ID", ""));
1972 534 : 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 533 : eCredentialsSource = AWSCredentialsSource::REGULAR;
1980 : osSessionToken = CSLFetchNameValueDef(
1981 : papszOptions, "AWS_SESSION_TOKEN",
1982 : VSIGetPathSpecificOption(osPathForOption.c_str(),
1983 533 : "AWS_SESSION_TOKEN", ""));
1984 533 : return true;
1985 : }
1986 :
1987 : // Next try to see if we have a current assumed role
1988 57 : bool bAssumedRole = false;
1989 57 : bool bSSO = false;
1990 57 : bool bCredentialProcess = false;
1991 : {
1992 57 : CPLMutexHolder oHolder(&ghMutex);
1993 57 : bAssumedRole = !gosRoleArn.empty();
1994 57 : bSSO = !gosSSOStartURL.empty();
1995 57 : bCredentialProcess = !gosCredentialProcessCommand.empty();
1996 : }
1997 57 : if (bAssumedRole && GetOrRefreshTemporaryCredentialsForRole(
1998 : /* bForceRefresh = */ false, osSecretAccessKey,
1999 : osAccessKeyId, osSessionToken, osRegion))
2000 : {
2001 4 : eCredentialsSource = AWSCredentialsSource::ASSUMED_ROLE;
2002 4 : return true;
2003 : }
2004 53 : else if (bSSO && GetOrRefreshTemporaryCredentialsForSSO(
2005 : /* bForceRefresh = */ false, osSecretAccessKey,
2006 : osAccessKeyId, osSessionToken, osRegion))
2007 : {
2008 1 : eCredentialsSource = AWSCredentialsSource::SSO;
2009 1 : return true;
2010 : }
2011 54 : 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 102 : std::string osCredentials;
2022 102 : std::string osRoleArn;
2023 102 : std::string osSourceProfile;
2024 102 : std::string osExternalId;
2025 102 : std::string osMFASerial;
2026 102 : std::string osRoleSessionName;
2027 102 : std::string osWebIdentityTokenFile;
2028 102 : std::string osSSOStartURL;
2029 102 : std::string osSSOAccountID;
2030 102 : std::string osSSORoleName;
2031 102 : std::string osSSOSession;
2032 102 : std::string osCredentialProcess;
2033 : // coverity[tainted_data]
2034 51 : 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 : eCredentialsSource = AWSCredentialsSource::REGULAR;
2198 5 : return true;
2199 : }
2200 :
2201 37 : if (CPLTestBool(CPLGetConfigOption("CPL_AWS_WEB_IDENTITY_ENABLE", "YES")))
2202 : {
2203 : // WebIdentity method: use Web Identity Token
2204 31 : if (GetConfigurationFromAssumeRoleWithWebIdentity(
2205 : /* bForceRefresh = */ false, osPathForOption,
2206 62 : /* osRoleArnIn = */ std::string(),
2207 62 : /* osWebIdentityTokenFileIn = */ std::string(),
2208 : osSecretAccessKey, osAccessKeyId, osSessionToken))
2209 : {
2210 1 : eCredentialsSource = AWSCredentialsSource::WEB_IDENTITY;
2211 1 : return true;
2212 : }
2213 : }
2214 :
2215 : // Last method: use IAM role security credentials on EC2 instances
2216 36 : if (GetConfigurationFromEC2(/* bForceRefresh = */ false, osPathForOption,
2217 : osSecretAccessKey, osAccessKeyId,
2218 : osSessionToken))
2219 : {
2220 12 : eCredentialsSource = AWSCredentialsSource::EC2;
2221 12 : return true;
2222 : }
2223 :
2224 24 : CPLString osMsg;
2225 : osMsg.Printf(
2226 : "No valid AWS credentials found. "
2227 : "For authenticated requests, you need to set "
2228 : "AWS_SECRET_ACCESS_KEY, AWS_ACCESS_KEY_ID or other configuration "
2229 : "options, or create a %s file. Consult "
2230 : "https://gdal.org/en/stable/user/"
2231 : "virtual_file_systems.html#vsis3-aws-s3-files for more details. "
2232 : "For unauthenticated requests on public resources, set the "
2233 : "AWS_NO_SIGN_REQUEST configuration option to YES.",
2234 24 : osCredentials.c_str());
2235 24 : CPLDebug(AWS_DEBUG_KEY, "%s", osMsg.c_str());
2236 24 : VSIError(VSIE_InvalidCredentials, "%s", osMsg.c_str());
2237 :
2238 24 : return false;
2239 : }
2240 :
2241 : /************************************************************************/
2242 : /* CleanMutex() */
2243 : /************************************************************************/
2244 :
2245 1136 : void VSIS3HandleHelper::CleanMutex()
2246 : {
2247 1136 : if (ghMutex != nullptr)
2248 1136 : CPLDestroyMutex(ghMutex);
2249 1136 : ghMutex = nullptr;
2250 1136 : }
2251 :
2252 : /************************************************************************/
2253 : /* ClearCache() */
2254 : /************************************************************************/
2255 :
2256 1504 : void VSIS3HandleHelper::ClearCache()
2257 : {
2258 3008 : CPLMutexHolder oHolder(&ghMutex);
2259 :
2260 1504 : geCredentialsSource = AWSCredentialsSource::UNINITIALIZED;
2261 1504 : gosIAMRole.clear();
2262 1504 : gosGlobalAccessKeyId.clear();
2263 1504 : gosGlobalSecretAccessKey.clear();
2264 1504 : gosGlobalSessionToken.clear();
2265 1504 : gnGlobalExpiration = 0;
2266 1504 : gosRoleArn.clear();
2267 1504 : gosExternalId.clear();
2268 1504 : gosMFASerial.clear();
2269 1504 : gosRoleSessionName.clear();
2270 1504 : gosSourceProfileAccessKeyId.clear();
2271 1504 : gosSourceProfileSecretAccessKey.clear();
2272 1504 : gosSourceProfileSessionToken.clear();
2273 1504 : gosRegion.clear();
2274 1504 : gosRoleArnWebIdentity.clear();
2275 1504 : gosWebIdentityTokenFile.clear();
2276 1504 : gosSSOStartURL.clear();
2277 1504 : gosSSOAccountID.clear();
2278 1504 : gosSSORoleName.clear();
2279 1504 : gosCredentialProcessCommand.clear();
2280 1504 : }
2281 :
2282 : /************************************************************************/
2283 : /* BuildFromURI() */
2284 : /************************************************************************/
2285 :
2286 632 : VSIS3HandleHelper *VSIS3HandleHelper::BuildFromURI(const char *pszURI,
2287 : const char *pszFSPrefix,
2288 : bool bAllowNoObject,
2289 : CSLConstList papszOptions)
2290 : {
2291 1264 : std::string osPathForOption("/vsis3/");
2292 632 : if (pszURI)
2293 629 : osPathForOption += pszURI;
2294 :
2295 1264 : std::string osSecretAccessKey;
2296 1264 : std::string osAccessKeyId;
2297 1264 : std::string osSessionToken;
2298 1264 : std::string osRegion;
2299 632 : AWSCredentialsSource eCredentialsSource =
2300 : AWSCredentialsSource::UNINITIALIZED;
2301 632 : if (!GetConfiguration(osPathForOption, papszOptions, osSecretAccessKey,
2302 : osAccessKeyId, osSessionToken, osRegion,
2303 : eCredentialsSource))
2304 : {
2305 29 : return nullptr;
2306 : }
2307 :
2308 : // According to
2309 : // http://docs.aws.amazon.com/cli/latest/userguide/cli-environment.html "
2310 : // This variable overrides the default region of the in-use profile, if
2311 : // set."
2312 : std::string osDefaultRegion = CSLFetchNameValueDef(
2313 : papszOptions, "AWS_DEFAULT_REGION",
2314 : VSIGetPathSpecificOption(osPathForOption.c_str(), "AWS_DEFAULT_REGION",
2315 1206 : ""));
2316 603 : if (!osDefaultRegion.empty())
2317 : {
2318 593 : osRegion = std::move(osDefaultRegion);
2319 : }
2320 :
2321 : std::string osEndpoint = VSIGetPathSpecificOption(
2322 1206 : osPathForOption.c_str(), "AWS_S3_ENDPOINT", "s3.amazonaws.com");
2323 603 : bool bForceHTTP = false;
2324 603 : bool bForceHTTPS = false;
2325 603 : if (STARTS_WITH(osEndpoint.c_str(), "http://"))
2326 : {
2327 14 : bForceHTTP = true;
2328 14 : osEndpoint = osEndpoint.substr(strlen("http://"));
2329 : }
2330 589 : else if (STARTS_WITH(osEndpoint.c_str(), "https://"))
2331 : {
2332 20 : bForceHTTPS = true;
2333 20 : osEndpoint = osEndpoint.substr(strlen("https://"));
2334 : }
2335 603 : if (!osEndpoint.empty() && osEndpoint.back() == '/')
2336 0 : osEndpoint.pop_back();
2337 :
2338 : const std::string osRequestPayer = VSIGetPathSpecificOption(
2339 1206 : osPathForOption.c_str(), "AWS_REQUEST_PAYER", "");
2340 1206 : std::string osBucket;
2341 1206 : std::string osObjectKey;
2342 1198 : if (pszURI != nullptr && pszURI[0] != '\0' &&
2343 595 : !GetBucketAndObjectKey(pszURI, pszFSPrefix, bAllowNoObject, osBucket,
2344 : osObjectKey))
2345 : {
2346 1 : return nullptr;
2347 : }
2348 :
2349 : // Detect if this is a directory bucket
2350 : // Cf https://docs.aws.amazon.com/AmazonS3/latest/userguide/directory-bucket-naming-rules.html
2351 1204 : std::string osZoneId;
2352 602 : constexpr const char *DIR_BUCKET_SUFFIX = "--x-s3";
2353 1089 : if (osBucket.size() > strlen(DIR_BUCKET_SUFFIX) &&
2354 487 : cpl::ends_with(osBucket, DIR_BUCKET_SUFFIX))
2355 : {
2356 4 : const auto posEndZoneId = osBucket.size() - strlen(DIR_BUCKET_SUFFIX);
2357 4 : auto posZoneId = osBucket.rfind("--", posEndZoneId - 1);
2358 4 : if (posZoneId != std::string::npos)
2359 : {
2360 4 : posZoneId += strlen("--");
2361 4 : osZoneId = osBucket.substr(posZoneId, posEndZoneId - posZoneId);
2362 : }
2363 : }
2364 :
2365 1204 : std::string osService = "s3";
2366 :
2367 602 : if (!osRegion.empty() && osEndpoint == "s3.amazonaws.com")
2368 : {
2369 53 : if (CPLTestBool(CSLFetchNameValueDef(papszOptions,
2370 : "LIST_DIRECTORY_BUCKETS", "NO")))
2371 : {
2372 0 : osService = "s3express";
2373 0 : osEndpoint = "s3express-control." + osRegion + ".amazonaws.com";
2374 : }
2375 53 : else if (!osZoneId.empty())
2376 : {
2377 : osEndpoint =
2378 0 : "s3express-" + osZoneId + "." + osRegion + ".amazonaws.com";
2379 : }
2380 : else
2381 : {
2382 53 : osEndpoint = "s3." + osRegion + ".amazonaws.com";
2383 : }
2384 : }
2385 :
2386 : const bool bUseHTTPS =
2387 1184 : bForceHTTPS ||
2388 582 : (!bForceHTTP && CPLTestBool(VSIGetPathSpecificOption(
2389 602 : osPathForOption.c_str(), "AWS_HTTPS", "YES")));
2390 : const bool bIsValidNameForVirtualHosting =
2391 602 : osBucket.find('.') == std::string::npos;
2392 602 : const bool bUseVirtualHosting = CPLTestBool(CSLFetchNameValueDef(
2393 : papszOptions, "AWS_VIRTUAL_HOSTING",
2394 : VSIGetPathSpecificOption(osPathForOption.c_str(), "AWS_VIRTUAL_HOSTING",
2395 : bIsValidNameForVirtualHosting ? "TRUE"
2396 : : "FALSE")));
2397 : const std::string osS3SessionToken = VSIGetPathSpecificOption(
2398 602 : osPathForOption.c_str(), "AWS_S3SESSION_TOKEN", "");
2399 :
2400 : return new VSIS3HandleHelper(osService, osSecretAccessKey, osAccessKeyId,
2401 : osSessionToken, osS3SessionToken, osEndpoint,
2402 : osRegion, osRequestPayer, osBucket,
2403 : osObjectKey, bUseHTTPS, bUseVirtualHosting,
2404 602 : eCredentialsSource, !osZoneId.empty());
2405 : }
2406 :
2407 : /************************************************************************/
2408 : /* GetQueryString() */
2409 : /************************************************************************/
2410 :
2411 : std::string
2412 1996 : IVSIS3LikeHandleHelper::GetQueryString(bool bAddEmptyValueAfterEqual) const
2413 : {
2414 1996 : std::string osQueryString;
2415 : std::map<std::string, std::string>::const_iterator oIter =
2416 1996 : m_oMapQueryParameters.begin();
2417 4101 : for (; oIter != m_oMapQueryParameters.end(); ++oIter)
2418 : {
2419 2105 : if (oIter == m_oMapQueryParameters.begin())
2420 946 : osQueryString += "?";
2421 : else
2422 1159 : osQueryString += "&";
2423 2105 : osQueryString += oIter->first;
2424 2105 : if (!oIter->second.empty() || bAddEmptyValueAfterEqual)
2425 : {
2426 2072 : osQueryString += "=";
2427 2072 : osQueryString += CPLAWSURLEncode(oIter->second);
2428 : }
2429 : }
2430 3992 : return osQueryString;
2431 : }
2432 :
2433 : /************************************************************************/
2434 : /* ResetQueryParameters() */
2435 : /************************************************************************/
2436 :
2437 610 : void IVSIS3LikeHandleHelper::ResetQueryParameters()
2438 : {
2439 610 : m_oMapQueryParameters.clear();
2440 610 : RebuildURL();
2441 610 : }
2442 :
2443 : /************************************************************************/
2444 : /* AddQueryParameter() */
2445 : /************************************************************************/
2446 :
2447 806 : void IVSIS3LikeHandleHelper::AddQueryParameter(const std::string &osKey,
2448 : const std::string &osValue)
2449 : {
2450 806 : m_oMapQueryParameters[osKey] = osValue;
2451 806 : RebuildURL();
2452 806 : }
2453 :
2454 : /************************************************************************/
2455 : /* GetURLNoKVP() */
2456 : /************************************************************************/
2457 :
2458 433 : std::string IVSIS3LikeHandleHelper::GetURLNoKVP() const
2459 : {
2460 433 : std::string osURL(GetURL());
2461 433 : const auto nPos = osURL.find('?');
2462 433 : if (nPos != std::string::npos)
2463 8 : osURL.resize(nPos);
2464 433 : return osURL;
2465 : }
2466 :
2467 : /************************************************************************/
2468 : /* RefreshCredentials() */
2469 : /************************************************************************/
2470 :
2471 368 : void VSIS3HandleHelper::RefreshCredentials(const std::string &osPathForOption,
2472 : bool bForceRefresh) const
2473 : {
2474 368 : if (m_eCredentialsSource == AWSCredentialsSource::EC2)
2475 : {
2476 22 : std::string osSecretAccessKey, osAccessKeyId, osSessionToken;
2477 11 : if (GetConfigurationFromEC2(bForceRefresh, osPathForOption.c_str(),
2478 : osSecretAccessKey, osAccessKeyId,
2479 : osSessionToken))
2480 : {
2481 11 : m_osSecretAccessKey = std::move(osSecretAccessKey);
2482 11 : m_osAccessKeyId = std::move(osAccessKeyId);
2483 11 : m_osSessionToken = std::move(osSessionToken);
2484 : }
2485 : }
2486 357 : else if (m_eCredentialsSource == AWSCredentialsSource::ASSUMED_ROLE)
2487 : {
2488 12 : std::string osSecretAccessKey, osAccessKeyId, osSessionToken;
2489 12 : std::string osRegion;
2490 6 : if (GetOrRefreshTemporaryCredentialsForRole(
2491 : bForceRefresh, osSecretAccessKey, osAccessKeyId, osSessionToken,
2492 : osRegion))
2493 : {
2494 5 : m_osSecretAccessKey = std::move(osSecretAccessKey);
2495 5 : m_osAccessKeyId = std::move(osAccessKeyId);
2496 5 : m_osSessionToken = std::move(osSessionToken);
2497 : }
2498 : }
2499 351 : else if (m_eCredentialsSource == AWSCredentialsSource::WEB_IDENTITY)
2500 : {
2501 2 : std::string osSecretAccessKey, osAccessKeyId, osSessionToken;
2502 1 : if (GetConfigurationFromAssumeRoleWithWebIdentity(
2503 2 : bForceRefresh, osPathForOption.c_str(), std::string(),
2504 2 : std::string(), osSecretAccessKey, osAccessKeyId,
2505 : osSessionToken))
2506 : {
2507 1 : m_osSecretAccessKey = std::move(osSecretAccessKey);
2508 1 : m_osAccessKeyId = std::move(osAccessKeyId);
2509 1 : m_osSessionToken = std::move(osSessionToken);
2510 : }
2511 : }
2512 350 : else if (m_eCredentialsSource == AWSCredentialsSource::SSO)
2513 : {
2514 4 : std::string osSecretAccessKey, osAccessKeyId, osSessionToken;
2515 4 : std::string osRegion;
2516 2 : if (GetOrRefreshTemporaryCredentialsForSSO(
2517 : bForceRefresh, osSecretAccessKey, osAccessKeyId, osSessionToken,
2518 : osRegion))
2519 : {
2520 2 : m_osSecretAccessKey = std::move(osSecretAccessKey);
2521 2 : m_osAccessKeyId = std::move(osAccessKeyId);
2522 2 : m_osSessionToken = std::move(osSessionToken);
2523 : }
2524 : }
2525 348 : else if (m_eCredentialsSource == AWSCredentialsSource::CREDENTIAL_PROCESS)
2526 : {
2527 6 : std::string osCredentialProcess;
2528 6 : std::string osSecretAccessKey, osAccessKeyId, osSessionToken, osRegion;
2529 6 : std::string osCredentials, osRoleArn, osSourceProfile, osExternalId;
2530 6 : std::string osMFASerial, osRoleSessionName, osWebIdentityTokenFile;
2531 6 : std::string osSSOStartURL, osSSOAccountID, osSSORoleName, osSSOSession;
2532 :
2533 3 : if (GetConfigurationFromAWSConfigFiles(
2534 : osPathForOption, nullptr, osSecretAccessKey, osAccessKeyId,
2535 : osSessionToken, osRegion, osCredentials, osRoleArn,
2536 : osSourceProfile, osExternalId, osMFASerial, osRoleSessionName,
2537 : osWebIdentityTokenFile, osSSOStartURL, osSSOAccountID,
2538 3 : osSSORoleName, osSSOSession, osCredentialProcess) &&
2539 0 : !osCredentialProcess.empty())
2540 : {
2541 0 : if (GetCredentialsFromProcess(osCredentialProcess,
2542 : osSecretAccessKey, osAccessKeyId,
2543 : osSessionToken))
2544 : {
2545 0 : m_osSecretAccessKey = std::move(osSecretAccessKey);
2546 0 : m_osAccessKeyId = std::move(osAccessKeyId);
2547 0 : m_osSessionToken = std::move(osSessionToken);
2548 : }
2549 : }
2550 : }
2551 368 : }
2552 :
2553 : /************************************************************************/
2554 : /* GetCurlHeaders() */
2555 : /************************************************************************/
2556 :
2557 367 : struct curl_slist *VSIS3HandleHelper::GetCurlHeaders(
2558 : const std::string &osVerb, struct curl_slist *psHeaders,
2559 : const void *pabyDataContent, size_t nBytesContent) const
2560 : {
2561 734 : std::string osPathForOption("/vsis3/");
2562 367 : osPathForOption += m_osBucket;
2563 367 : osPathForOption += '/';
2564 367 : osPathForOption += m_osObjectKey;
2565 :
2566 367 : RefreshCredentials(osPathForOption, /* bForceRefresh = */ false);
2567 :
2568 : std::string osXAMZDate =
2569 734 : VSIGetPathSpecificOption(osPathForOption.c_str(), "AWS_TIMESTAMP", "");
2570 367 : if (osXAMZDate.empty())
2571 12 : osXAMZDate = CPLGetAWS_SIGN4_Timestamp(time(nullptr));
2572 :
2573 : const std::string osXAMZContentSHA256 =
2574 734 : CPLGetLowerCaseHexSHA256(pabyDataContent, nBytesContent);
2575 :
2576 734 : std::string osCanonicalQueryString(GetQueryString(true));
2577 367 : if (!osCanonicalQueryString.empty())
2578 131 : osCanonicalQueryString = osCanonicalQueryString.substr(1);
2579 :
2580 : // If accessing a AWS account from a AWS VM, check that
2581 : // AWS is still a sponsor, and if not, make some (kind) noise.
2582 1101 : if ((m_eCredentialsSource != AWSCredentialsSource::UNINITIALIZED &&
2583 367 : m_eCredentialsSource != AWSCredentialsSource::NO_SIGN_REQUEST &&
2584 363 : m_eCredentialsSource != AWSCredentialsSource::REGULAR &&
2585 22 : m_osEndpoint.find(".amazonaws.com") != std::string::npos)
2586 : #ifdef DEBUG
2587 734 : || CPLTestBool(CPLGetConfigOption("GDAL_TEST_NAME_AND_SHAME", "NO"))
2588 : #endif
2589 : )
2590 : {
2591 0 : static const bool bCheckSponsoring = []()
2592 : {
2593 0 : if (!CPLTestBool(CPLGetConfigOption("GDAL_NAME_AND_SHAME", "YES")))
2594 0 : return true;
2595 :
2596 0 : const std::string osCacheDir = []()
2597 : {
2598 : #ifdef _WIN32
2599 : const char *pszHome =
2600 : CPLGetConfigOption("USERPROFILE", nullptr);
2601 : #else
2602 0 : const char *pszHome = CPLGetConfigOption("HOME", nullptr);
2603 : #endif
2604 0 : if (pszHome != nullptr)
2605 : {
2606 0 : return CPLFormFilenameSafe(pszHome, ".gdal", nullptr);
2607 : }
2608 : else
2609 : {
2610 0 : const char *pszDir = CPLGetConfigOption("TEMP", "/tmp");
2611 : VSIStatBufL sStat;
2612 0 : if (VSIStatL(pszDir, &sStat) == 0)
2613 : {
2614 : const char *pszUsername =
2615 0 : CPLGetConfigOption("USERNAME", nullptr);
2616 0 : if (pszUsername == nullptr)
2617 0 : pszUsername = CPLGetConfigOption("USER", nullptr);
2618 :
2619 0 : if (pszUsername != nullptr)
2620 : {
2621 : return CPLFormFilenameSafe(
2622 : pszDir, CPLSPrintf(".gdal_%s", pszUsername),
2623 0 : nullptr);
2624 : }
2625 : }
2626 : }
2627 0 : return std::string();
2628 0 : }();
2629 0 : if (!osCacheDir.empty())
2630 : {
2631 : VSIStatBufL sStat;
2632 0 : if (VSIStatL(osCacheDir.c_str(), &sStat) != 0)
2633 0 : VSIMkdir(osCacheDir.c_str(), 0755);
2634 : const std::string osCloudCheck = CPLFormFilenameSafe(
2635 0 : osCacheDir.c_str(), "cloud_check_aws.txt", nullptr);
2636 : // Sideral day, why not? "Aim for the stars, expect dust"
2637 0 : constexpr int ONE_DAY_IN_SECS = 86164;
2638 0 : if (VSIStatL(osCloudCheck.c_str(), &sStat) == 0 &&
2639 0 : sStat.st_mtime + ONE_DAY_IN_SECS >= time(nullptr))
2640 : {
2641 0 : CPLDebugOnly("GDAL", "%s checked", osCloudCheck.c_str());
2642 : }
2643 : else
2644 : {
2645 0 : FILE *f = fopen(osCloudCheck.c_str(), "wb");
2646 0 : if (f)
2647 0 : fclose(f);
2648 :
2649 0 : const auto PingURL = [](const char *pszURL)
2650 : {
2651 0 : CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
2652 0 : const char *const apszOptions[] = {
2653 : "CUSTOMREQUEST=HEAD", "TIMEOUT=1", nullptr};
2654 0 : auto res = CPLHTTPFetch(pszURL, apszOptions);
2655 0 : const bool bOK = res && !res->pszErrBuf;
2656 0 : CPLHTTPDestroyResult(res);
2657 0 : return bOK;
2658 : };
2659 0 : if (!PingURL("https://gdal.org/en/latest/sponsors/"
2660 0 : "did_aws_sponsor.html") &&
2661 : // check that gdal.org is responding to avoid false positive
2662 0 : PingURL("https://gdal.org/en/latest/index.html"))
2663 : {
2664 0 : const auto CPLE_NonCooperativeSponsor = CPLE_AppDefined;
2665 0 : CPLError(
2666 : CE_Warning, CPLE_NonCooperativeSponsor,
2667 : "Due to lack of resources, Amazon S3 access is "
2668 : "undergoing minimal maintenance and may "
2669 : "be removed in the future unless AWS re-evaluates "
2670 : "its decision to stop sponsoring GDAL. If you are "
2671 : "interested in keeping this functionality please "
2672 : "get in touch with your AWS representative.");
2673 : }
2674 : }
2675 : }
2676 0 : return true;
2677 0 : }();
2678 0 : CPL_IGNORE_RET_VAL(bCheckSponsoring);
2679 : }
2680 :
2681 14 : const std::string osHost(m_bUseVirtualHosting && !m_osBucket.empty()
2682 381 : ? std::string(m_osBucket + "." + m_osEndpoint)
2683 748 : : m_osEndpoint);
2684 :
2685 367 : if (!m_osSessionToken.empty())
2686 14 : psHeaders =
2687 14 : curl_slist_append(psHeaders, CPLSPrintf("X-Amz-Security-Token: %s",
2688 : m_osSessionToken.c_str()));
2689 :
2690 367 : if (!m_osS3SessionToken.empty())
2691 1 : psHeaders = curl_slist_append(psHeaders,
2692 : CPLSPrintf("x-amz-s3session-token: %s",
2693 : m_osS3SessionToken.c_str()));
2694 :
2695 367 : if (!m_osRequestPayer.empty())
2696 2 : psHeaders =
2697 2 : curl_slist_append(psHeaders, CPLSPrintf("x-amz-request-payer: %s",
2698 : m_osRequestPayer.c_str()));
2699 : const std::string osAuthorization =
2700 367 : m_osSecretAccessKey.empty()
2701 : ? std::string()
2702 : : CPLGetAWS_SIGN4_Authorization(
2703 341 : m_osSecretAccessKey, m_osAccessKeyId, m_osRegion, m_osService,
2704 : osVerb, psHeaders, osHost,
2705 341 : m_bUseVirtualHosting
2706 367 : ? CPLAWSURLEncode("/" + m_osObjectKey, false).c_str()
2707 1731 : : CPLAWSURLEncode("/" + m_osBucket + "/" + m_osObjectKey,
2708 : false)
2709 341 : .c_str(),
2710 : osCanonicalQueryString, osXAMZContentSHA256,
2711 : true, // bAddHeaderAMZContentSHA256
2712 1416 : osXAMZDate);
2713 :
2714 367 : if (!osAuthorization.empty())
2715 : {
2716 341 : psHeaders =
2717 341 : curl_slist_append(psHeaders, CPLSPrintf("Authorization: %s",
2718 : osAuthorization.c_str()));
2719 : }
2720 734 : return psHeaders;
2721 : }
2722 :
2723 : /************************************************************************/
2724 : /* CanRestartOnError() */
2725 : /************************************************************************/
2726 :
2727 54 : bool VSIS3HandleHelper::CanRestartOnError(const char *pszErrorMsg,
2728 : const char *pszHeaders,
2729 : bool bSetError)
2730 : {
2731 : #ifdef DEBUG_VERBOSE
2732 : CPLDebug(AWS_DEBUG_KEY, "%s", pszErrorMsg);
2733 : CPLDebug(AWS_DEBUG_KEY, "%s", pszHeaders ? pszHeaders : "");
2734 : #endif
2735 :
2736 54 : if (!STARTS_WITH(pszErrorMsg, "<?xml") &&
2737 17 : !STARTS_WITH(pszErrorMsg, "<Error>"))
2738 : {
2739 17 : if (bSetError)
2740 : {
2741 9 : VSIError(VSIE_ObjectStorageGenericError, "Invalid AWS response: %s",
2742 : pszErrorMsg);
2743 : }
2744 17 : return false;
2745 : }
2746 :
2747 37 : CPLXMLNode *psTree = CPLParseXMLString(pszErrorMsg);
2748 37 : if (psTree == nullptr)
2749 : {
2750 2 : if (bSetError)
2751 : {
2752 2 : VSIError(VSIE_ObjectStorageGenericError,
2753 : "Malformed AWS XML response: %s", pszErrorMsg);
2754 : }
2755 2 : return false;
2756 : }
2757 :
2758 35 : const char *pszCode = CPLGetXMLValue(psTree, "=Error.Code", nullptr);
2759 35 : if (pszCode == nullptr)
2760 : {
2761 2 : CPLDestroyXMLNode(psTree);
2762 2 : if (bSetError)
2763 : {
2764 2 : VSIError(VSIE_ObjectStorageGenericError,
2765 : "Malformed AWS XML response: %s", pszErrorMsg);
2766 : }
2767 2 : return false;
2768 : }
2769 :
2770 33 : if (EQUAL(pszCode, "AuthorizationHeaderMalformed"))
2771 : {
2772 : const char *pszRegion =
2773 9 : CPLGetXMLValue(psTree, "=Error.Region", nullptr);
2774 9 : if (pszRegion == nullptr)
2775 : {
2776 2 : CPLDestroyXMLNode(psTree);
2777 2 : if (bSetError)
2778 : {
2779 2 : VSIError(VSIE_ObjectStorageGenericError,
2780 : "Malformed AWS XML response: %s", pszErrorMsg);
2781 : }
2782 2 : return false;
2783 : }
2784 7 : SetRegion(pszRegion);
2785 7 : CPLDebug(AWS_DEBUG_KEY, "Switching to region %s", m_osRegion.c_str());
2786 7 : CPLDestroyXMLNode(psTree);
2787 :
2788 7 : VSIS3UpdateParams::UpdateMapFromHandle(this);
2789 :
2790 7 : return true;
2791 : }
2792 :
2793 24 : if (EQUAL(pszCode, "PermanentRedirect") ||
2794 16 : EQUAL(pszCode, "TemporaryRedirect"))
2795 : {
2796 14 : const bool bIsTemporaryRedirect = EQUAL(pszCode, "TemporaryRedirect");
2797 : const char *pszEndpoint =
2798 14 : CPLGetXMLValue(psTree, "=Error.Endpoint", nullptr);
2799 26 : if (pszEndpoint == nullptr ||
2800 12 : (m_bUseVirtualHosting && (strncmp(pszEndpoint, m_osBucket.c_str(),
2801 0 : m_osBucket.size()) != 0 ||
2802 0 : pszEndpoint[m_osBucket.size()] != '.')))
2803 : {
2804 2 : CPLDestroyXMLNode(psTree);
2805 2 : if (bSetError)
2806 : {
2807 2 : VSIError(VSIE_ObjectStorageGenericError,
2808 : "Malformed AWS XML response: %s", pszErrorMsg);
2809 : }
2810 2 : return false;
2811 : }
2812 36 : if (!m_bUseVirtualHosting &&
2813 13 : strncmp(pszEndpoint, m_osBucket.c_str(), m_osBucket.size()) == 0 &&
2814 1 : pszEndpoint[m_osBucket.size()] == '.')
2815 : {
2816 : /* If we have a body with
2817 : <Error><Code>PermanentRedirect</Code><Message>The bucket you are
2818 : attempting to access must be addressed using the specified endpoint.
2819 : Please send all future requests to this
2820 : endpoint.</Message><Bucket>bucket.with.dot</Bucket><Endpoint>bucket.with.dot.s3.amazonaws.com</Endpoint></Error>
2821 : and headers like
2822 : x-amz-bucket-region: eu-west-1
2823 : and the bucket name has dot in it,
2824 : then we must use s3.$(x-amz-bucket-region).amazon.com as endpoint.
2825 : See #7154 */
2826 1 : const char *pszRegionPtr =
2827 : (pszHeaders != nullptr)
2828 1 : ? strstr(pszHeaders, "x-amz-bucket-region: ")
2829 : : nullptr;
2830 1 : if (strchr(m_osBucket.c_str(), '.') != nullptr &&
2831 : pszRegionPtr != nullptr)
2832 : {
2833 : std::string osRegion(pszRegionPtr +
2834 1 : strlen("x-amz-bucket-region: "));
2835 1 : size_t nPos = osRegion.find('\r');
2836 1 : if (nPos != std::string::npos)
2837 1 : osRegion.resize(nPos);
2838 1 : SetEndpoint(
2839 : CPLSPrintf("s3.%s.amazonaws.com", osRegion.c_str()));
2840 1 : SetRegion(osRegion.c_str());
2841 1 : CPLDebug(AWS_DEBUG_KEY, "Switching to endpoint %s",
2842 : m_osEndpoint.c_str());
2843 1 : CPLDebug(AWS_DEBUG_KEY, "Switching to region %s",
2844 : m_osRegion.c_str());
2845 1 : CPLDestroyXMLNode(psTree);
2846 1 : if (!bIsTemporaryRedirect)
2847 1 : VSIS3UpdateParams::UpdateMapFromHandle(this);
2848 1 : return true;
2849 : }
2850 :
2851 0 : m_bUseVirtualHosting = true;
2852 0 : CPLDebug(AWS_DEBUG_KEY, "Switching to virtual hosting");
2853 : }
2854 11 : SetEndpoint(m_bUseVirtualHosting ? pszEndpoint + m_osBucket.size() + 1
2855 : : pszEndpoint);
2856 11 : CPLDebug(AWS_DEBUG_KEY, "Switching to endpoint %s",
2857 : m_osEndpoint.c_str());
2858 11 : CPLDestroyXMLNode(psTree);
2859 :
2860 11 : if (!bIsTemporaryRedirect)
2861 5 : VSIS3UpdateParams::UpdateMapFromHandle(this);
2862 :
2863 11 : return true;
2864 : }
2865 :
2866 10 : if (EQUAL(pszCode, "RequestTimeout"))
2867 : {
2868 : const char *pszMessage =
2869 2 : CPLGetXMLValue(psTree, "=Error.Message", nullptr);
2870 2 : if (pszMessage != nullptr)
2871 1 : CPLDebug("S3", "Request Timeout: %s", pszMessage);
2872 :
2873 2 : CPLDestroyXMLNode(psTree);
2874 2 : return true;
2875 : }
2876 :
2877 8 : if (bSetError)
2878 : {
2879 : // Translate AWS errors into VSI errors.
2880 :
2881 : const char *pszMessage =
2882 8 : CPLGetXMLValue(psTree, "=Error.Message", pszErrorMsg);
2883 : // Some S3 implementations (e.g. OpenStack) skip the Message part
2884 :
2885 8 : if (EQUAL(pszCode, "AccessDenied"))
2886 : {
2887 0 : VSIError(VSIE_AccessDenied, "%s", pszMessage);
2888 : }
2889 8 : else if (EQUAL(pszCode, "NoSuchBucket"))
2890 : {
2891 0 : VSIError(VSIE_BucketNotFound, "%s", pszMessage);
2892 : }
2893 8 : else if (EQUAL(pszCode, "NoSuchKey"))
2894 : {
2895 2 : VSIError(VSIE_ObjectNotFound, "%s", pszMessage);
2896 : }
2897 6 : else if (EQUAL(pszCode, "SignatureDoesNotMatch"))
2898 : {
2899 0 : VSIError(VSIE_SignatureDoesNotMatch, "%s", pszMessage);
2900 : }
2901 : else
2902 : {
2903 6 : VSIError(VSIE_ObjectStorageGenericError, "%s", pszMessage);
2904 : }
2905 : }
2906 :
2907 8 : CPLDestroyXMLNode(psTree);
2908 :
2909 8 : return false;
2910 : }
2911 :
2912 : /************************************************************************/
2913 : /* SetEndpoint() */
2914 : /************************************************************************/
2915 :
2916 62 : void VSIS3HandleHelper::SetEndpoint(const std::string &osStr)
2917 : {
2918 62 : m_osEndpoint = osStr;
2919 62 : RebuildURL();
2920 62 : }
2921 :
2922 : /************************************************************************/
2923 : /* SetRegion() */
2924 : /************************************************************************/
2925 :
2926 58 : void VSIS3HandleHelper::SetRegion(const std::string &osStr)
2927 : {
2928 58 : m_osRegion = osStr;
2929 58 : }
2930 :
2931 : /************************************************************************/
2932 : /* SetRequestPayer() */
2933 : /************************************************************************/
2934 :
2935 50 : void VSIS3HandleHelper::SetRequestPayer(const std::string &osStr)
2936 : {
2937 50 : m_osRequestPayer = osStr;
2938 50 : }
2939 :
2940 : /************************************************************************/
2941 : /* SetVirtualHosting() */
2942 : /************************************************************************/
2943 :
2944 50 : void VSIS3HandleHelper::SetVirtualHosting(bool b)
2945 : {
2946 50 : m_bUseVirtualHosting = b;
2947 50 : RebuildURL();
2948 50 : }
2949 :
2950 : /************************************************************************/
2951 : /* GetSignedURL() */
2952 : /************************************************************************/
2953 :
2954 5 : std::string VSIS3HandleHelper::GetSignedURL(CSLConstList papszOptions)
2955 : {
2956 10 : std::string osPathForOption("/vsis3/");
2957 5 : osPathForOption += m_osBucket;
2958 5 : osPathForOption += '/';
2959 5 : osPathForOption += m_osObjectKey;
2960 :
2961 : std::string osXAMZDate = CSLFetchNameValueDef(
2962 : papszOptions, "START_DATE",
2963 10 : VSIGetPathSpecificOption(osPathForOption.c_str(), "AWS_TIMESTAMP", ""));
2964 5 : if (osXAMZDate.empty())
2965 0 : osXAMZDate = CPLGetAWS_SIGN4_Timestamp(time(nullptr));
2966 10 : std::string osDate(osXAMZDate);
2967 5 : osDate.resize(8);
2968 :
2969 : std::string osXAMZExpires =
2970 10 : CSLFetchNameValueDef(papszOptions, "EXPIRATION_DELAY", "3600");
2971 :
2972 5 : if (m_eCredentialsSource != AWSCredentialsSource::REGULAR)
2973 : {
2974 : // For credentials that have an expiration, we must check their
2975 : // expiration compared to the expiration of the signed URL, since
2976 : // if the effective expiration is min(desired_expiration,
2977 : // credential_expiration) Cf
2978 : // https://aws.amazon.com/premiumsupport/knowledge-center/presigned-url-s3-bucket-expiration
2979 2 : int nYear, nMonth, nDay, nHour = 0, nMin = 0, nSec = 0;
2980 2 : if (sscanf(osXAMZDate.c_str(), "%04d%02d%02dT%02d%02d%02dZ", &nYear,
2981 2 : &nMonth, &nDay, &nHour, &nMin, &nSec) < 3)
2982 : {
2983 0 : CPLError(CE_Failure, CPLE_AppDefined, "Bad format for START_DATE");
2984 0 : return std::string();
2985 : }
2986 : struct tm brokendowntime;
2987 2 : brokendowntime.tm_year = nYear - 1900;
2988 2 : brokendowntime.tm_mon = nMonth - 1;
2989 2 : brokendowntime.tm_mday = nDay;
2990 2 : brokendowntime.tm_hour = nHour;
2991 2 : brokendowntime.tm_min = nMin;
2992 2 : brokendowntime.tm_sec = nSec;
2993 2 : const GIntBig nStartDate = CPLYMDHMSToUnixTime(&brokendowntime);
2994 :
2995 : {
2996 4 : CPLMutexHolder oHolder(&ghMutex);
2997 :
2998 : // Try to reuse credentials if they will still be valid after the
2999 : // desired end of the validity of the signed URL,
3000 : // with one minute of margin
3001 2 : if (nStartDate + CPLAtoGIntBig(osXAMZExpires.c_str()) >=
3002 2 : gnGlobalExpiration - 60)
3003 : {
3004 1 : RefreshCredentials(osPathForOption, /* bForceRefresh = */ true);
3005 : }
3006 : }
3007 : }
3008 :
3009 10 : std::string osVerb(CSLFetchNameValueDef(papszOptions, "VERB", "GET"));
3010 :
3011 5 : ResetQueryParameters();
3012 5 : AddQueryParameter("X-Amz-Algorithm", "AWS4-HMAC-SHA256");
3013 15 : AddQueryParameter("X-Amz-Credential", m_osAccessKeyId + "/" + osDate + "/" +
3014 15 : m_osRegion + "/s3/aws4_request");
3015 5 : AddQueryParameter("X-Amz-Date", osXAMZDate);
3016 5 : AddQueryParameter("X-Amz-Expires", osXAMZExpires);
3017 5 : if (!m_osSessionToken.empty())
3018 1 : AddQueryParameter("X-Amz-Security-Token", m_osSessionToken);
3019 5 : AddQueryParameter("X-Amz-SignedHeaders", "host");
3020 :
3021 10 : std::string osCanonicalQueryString(GetQueryString(true).substr(1));
3022 :
3023 0 : const std::string osHost(m_bUseVirtualHosting && !m_osBucket.empty()
3024 5 : ? std::string(m_osBucket + "." + m_osEndpoint)
3025 10 : : m_osEndpoint);
3026 10 : std::string osSignedHeaders;
3027 :
3028 5 : struct curl_slist *psHeaders = nullptr;
3029 5 : if (!m_osRequestPayer.empty())
3030 0 : psHeaders =
3031 0 : curl_slist_append(psHeaders, CPLSPrintf("x-amz-request-payer: %s",
3032 : m_osRequestPayer.c_str()));
3033 : const std::string osSignature = CPLGetAWS_SIGN4_Signature(
3034 5 : m_osSecretAccessKey, m_osRegion, "s3", osVerb, psHeaders, osHost,
3035 5 : m_bUseVirtualHosting
3036 5 : ? CPLAWSURLEncode("/" + m_osObjectKey, false).c_str()
3037 25 : : CPLAWSURLEncode("/" + m_osBucket + "/" + m_osObjectKey, false)
3038 5 : .c_str(),
3039 : osCanonicalQueryString, "UNSIGNED-PAYLOAD",
3040 : false, // bAddHeaderAMZContentSHA256
3041 25 : osXAMZDate, osSignedHeaders);
3042 :
3043 5 : curl_slist_free_all(psHeaders);
3044 :
3045 5 : AddQueryParameter("X-Amz-Signature", osSignature);
3046 5 : return m_osURL;
3047 : }
3048 :
3049 : /************************************************************************/
3050 : /* UpdateMapFromHandle() */
3051 : /************************************************************************/
3052 :
3053 : std::mutex VSIS3UpdateParams::gsMutex{};
3054 :
3055 : std::map<std::string, VSIS3UpdateParams>
3056 : VSIS3UpdateParams::goMapBucketsToS3Params{};
3057 :
3058 13 : void VSIS3UpdateParams::UpdateMapFromHandle(VSIS3HandleHelper *poS3HandleHelper)
3059 : {
3060 13 : std::lock_guard<std::mutex> guard(gsMutex);
3061 :
3062 13 : goMapBucketsToS3Params[poS3HandleHelper->GetBucket()] =
3063 26 : VSIS3UpdateParams(poS3HandleHelper);
3064 13 : }
3065 :
3066 : /************************************************************************/
3067 : /* UpdateHandleFromMap() */
3068 : /************************************************************************/
3069 :
3070 602 : void VSIS3UpdateParams::UpdateHandleFromMap(VSIS3HandleHelper *poS3HandleHelper)
3071 : {
3072 1204 : std::lock_guard<std::mutex> guard(gsMutex);
3073 :
3074 : std::map<std::string, VSIS3UpdateParams>::iterator oIter =
3075 602 : goMapBucketsToS3Params.find(poS3HandleHelper->GetBucket());
3076 602 : if (oIter != goMapBucketsToS3Params.end())
3077 : {
3078 50 : oIter->second.UpdateHandlerHelper(poS3HandleHelper);
3079 : }
3080 602 : }
3081 :
3082 : /************************************************************************/
3083 : /* ClearCache() */
3084 : /************************************************************************/
3085 :
3086 1872 : void VSIS3UpdateParams::ClearCache()
3087 : {
3088 3744 : std::lock_guard<std::mutex> guard(gsMutex);
3089 :
3090 1872 : goMapBucketsToS3Params.clear();
3091 1872 : }
3092 :
3093 : #endif
3094 :
3095 : //! @endcond
|