Coverage Report

Created: 2025-06-09 07:43

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