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