Coverage Report

Created: 2025-11-15 08:43

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