• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

OSGeo / gdal / 13813968540

12 Mar 2025 02:33PM UTC coverage: 70.43% (-0.02%) from 70.446%
13813968540

Pull #11951

github

web-flow
Merge 0560ed8f8 into 5ab779ac6
Pull Request #11951: Doc: Build docs using CMake

553276 of 785573 relevant lines covered (70.43%)

222076.27 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

83.33
/port/cpl_aws.cpp
1
/**********************************************************************
2
 *
3
 * Name:     cpl_aws.cpp
4
 * Project:  CPL - Common Portability Library
5
 * Purpose:  Amazon Web Services routines
6
 * Author:   Even Rouault <even.rouault at spatialys.com>
7
 *
8
 **********************************************************************
9
 * Copyright (c) 2015, Even Rouault <even.rouault at spatialys.com>
10
 *
11
 * SPDX-License-Identifier: MIT
12
 ****************************************************************************/
13

14
//! @cond Doxygen_Suppress
15

16
#include "cpl_aws.h"
17
#include "cpl_json.h"
18
#include "cpl_vsi_error.h"
19
#include "cpl_sha1.h"
20
#include "cpl_sha256.h"
21
#include "cpl_time.h"
22
#include "cpl_minixml.h"
23
#include "cpl_multiproc.h"
24
#include "cpl_http.h"
25
#include <algorithm>
26

27
// #define DEBUG_VERBOSE 1
28

29
#ifdef _WIN32
30
#if defined(HAVE_ATLBASE_H)
31
bool CPLFetchWindowsProductUUID(
32
    std::string &osStr);  // defined in cpl_aws_win32.cpp
33
#endif
34
const char *CPLGetWineVersion();  // defined in cpl_vsil_win32.cpp
35
#endif
36

37
#ifdef HAVE_CURL
38
static CPLMutex *ghMutex = nullptr;
39
static std::string gosIAMRole;
40
static std::string gosGlobalAccessKeyId;
41
static std::string gosGlobalSecretAccessKey;
42
static std::string gosGlobalSessionToken;
43
static GIntBig gnGlobalExpiration = 0;
44
static std::string gosRegion;
45

46
// The below variables are used for credentials retrieved through a STS
47
// AssumedRole operation
48
static std::string gosRoleArn;
49
static std::string gosExternalId;
50
static std::string gosMFASerial;
51
static std::string gosRoleSessionName;
52
static std::string gosSourceProfileAccessKeyId;
53
static std::string gosSourceProfileSecretAccessKey;
54
static std::string gosSourceProfileSessionToken;
55

56
// The below variables are used for web identity settings in aws/config
57
static std::string gosRoleArnWebIdentity;
58
static std::string gosWebIdentityTokenFile;
59

60
// The below variables are used for SSO authentication
61
static std::string gosSSOStartURL;
62
static std::string gosSSOAccountID;
63
static std::string gosSSORoleName;
64

65
/************************************************************************/
66
/*                         CPLGetLowerCaseHex()                         */
67
/************************************************************************/
68

69
static std::string CPLGetLowerCaseHex(const GByte *pabyData, size_t nBytes)
1,065✔
70

71
{
72
    std::string osRet;
1,065✔
73
    osRet.resize(nBytes * 2);
1,065✔
74

75
    constexpr char achHex[] = "0123456789abcdef";
1,065✔
76

77
    for (size_t i = 0; i < nBytes; ++i)
35,145✔
78
    {
79
        const int nLow = pabyData[i] & 0x0f;
34,080✔
80
        const int nHigh = (pabyData[i] & 0xf0) >> 4;
34,080✔
81

82
        osRet[i * 2] = achHex[nHigh];
34,080✔
83
        osRet[i * 2 + 1] = achHex[nLow];
34,080✔
84
    }
85

86
    return osRet;
2,130✔
87
}
88

89
/************************************************************************/
90
/*                       CPLGetLowerCaseHexSHA256()                     */
91
/************************************************************************/
92

93
std::string CPLGetLowerCaseHexSHA256(const void *pabyData, size_t nBytes)
713✔
94
{
95
    GByte hash[CPL_SHA256_HASH_SIZE] = {};
713✔
96
    CPL_SHA256(static_cast<const GByte *>(pabyData), nBytes, hash);
713✔
97
    return CPLGetLowerCaseHex(hash, CPL_SHA256_HASH_SIZE);
1,426✔
98
}
99

100
/************************************************************************/
101
/*                       CPLGetLowerCaseHexSHA256()                     */
102
/************************************************************************/
103

104
std::string CPLGetLowerCaseHexSHA256(const std::string &osStr)
357✔
105
{
106
    return CPLGetLowerCaseHexSHA256(osStr.c_str(), osStr.size());
357✔
107
}
108

109
/************************************************************************/
110
/*                       CPLAWSURLEncode()                              */
111
/************************************************************************/
112

113
std::string CPLAWSURLEncode(const std::string &osURL, bool bEncodeSlash)
6,226✔
114
{
115
    std::string osRet;
6,226✔
116
    for (size_t i = 0; i < osURL.size(); i++)
12,102,200✔
117
    {
118
        char ch = osURL[i];
12,095,900✔
119
        if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') ||
12,096,000✔
120
            (ch >= '0' && ch <= '9') || ch == '_' || ch == '-' || ch == '~' ||
41,462✔
121
            ch == '.')
122
        {
123
            osRet += ch;
12,092,200✔
124
        }
125
        else if (ch == '/')
3,784✔
126
        {
127
            if (bEncodeSlash)
3,531✔
128
                osRet += "%2F";
616✔
129
            else
130
                osRet += ch;
2,915✔
131
        }
132
        else
133
        {
134
            osRet += CPLSPrintf("%%%02X", static_cast<unsigned char>(ch));
253✔
135
        }
136
    }
137
    return osRet;
6,226✔
138
}
139

140
/************************************************************************/
141
/*                         CPLAWSGetHeaderVal()                         */
142
/************************************************************************/
143

144
std::string CPLAWSGetHeaderVal(const struct curl_slist *psExistingHeaders,
2,556✔
145
                               const char *pszKey)
146
{
147
    std::string osKey(pszKey);
5,112✔
148
    osKey += ":";
2,556✔
149
    const struct curl_slist *psIter = psExistingHeaders;
2,556✔
150
    for (; psIter != nullptr; psIter = psIter->next)
3,915✔
151
    {
152
        if (STARTS_WITH(psIter->data, osKey.c_str()))
1,445✔
153
            return CPLString(psIter->data + osKey.size()).Trim();
172✔
154
    }
155
    return std::string();
2,470✔
156
}
157

158
/************************************************************************/
159
/*                 CPLGetAWS_SIGN4_Signature()                          */
160
/************************************************************************/
161

162
// See:
163
// http://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html
164
std::string CPLGetAWS_SIGN4_Signature(
352✔
165
    const std::string &osSecretAccessKey, const std::string &osAccessToken,
166
    const std::string &osRegion, const std::string &osRequestPayer,
167
    const std::string &osService, const std::string &osVerb,
168
    const struct curl_slist *psExistingHeaders, const std::string &osHost,
169
    const std::string &osCanonicalURI,
170
    const std::string &osCanonicalQueryString,
171
    const std::string &osXAMZContentSHA256, bool bAddHeaderAMZContentSHA256,
172
    const std::string &osTimestamp, std::string &osSignedHeaders)
173
{
174
    /* -------------------------------------------------------------------- */
175
    /*      Compute canonical request string.                               */
176
    /* -------------------------------------------------------------------- */
177
    std::string osCanonicalRequest = osVerb + "\n";
704✔
178

179
    osCanonicalRequest += osCanonicalURI + "\n";
352✔
180

181
    osCanonicalRequest += osCanonicalQueryString + "\n";
352✔
182

183
    std::map<std::string, std::string> oSortedMapHeaders;
704✔
184
    oSortedMapHeaders["host"] = osHost;
352✔
185
    if (osXAMZContentSHA256 != "UNSIGNED-PAYLOAD" && bAddHeaderAMZContentSHA256)
352✔
186
    {
187
        oSortedMapHeaders["x-amz-content-sha256"] = osXAMZContentSHA256;
342✔
188
        oSortedMapHeaders["x-amz-date"] = osTimestamp;
342✔
189
    }
190
    if (!osRequestPayer.empty())
352✔
191
        oSortedMapHeaders["x-amz-request-payer"] = osRequestPayer;
2✔
192
    if (!osAccessToken.empty())
352✔
193
        oSortedMapHeaders["x-amz-security-token"] = osAccessToken;
11✔
194
    std::string osCanonicalizedHeaders(
195
        IVSIS3LikeHandleHelper::BuildCanonicalizedHeaders(
196
            oSortedMapHeaders, psExistingHeaders, "x-amz-"));
704✔
197

198
    osCanonicalRequest += osCanonicalizedHeaders + "\n";
352✔
199

200
    osSignedHeaders.clear();
352✔
201
    std::map<std::string, std::string>::const_iterator oIter =
202
        oSortedMapHeaders.begin();
352✔
203
    for (; oIter != oSortedMapHeaders.end(); ++oIter)
1,419✔
204
    {
205
        if (!osSignedHeaders.empty())
1,067✔
206
            osSignedHeaders += ";";
715✔
207
        osSignedHeaders += oIter->first;
1,067✔
208
    }
209

210
    osCanonicalRequest += osSignedHeaders + "\n";
352✔
211

212
    osCanonicalRequest += osXAMZContentSHA256;
352✔
213

214
#ifdef DEBUG_VERBOSE
215
    CPLDebug("S3", "osCanonicalRequest='%s'", osCanonicalRequest.c_str());
216
#endif
217

218
    /* -------------------------------------------------------------------- */
219
    /*      Compute StringToSign .                                          */
220
    /* -------------------------------------------------------------------- */
221
    std::string osStringToSign = "AWS4-HMAC-SHA256\n";
704✔
222
    osStringToSign += osTimestamp + "\n";
352✔
223

224
    std::string osYYMMDD(osTimestamp);
704✔
225
    osYYMMDD.resize(8);
352✔
226

227
    std::string osScope = osYYMMDD + "/";
704✔
228
    osScope += osRegion;
352✔
229
    osScope += "/";
352✔
230
    osScope += osService;
352✔
231
    osScope += "/aws4_request";
352✔
232
    osStringToSign += osScope + "\n";
352✔
233
    osStringToSign += CPLGetLowerCaseHexSHA256(osCanonicalRequest);
352✔
234

235
#ifdef DEBUG_VERBOSE
236
    CPLDebug("S3", "osStringToSign='%s'", osStringToSign.c_str());
237
#endif
238

239
    /* -------------------------------------------------------------------- */
240
    /*      Compute signing key.                                            */
241
    /* -------------------------------------------------------------------- */
242
    GByte abySigningKeyIn[CPL_SHA256_HASH_SIZE] = {};
352✔
243
    GByte abySigningKeyOut[CPL_SHA256_HASH_SIZE] = {};
352✔
244

245
    std::string osFirstKey(std::string("AWS4") + osSecretAccessKey);
1,056✔
246
    CPL_HMAC_SHA256(osFirstKey.c_str(), osFirstKey.size(), osYYMMDD.c_str(),
352✔
247
                    osYYMMDD.size(), abySigningKeyOut);
248
    memcpy(abySigningKeyIn, abySigningKeyOut, CPL_SHA256_HASH_SIZE);
352✔
249

250
    CPL_HMAC_SHA256(abySigningKeyIn, CPL_SHA256_HASH_SIZE, osRegion.c_str(),
352✔
251
                    osRegion.size(), abySigningKeyOut);
252
    memcpy(abySigningKeyIn, abySigningKeyOut, CPL_SHA256_HASH_SIZE);
352✔
253

254
    CPL_HMAC_SHA256(abySigningKeyIn, CPL_SHA256_HASH_SIZE, osService.c_str(),
352✔
255
                    osService.size(), abySigningKeyOut);
256
    memcpy(abySigningKeyIn, abySigningKeyOut, CPL_SHA256_HASH_SIZE);
352✔
257

258
    CPL_HMAC_SHA256(abySigningKeyIn, CPL_SHA256_HASH_SIZE, "aws4_request",
352✔
259
                    strlen("aws4_request"), abySigningKeyOut);
260
    memcpy(abySigningKeyIn, abySigningKeyOut, CPL_SHA256_HASH_SIZE);
352✔
261

262
#ifdef DEBUG_VERBOSE
263
    std::string osSigningKey(
264
        CPLGetLowerCaseHex(abySigningKeyIn, CPL_SHA256_HASH_SIZE));
265
    CPLDebug("S3", "osSigningKey='%s'", osSigningKey.c_str());
266
#endif
267

268
    /* -------------------------------------------------------------------- */
269
    /*      Compute signature.                                              */
270
    /* -------------------------------------------------------------------- */
271
    GByte abySignature[CPL_SHA256_HASH_SIZE] = {};
352✔
272
    CPL_HMAC_SHA256(abySigningKeyIn, CPL_SHA256_HASH_SIZE,
704✔
273
                    osStringToSign.c_str(), osStringToSign.size(),
352✔
274
                    abySignature);
275
    std::string osSignature(
276
        CPLGetLowerCaseHex(abySignature, CPL_SHA256_HASH_SIZE));
352✔
277

278
#ifdef DEBUG_VERBOSE
279
    CPLDebug("S3", "osSignature='%s'", osSignature.c_str());
280
#endif
281

282
    return osSignature;
704✔
283
}
284

285
/************************************************************************/
286
/*                CPLGetAWS_SIGN4_Authorization()                       */
287
/************************************************************************/
288

289
std::string CPLGetAWS_SIGN4_Authorization(
347✔
290
    const std::string &osSecretAccessKey, const std::string &osAccessKeyId,
291
    const std::string &osAccessToken, const std::string &osRegion,
292
    const std::string &osRequestPayer, const std::string &osService,
293
    const std::string &osVerb, const struct curl_slist *psExistingHeaders,
294
    const std::string &osHost, const std::string &osCanonicalURI,
295
    const std::string &osCanonicalQueryString,
296
    const std::string &osXAMZContentSHA256, bool bAddHeaderAMZContentSHA256,
297
    const std::string &osTimestamp)
298
{
299
    std::string osSignedHeaders;
694✔
300
    std::string osSignature(CPLGetAWS_SIGN4_Signature(
301
        osSecretAccessKey, osAccessToken, osRegion, osRequestPayer, osService,
302
        osVerb, psExistingHeaders, osHost, osCanonicalURI,
303
        osCanonicalQueryString, osXAMZContentSHA256, bAddHeaderAMZContentSHA256,
304
        osTimestamp, osSignedHeaders));
694✔
305

306
    std::string osYYMMDD(osTimestamp);
694✔
307
    osYYMMDD.resize(8);
347✔
308

309
    /* -------------------------------------------------------------------- */
310
    /*      Build authorization header.                                     */
311
    /* -------------------------------------------------------------------- */
312
    std::string osAuthorization;
347✔
313
    osAuthorization = "AWS4-HMAC-SHA256 Credential=";
347✔
314
    osAuthorization += osAccessKeyId;
347✔
315
    osAuthorization += "/";
347✔
316
    osAuthorization += osYYMMDD;
347✔
317
    osAuthorization += "/";
347✔
318
    osAuthorization += osRegion;
347✔
319
    osAuthorization += "/";
347✔
320
    osAuthorization += osService;
347✔
321
    osAuthorization += "/";
347✔
322
    osAuthorization += "aws4_request";
347✔
323
    osAuthorization += ",";
347✔
324
    osAuthorization += "SignedHeaders=";
347✔
325
    osAuthorization += osSignedHeaders;
347✔
326
    osAuthorization += ",";
347✔
327
    osAuthorization += "Signature=";
347✔
328
    osAuthorization += osSignature;
347✔
329

330
#ifdef DEBUG_VERBOSE
331
    CPLDebug("S3", "osAuthorization='%s'", osAuthorization.c_str());
332
#endif
333

334
    return osAuthorization;
694✔
335
}
336

337
/************************************************************************/
338
/*                        CPLGetAWS_SIGN4_Timestamp()                   */
339
/************************************************************************/
340

341
std::string CPLGetAWS_SIGN4_Timestamp(GIntBig timestamp)
4✔
342
{
343
    struct tm brokenDown;
344
    CPLUnixTimeToYMDHMS(timestamp, &brokenDown);
4✔
345

346
    char szTimeStamp[80] = {};
4✔
347
    snprintf(szTimeStamp, sizeof(szTimeStamp), "%04d%02d%02dT%02d%02d%02dZ",
4✔
348
             brokenDown.tm_year + 1900, brokenDown.tm_mon + 1,
4✔
349
             brokenDown.tm_mday, brokenDown.tm_hour, brokenDown.tm_min,
350
             brokenDown.tm_sec);
351
    return szTimeStamp;
4✔
352
}
353

354
/************************************************************************/
355
/*                         VSIS3HandleHelper()                          */
356
/************************************************************************/
357
VSIS3HandleHelper::VSIS3HandleHelper(
462✔
358
    const std::string &osSecretAccessKey, const std::string &osAccessKeyId,
359
    const std::string &osSessionToken, const std::string &osEndpoint,
360
    const std::string &osRegion, const std::string &osRequestPayer,
361
    const std::string &osBucket, const std::string &osObjectKey, bool bUseHTTPS,
362
    bool bUseVirtualHosting, AWSCredentialsSource eCredentialsSource)
462✔
363
    : m_osURL(BuildURL(osEndpoint, osBucket, osObjectKey, bUseHTTPS,
364
                       bUseVirtualHosting)),
365
      m_osSecretAccessKey(osSecretAccessKey), m_osAccessKeyId(osAccessKeyId),
366
      m_osSessionToken(osSessionToken), m_osEndpoint(osEndpoint),
367
      m_osRegion(osRegion), m_osRequestPayer(osRequestPayer),
368
      m_osBucket(osBucket), m_osObjectKey(osObjectKey), m_bUseHTTPS(bUseHTTPS),
369
      m_bUseVirtualHosting(bUseVirtualHosting),
370
      m_eCredentialsSource(eCredentialsSource)
462✔
371
{
372
    VSIS3UpdateParams::UpdateHandleFromMap(this);
462✔
373
}
462✔
374

375
/************************************************************************/
376
/*                        ~VSIS3HandleHelper()                          */
377
/************************************************************************/
378

379
VSIS3HandleHelper::~VSIS3HandleHelper()
924✔
380
{
381
    for (size_t i = 0; i < m_osSecretAccessKey.size(); i++)
9,598✔
382
        m_osSecretAccessKey[i] = 0;
9,136✔
383
}
924✔
384

385
/************************************************************************/
386
/*                           BuildURL()                                 */
387
/************************************************************************/
388

389
std::string VSIS3HandleHelper::BuildURL(const std::string &osEndpoint,
1,102✔
390
                                        const std::string &osBucket,
391
                                        const std::string &osObjectKey,
392
                                        bool bUseHTTPS, bool bUseVirtualHosting)
393
{
394
    const char *pszProtocol = (bUseHTTPS) ? "https" : "http";
1,102✔
395
    if (osBucket.empty())
1,102✔
396
        return CPLSPrintf("%s://%s", pszProtocol, osEndpoint.c_str());
9✔
397
    else if (bUseVirtualHosting)
1,093✔
398
        return CPLSPrintf("%s://%s.%s/%s", pszProtocol, osBucket.c_str(),
399
                          osEndpoint.c_str(),
400
                          CPLAWSURLEncode(osObjectKey, false).c_str());
36✔
401
    else
402
        return CPLSPrintf("%s://%s/%s/%s", pszProtocol, osEndpoint.c_str(),
403
                          osBucket.c_str(),
404
                          CPLAWSURLEncode(osObjectKey, false).c_str());
2,151✔
405
}
406

407
/************************************************************************/
408
/*                           RebuildURL()                               */
409
/************************************************************************/
410

411
void VSIS3HandleHelper::RebuildURL()
640✔
412
{
413
    m_osURL = BuildURL(m_osEndpoint, m_osBucket, m_osObjectKey, m_bUseHTTPS,
640✔
414
                       m_bUseVirtualHosting);
640✔
415
    m_osURL += GetQueryString(false);
640✔
416
}
640✔
417

418
/************************************************************************/
419
/*                        GetBucketAndObjectKey()                       */
420
/************************************************************************/
421

422
bool IVSIS3LikeHandleHelper::GetBucketAndObjectKey(const char *pszURI,
543✔
423
                                                   const char *pszFSPrefix,
424
                                                   bool bAllowNoObject,
425
                                                   std::string &osBucket,
426
                                                   std::string &osObjectKey)
427
{
428
    osBucket = pszURI;
543✔
429
    if (osBucket.empty())
543✔
430
    {
431
        return false;
×
432
    }
433
    size_t nPos = osBucket.find('/');
543✔
434
    if (nPos == std::string::npos)
543✔
435
    {
436
        if (bAllowNoObject)
107✔
437
        {
438
            osObjectKey = "";
105✔
439
            return true;
105✔
440
        }
441
        CPLError(CE_Failure, CPLE_AppDefined,
2✔
442
                 "Filename should be of the form %sbucket/key", pszFSPrefix);
443
        return false;
2✔
444
    }
445
    osBucket.resize(nPos);
436✔
446
    osObjectKey = pszURI + nPos + 1;
436✔
447
    return true;
436✔
448
}
449

450
/************************************************************************/
451
/*                      BuildCanonicalizedHeaders()                    */
452
/************************************************************************/
453

454
std::string IVSIS3LikeHandleHelper::BuildCanonicalizedHeaders(
667✔
455
    std::map<std::string, std::string> &oSortedMapHeaders,
456
    const struct curl_slist *psExistingHeaders, const char *pszHeaderPrefix)
457
{
458
    const struct curl_slist *psIter = psExistingHeaders;
667✔
459
    for (; psIter != nullptr; psIter = psIter->next)
1,043✔
460
    {
461
        if (STARTS_WITH_CI(psIter->data, pszHeaderPrefix) ||
376✔
462
            STARTS_WITH_CI(psIter->data, "Content-MD5"))
332✔
463
        {
464
            const char *pszColumn = strstr(psIter->data, ":");
50✔
465
            if (pszColumn)
50✔
466
            {
467
                CPLString osKey(psIter->data);
50✔
468
                osKey.resize(pszColumn - psIter->data);
50✔
469
                oSortedMapHeaders[osKey.tolower()] =
50✔
470
                    CPLString(pszColumn + strlen(":")).Trim();
100✔
471
            }
472
        }
473
    }
474

475
    std::string osCanonicalizedHeaders;
667✔
476
    std::map<std::string, std::string>::const_iterator oIter =
477
        oSortedMapHeaders.begin();
667✔
478
    for (; oIter != oSortedMapHeaders.end(); ++oIter)
2,189✔
479
    {
480
        osCanonicalizedHeaders += oIter->first + ":" + oIter->second + "\n";
1,522✔
481
    }
482
    return osCanonicalizedHeaders;
1,334✔
483
}
484

485
/************************************************************************/
486
/*                         GetRFC822DateTime()                          */
487
/************************************************************************/
488

489
std::string IVSIS3LikeHandleHelper::GetRFC822DateTime()
30✔
490
{
491
    char szDate[64];
492
    time_t nNow = time(nullptr);
30✔
493
    struct tm tm;
494
    CPLUnixTimeToYMDHMS(nNow, &tm);
30✔
495
    int nRet = CPLPrintTime(szDate, sizeof(szDate) - 1,
30✔
496
                            "%a, %d %b %Y %H:%M:%S GMT", &tm, "C");
497
    szDate[nRet] = 0;
30✔
498
    return szDate;
30✔
499
}
500

501
/************************************************************************/
502
/*                        Iso8601ToUnixTime()                           */
503
/************************************************************************/
504

505
static bool Iso8601ToUnixTime(const char *pszDT, GIntBig *pnUnixTime)
16✔
506
{
507
    int nYear;
508
    int nMonth;
509
    int nDay;
510
    int nHour;
511
    int nMinute;
512
    int nSecond;
513
    if (sscanf(pszDT, "%04d-%02d-%02dT%02d:%02d:%02d", &nYear, &nMonth, &nDay,
16✔
514
               &nHour, &nMinute, &nSecond) == 6)
16✔
515
    {
516
        struct tm brokendowntime;
517
        brokendowntime.tm_year = nYear - 1900;
16✔
518
        brokendowntime.tm_mon = nMonth - 1;
16✔
519
        brokendowntime.tm_mday = nDay;
16✔
520
        brokendowntime.tm_hour = nHour;
16✔
521
        brokendowntime.tm_min = nMinute;
16✔
522
        brokendowntime.tm_sec = nSecond;
16✔
523
        *pnUnixTime = CPLYMDHMSToUnixTime(&brokendowntime);
16✔
524
        return true;
16✔
525
    }
526
    return false;
×
527
}
528

529
/************************************************************************/
530
/*                  IsMachinePotentiallyEC2Instance()                   */
531
/************************************************************************/
532

533
enum class EC2InstanceCertainty
534
{
535
    YES,
536
    NO,
537
    MAYBE
538
};
539

540
static EC2InstanceCertainty IsMachinePotentiallyEC2Instance()
13✔
541
{
542
#if defined(__linux) || defined(_WIN32)
543
    const auto IsMachinePotentiallyEC2InstanceFromLinuxHost = []()
7✔
544
    {
545
        // On the newer Nitro Hypervisor (C5, M5, H1, T3), use
546
        // /sys/devices/virtual/dmi/id/sys_vendor = 'Amazon EC2' instead.
547

548
        // On older Xen hypervisor EC2 instances, a /sys/hypervisor/uuid file
549
        // will exist with a string beginning with 'ec2'.
550

551
        // If the files exist but don't contain the correct content, then we're
552
        // not EC2 and do not attempt any network access
553

554
        // Check for Xen Hypervisor instances
555
        // This file doesn't exist on Nitro instances
556
        VSILFILE *fp = VSIFOpenL("/sys/hypervisor/uuid", "rb");
7✔
557
        if (fp != nullptr)
7✔
558
        {
559
            char uuid[36 + 1] = {0};
×
560
            VSIFReadL(uuid, 1, sizeof(uuid) - 1, fp);
×
561
            VSIFCloseL(fp);
×
562
            return EQUALN(uuid, "ec2", 3) ? EC2InstanceCertainty::YES
×
563
                                          : EC2InstanceCertainty::NO;
×
564
        }
565

566
        // Check for Nitro Hypervisor instances
567
        // This file may exist on Xen instances with a value of 'Xen'
568
        // (but that doesn't mean we're on EC2)
569
        fp = VSIFOpenL("/sys/devices/virtual/dmi/id/sys_vendor", "rb");
7✔
570
        if (fp != nullptr)
7✔
571
        {
572
            char buf[10 + 1] = {0};
7✔
573
            VSIFReadL(buf, 1, sizeof(buf) - 1, fp);
7✔
574
            VSIFCloseL(fp);
7✔
575
            return EQUALN(buf, "Amazon EC2", 10) ? EC2InstanceCertainty::YES
7✔
576
                                                 : EC2InstanceCertainty::NO;
7✔
577
        }
578

579
        // Fallback: Check via the network
580
        return EC2InstanceCertainty::MAYBE;
×
581
    };
582
#endif
583

584
#ifdef __linux
585
    // Optimization on Linux to avoid the network request
586
    // See
587
    // http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/identify_ec2_instances.html
588
    // Skip if either:
589
    // - CPL_AWS_AUTODETECT_EC2=NO
590
    // - CPL_AWS_CHECK_HYPERVISOR_UUID=NO (deprecated)
591

592
    if (!CPLTestBool(CPLGetConfigOption("CPL_AWS_AUTODETECT_EC2", "YES")))
13✔
593
    {
594
        return EC2InstanceCertainty::MAYBE;
6✔
595
    }
596
    else
597
    {
598
        const char *opt =
599
            CPLGetConfigOption("CPL_AWS_CHECK_HYPERVISOR_UUID", "");
7✔
600
        if (opt[0])
7✔
601
        {
602
            CPLDebug("AWS", "CPL_AWS_CHECK_HYPERVISOR_UUID is deprecated. Use "
×
603
                            "CPL_AWS_AUTODETECT_EC2 instead");
604
            if (!CPLTestBool(opt))
×
605
            {
606
                return EC2InstanceCertainty::MAYBE;
×
607
            }
608
        }
609
    }
610

611
    return IsMachinePotentiallyEC2InstanceFromLinuxHost();
7✔
612

613
#elif defined(_WIN32)
614
    if (!CPLTestBool(CPLGetConfigOption("CPL_AWS_AUTODETECT_EC2", "YES")))
615
    {
616
        return EC2InstanceCertainty::MAYBE;
617
    }
618

619
    // Regular UUID is not valid for WINE, fetch from sysfs instead.
620
    if (CPLGetWineVersion() != nullptr)
621
    {
622
        return IsMachinePotentiallyEC2InstanceFromLinuxHost();
623
    }
624
    else
625
    {
626
#if defined(HAVE_ATLBASE_H)
627
        std::string osMachineUUID;
628
        if (CPLFetchWindowsProductUUID(osMachineUUID))
629
        {
630
            if (osMachineUUID.length() >= 3 &&
631
                EQUALN(osMachineUUID.c_str(), "EC2", 3))
632
            {
633
                return EC2InstanceCertainty::YES;
634
            }
635
            else if (osMachineUUID.length() >= 8 && osMachineUUID[4] == '2' &&
636
                     osMachineUUID[6] == 'E' && osMachineUUID[7] == 'C')
637
            {
638
                return EC2InstanceCertainty::YES;
639
            }
640
            else
641
            {
642
                return EC2InstanceCertainty::NO;
643
            }
644
        }
645
#endif
646
    }
647

648
    // Fallback: Check via the network
649
    return EC2InstanceCertainty::MAYBE;
650
#else
651
    // At time of writing EC2 instances can be only Linux or Windows
652
    return EC2InstanceCertainty::NO;
653
#endif
654
}
655

656
/************************************************************************/
657
/*                   ReadAWSTokenFile()                                 */
658
/************************************************************************/
659

660
static bool ReadAWSTokenFile(const std::string &osAWSTokenFile,
5✔
661
                             std::string &awsToken)
662
{
663
    GByte *pabyOut = nullptr;
5✔
664
    if (!VSIIngestFile(nullptr, osAWSTokenFile.c_str(), &pabyOut, nullptr, -1))
5✔
665
        return false;
×
666

667
    awsToken = reinterpret_cast<char *>(pabyOut);
5✔
668
    VSIFree(pabyOut);
5✔
669
    // Remove trailing end-of-line character
670
    if (!awsToken.empty() && awsToken.back() == '\n')
5✔
671
        awsToken.pop_back();
4✔
672
    return !awsToken.empty();
5✔
673
}
674

675
/************************************************************************/
676
/*          GetConfigurationFromAssumeRoleWithWebIdentity()             */
677
/************************************************************************/
678

679
bool VSIS3HandleHelper::GetConfigurationFromAssumeRoleWithWebIdentity(
16✔
680
    bool bForceRefresh, const std::string &osPathForOption,
681
    const std::string &osRoleArnIn, const std::string &osWebIdentityTokenFileIn,
682
    std::string &osSecretAccessKey, std::string &osAccessKeyId,
683
    std::string &osSessionToken)
684
{
685
    CPLMutexHolder oHolder(&ghMutex);
32✔
686
    if (!bForceRefresh)
16✔
687
    {
688
        time_t nCurTime;
689
        time(&nCurTime);
16✔
690
        // Try to reuse credentials if they are still valid, but
691
        // keep one minute of margin...
692
        if (!gosGlobalAccessKeyId.empty() && nCurTime < gnGlobalExpiration - 60)
16✔
693
        {
694
            osAccessKeyId = gosGlobalAccessKeyId;
1✔
695
            osSecretAccessKey = gosGlobalSecretAccessKey;
1✔
696
            osSessionToken = gosGlobalSessionToken;
1✔
697
            return true;
1✔
698
        }
699
    }
700

701
    const std::string roleArn =
702
        !osRoleArnIn.empty() ? osRoleArnIn
15✔
703
                             : VSIGetPathSpecificOption(osPathForOption.c_str(),
704
                                                        "AWS_ROLE_ARN", "");
30✔
705
    if (roleArn.empty())
15✔
706
    {
707
        CPLDebug("AWS", "AWS_ROLE_ARN configuration option not defined");
11✔
708
        return false;
11✔
709
    }
710

711
    const std::string webIdentityTokenFile =
712
        !osWebIdentityTokenFileIn.empty()
4✔
713
            ? osWebIdentityTokenFileIn
714
            : VSIGetPathSpecificOption(osPathForOption.c_str(),
715
                                       "AWS_WEB_IDENTITY_TOKEN_FILE", "");
8✔
716
    if (webIdentityTokenFile.empty())
4✔
717
    {
718
        CPLDebug(
×
719
            "AWS",
720
            "AWS_WEB_IDENTITY_TOKEN_FILE configuration option not defined");
721
        return false;
×
722
    }
723

724
    const std::string stsRegionalEndpoints = VSIGetPathSpecificOption(
725
        osPathForOption.c_str(), "AWS_STS_REGIONAL_ENDPOINTS", "regional");
8✔
726

727
    std::string osStsDefaultUrl;
8✔
728
    if (stsRegionalEndpoints == "regional")
4✔
729
    {
730
        const std::string osRegion = VSIGetPathSpecificOption(
731
            osPathForOption.c_str(), "AWS_REGION", "us-east-1");
4✔
732
        osStsDefaultUrl = "https://sts." + osRegion + ".amazonaws.com";
4✔
733
    }
734
    else
735
    {
736
        osStsDefaultUrl = "https://sts.amazonaws.com";
×
737
    }
738
    const std::string osStsRootUrl(VSIGetPathSpecificOption(
739
        osPathForOption.c_str(), "CPL_AWS_STS_ROOT_URL",
740
        osStsDefaultUrl.c_str()));
8✔
741

742
    // Get token from web identity token file
743
    std::string webIdentityToken;
8✔
744
    if (!ReadAWSTokenFile(webIdentityTokenFile, webIdentityToken))
4✔
745
    {
746
        CPLDebug("AWS", "%s is empty", webIdentityTokenFile.c_str());
×
747
        return false;
×
748
    }
749

750
    // Get credentials from sts AssumeRoleWithWebIdentity
751
    std::string osExpiration;
4✔
752
    {
753
        const std::string osSTS_asuume_role_with_web_identity_URL =
754
            osStsRootUrl +
8✔
755
            "/?Action=AssumeRoleWithWebIdentity&RoleSessionName=gdal"
756
            "&Version=2011-06-15&RoleArn=" +
8✔
757
            CPLAWSURLEncode(roleArn) +
16✔
758
            "&WebIdentityToken=" + CPLAWSURLEncode(webIdentityToken);
12✔
759

760
        CPLPushErrorHandler(CPLQuietErrorHandler);
4✔
761

762
        CPLHTTPResult *psResult = CPLHTTPFetch(
4✔
763
            osSTS_asuume_role_with_web_identity_URL.c_str(), nullptr);
764
        CPLPopErrorHandler();
4✔
765
        if (psResult)
4✔
766
        {
767
            if (psResult->nStatus == 0 && psResult->pabyData != nullptr)
4✔
768
            {
769
                CPLXMLTreeCloser oTree(CPLParseXMLString(
770
                    reinterpret_cast<char *>(psResult->pabyData)));
8✔
771
                if (oTree)
4✔
772
                {
773
                    const auto psCredentials = CPLGetXMLNode(
4✔
774
                        oTree.get(),
775
                        "=AssumeRoleWithWebIdentityResponse."
776
                        "AssumeRoleWithWebIdentityResult.Credentials");
777
                    if (psCredentials)
4✔
778
                    {
779
                        osAccessKeyId =
780
                            CPLGetXMLValue(psCredentials, "AccessKeyId", "");
3✔
781
                        osSecretAccessKey = CPLGetXMLValue(
782
                            psCredentials, "SecretAccessKey", "");
3✔
783
                        osSessionToken =
784
                            CPLGetXMLValue(psCredentials, "SessionToken", "");
3✔
785
                        osExpiration =
786
                            CPLGetXMLValue(psCredentials, "Expiration", "");
3✔
787
                    }
788
                }
789
            }
790
            CPLHTTPDestroyResult(psResult);
4✔
791
        }
792
    }
793

794
    GIntBig nExpirationUnix = 0;
4✔
795
    if (!osAccessKeyId.empty() && !osSecretAccessKey.empty() &&
7✔
796
        !osSessionToken.empty() &&
10✔
797
        Iso8601ToUnixTime(osExpiration.c_str(), &nExpirationUnix))
3✔
798
    {
799
        gosGlobalAccessKeyId = osAccessKeyId;
3✔
800
        gosGlobalSecretAccessKey = osSecretAccessKey;
3✔
801
        gosGlobalSessionToken = osSessionToken;
3✔
802
        gnGlobalExpiration = nExpirationUnix;
3✔
803
        CPLDebug("AWS", "Storing AIM credentials until %s",
3✔
804
                 osExpiration.c_str());
805
    }
806
    return !osAccessKeyId.empty() && !osSecretAccessKey.empty() &&
7✔
807
           !osSessionToken.empty();
7✔
808
}
809

810
/************************************************************************/
811
/*                      GetConfigurationFromEC2()                       */
812
/************************************************************************/
813

814
bool VSIS3HandleHelper::GetConfigurationFromEC2(
25✔
815
    bool bForceRefresh, const std::string &osPathForOption,
816
    std::string &osSecretAccessKey, std::string &osAccessKeyId,
817
    std::string &osSessionToken)
818
{
819
    CPLMutexHolder oHolder(&ghMutex);
50✔
820
    if (!bForceRefresh)
25✔
821
    {
822
        time_t nCurTime;
823
        time(&nCurTime);
24✔
824
        // Try to reuse credentials if they are still valid, but
825
        // keep one minute of margin...
826
        if (!gosGlobalAccessKeyId.empty() && nCurTime < gnGlobalExpiration - 60)
24✔
827
        {
828
            osAccessKeyId = gosGlobalAccessKeyId;
9✔
829
            osSecretAccessKey = gosGlobalSecretAccessKey;
9✔
830
            osSessionToken = gosGlobalSessionToken;
9✔
831
            return true;
9✔
832
        }
833
    }
834

835
    std::string osURLRefreshCredentials;
32✔
836
    const std::string osEC2DefaultURL("http://169.254.169.254");
32✔
837
    // coverity[tainted_data]
838
    const std::string osEC2RootURL(VSIGetPathSpecificOption(
839
        osPathForOption.c_str(), "CPL_AWS_EC2_API_ROOT_URL",
840
        osEC2DefaultURL.c_str()));
32✔
841
    // coverity[tainted_data]
842
    const std::string osECSFullURI(VSIGetPathSpecificOption(
843
        osPathForOption.c_str(), "AWS_CONTAINER_CREDENTIALS_FULL_URI", ""));
32✔
844
    // coverity[tainted_data]
845
    const std::string osECSRelativeURI(
846
        osECSFullURI.empty() ? VSIGetPathSpecificOption(
16✔
847
                                   osPathForOption.c_str(),
848
                                   "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI", "")
849
                             : std::string());
32✔
850
    // coverity[tainted_data]
851
    const std::string osECSTokenFile(
852
        (osECSFullURI.empty() && osECSRelativeURI.empty())
13✔
853
            ? std::string()
16✔
854
            : VSIGetPathSpecificOption(osPathForOption.c_str(),
855
                                       "AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE",
856
                                       ""));
48✔
857

858
    // coverity[tainted_data]
859
    const std::string osECSTokenValue(
860
        (osECSFullURI.empty() && osECSRelativeURI.empty() &&
13✔
861
         !osECSTokenFile.empty())
13✔
862
            ? std::string()
16✔
863
            : VSIGetPathSpecificOption(osPathForOption.c_str(),
864
                                       "AWS_CONTAINER_AUTHORIZATION_TOKEN",
865
                                       ""));
48✔
866

867
    std::string osECSToken;
32✔
868
    if (!osECSTokenFile.empty())
16✔
869
    {
870
        if (!ReadAWSTokenFile(osECSTokenFile, osECSToken))
1✔
871
        {
872
            CPLDebug("AWS", "%s is empty", osECSTokenFile.c_str());
×
873
        }
874
    }
875
    else if (!osECSTokenValue.empty())
15✔
876
    {
877
        osECSToken = osECSTokenValue;
1✔
878
    }
879

880
    std::string osToken;
32✔
881
    if (!osECSFullURI.empty())
16✔
882
    {
883
        // Cf https://docs.aws.amazon.com/sdkref/latest/guide/feature-container-credentials.html
884
        osURLRefreshCredentials = osECSFullURI;
3✔
885
    }
886
    else if (osEC2RootURL == osEC2DefaultURL && !osECSRelativeURI.empty())
13✔
887
    {
888
        // See
889
        // https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html
890
        osURLRefreshCredentials = "http://169.254.170.2" + osECSRelativeURI;
×
891
    }
892
    else
893
    {
894
        const auto eIsEC2 = IsMachinePotentiallyEC2Instance();
13✔
895
        if (eIsEC2 == EC2InstanceCertainty::NO)
13✔
896
            return false;
7✔
897

898
        // Use IMDSv2 protocol:
899
        // https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-instance-metadata-service.html
900

901
        // Retrieve IMDSv2 token
902
        {
903
            const std::string osEC2_IMDSv2_api_token_URL =
904
                osEC2RootURL + "/latest/api/token";
12✔
905
            CPLStringList aosOptions;
12✔
906
            aosOptions.SetNameValue("TIMEOUT", "1");
6✔
907
            aosOptions.SetNameValue("CUSTOMREQUEST", "PUT");
6✔
908
            aosOptions.SetNameValue("HEADERS",
909
                                    "X-aws-ec2-metadata-token-ttl-seconds: 10");
6✔
910
            CPLPushErrorHandler(CPLQuietErrorHandler);
6✔
911
            CPLHTTPResult *psResult = CPLHTTPFetch(
6✔
912
                osEC2_IMDSv2_api_token_URL.c_str(), aosOptions.List());
6✔
913
            CPLPopErrorHandler();
6✔
914
            if (psResult)
6✔
915
            {
916
                if (psResult->nStatus == 0 && psResult->pabyData != nullptr)
6✔
917
                {
918
                    osToken = reinterpret_cast<char *>(psResult->pabyData);
4✔
919
                }
920
                else
921
                {
922
                    // Failure: either we are not running on EC2 (or something
923
                    // emulating it) or this doesn't implement yet IMDSv2.
924
                    // Fallback to IMDSv1
925

926
                    // /latest/api/token doesn't work inside a Docker container
927
                    // that has no host networking. Cf
928
                    // https://community.grafana.com/t/imdsv2-is-not-working-from-docker/65944
929
                    if (psResult->pszErrBuf != nullptr &&
2✔
930
                        strstr(psResult->pszErrBuf,
2✔
931
                               "Operation timed out after") != nullptr)
932
                    {
933
                        aosOptions.Clear();
×
934
                        aosOptions.SetNameValue("TIMEOUT", "1");
×
935
                        CPLPushErrorHandler(CPLQuietErrorHandler);
×
936
                        CPLHTTPResult *psResult2 = CPLHTTPFetch(
×
937
                            (osEC2RootURL + "/latest/meta-data").c_str(),
×
938
                            aosOptions.List());
×
939
                        CPLPopErrorHandler();
×
940
                        if (psResult2)
×
941
                        {
942
                            if (psResult2->nStatus == 0 &&
×
943
                                psResult2->pabyData != nullptr)
×
944
                            {
945
                                CPLDebug("AWS",
×
946
                                         "/latest/api/token EC2 IMDSv2 request "
947
                                         "timed out, but /latest/metadata "
948
                                         "succeeded. "
949
                                         "Trying with IMDSv1. "
950
                                         "Consult "
951
                                         "https://gdal.org/user/"
952
                                         "virtual_file_systems.html#vsis3_imds "
953
                                         "for IMDS related issues.");
954
                            }
955
                            CPLHTTPDestroyResult(psResult2);
×
956
                        }
957
                    }
958
                }
959
                CPLHTTPDestroyResult(psResult);
6✔
960
            }
961
            CPLErrorReset();
6✔
962
        }
963

964
        // If we don't know yet the IAM role, fetch it
965
        const std::string osEC2CredentialsURL =
966
            osEC2RootURL + "/latest/meta-data/iam/security-credentials/";
6✔
967
        if (gosIAMRole.empty())
6✔
968
        {
969
            CPLStringList aosOptions;
3✔
970
            aosOptions.SetNameValue("TIMEOUT", "1");
3✔
971
            if (!osToken.empty())
3✔
972
            {
973
                aosOptions.SetNameValue(
974
                    "HEADERS",
975
                    ("X-aws-ec2-metadata-token: " + osToken).c_str());
2✔
976
            }
977
            CPLPushErrorHandler(CPLQuietErrorHandler);
3✔
978
            CPLHTTPResult *psResult =
979
                CPLHTTPFetch(osEC2CredentialsURL.c_str(), aosOptions.List());
3✔
980
            CPLPopErrorHandler();
3✔
981
            if (psResult)
3✔
982
            {
983
                if (psResult->nStatus == 0 && psResult->pabyData != nullptr)
3✔
984
                {
985
                    gosIAMRole = reinterpret_cast<char *>(psResult->pabyData);
3✔
986
                }
987
                CPLHTTPDestroyResult(psResult);
3✔
988
            }
989
            CPLErrorReset();
3✔
990
            if (gosIAMRole.empty())
3✔
991
            {
992
                // We didn't get the IAM role. We are definitely not running
993
                // on (a correctly configured) EC2 or an emulation of it.
994

995
                if (eIsEC2 == EC2InstanceCertainty::YES)
×
996
                {
997
                    CPLError(CE_Failure, CPLE_AppDefined,
×
998
                             "EC2 IMDSv2 and IMDSv1 requests failed. Consult "
999
                             "https://gdal.org/user/"
1000
                             "virtual_file_systems.html#vsis3_imds "
1001
                             "for IMDS related issues.");
1002
                }
1003

1004
                return false;
×
1005
            }
1006
        }
1007
        osURLRefreshCredentials = osEC2CredentialsURL + gosIAMRole;
6✔
1008
    }
1009

1010
    // Now fetch the refreshed credentials
1011
    CPLStringList oResponse;
18✔
1012
    CPLStringList aosOptions;
18✔
1013
    if (!osToken.empty())
9✔
1014
    {
1015
        aosOptions.SetNameValue(
1016
            "HEADERS", ("X-aws-ec2-metadata-token: " + osToken).c_str());
4✔
1017
    }
1018
    else if (!osECSToken.empty())
5✔
1019
    {
1020
        aosOptions.SetNameValue("HEADERS",
1021
                                ("Authorization: " + osECSToken).c_str());
2✔
1022
    }
1023
    CPLHTTPResult *psResult =
1024
        CPLHTTPFetch(osURLRefreshCredentials.c_str(), aosOptions.List());
9✔
1025
    if (psResult)
9✔
1026
    {
1027
        if (psResult->nStatus == 0 && psResult->pabyData != nullptr)
9✔
1028
        {
1029
            const std::string osJSon =
1030
                reinterpret_cast<char *>(psResult->pabyData);
8✔
1031
            oResponse = CPLParseKeyValueJson(osJSon.c_str());
8✔
1032
        }
1033
        CPLHTTPDestroyResult(psResult);
9✔
1034
    }
1035
    CPLErrorReset();
9✔
1036
    osAccessKeyId = oResponse.FetchNameValueDef("AccessKeyId", "");
9✔
1037
    osSecretAccessKey = oResponse.FetchNameValueDef("SecretAccessKey", "");
9✔
1038
    osSessionToken = oResponse.FetchNameValueDef("Token", "");
9✔
1039
    const std::string osExpiration =
1040
        oResponse.FetchNameValueDef("Expiration", "");
9✔
1041
    GIntBig nExpirationUnix = 0;
9✔
1042
    if (!osAccessKeyId.empty() && !osSecretAccessKey.empty() &&
17✔
1043
        Iso8601ToUnixTime(osExpiration.c_str(), &nExpirationUnix))
8✔
1044
    {
1045
        gosGlobalAccessKeyId = osAccessKeyId;
8✔
1046
        gosGlobalSecretAccessKey = osSecretAccessKey;
8✔
1047
        gosGlobalSessionToken = osSessionToken;
8✔
1048
        gnGlobalExpiration = nExpirationUnix;
8✔
1049
        CPLDebug("AWS", "Storing AIM credentials until %s",
8✔
1050
                 osExpiration.c_str());
1051
    }
1052
    return !osAccessKeyId.empty() && !osSecretAccessKey.empty();
9✔
1053
}
1054

1055
/************************************************************************/
1056
/*                      UpdateAndWarnIfInconsistent()                   */
1057
/************************************************************************/
1058

1059
static void UpdateAndWarnIfInconsistent(const char *pszKeyword,
6✔
1060
                                        std::string &osVal,
1061
                                        const std::string &osNewVal,
1062
                                        const std::string &osCredentials,
1063
                                        const std::string &osConfig)
1064
{
1065
    // nominally defined in ~/.aws/credentials but can
1066
    // be set here too. If both values exist, credentials
1067
    // has the priority
1068
    if (osVal.empty())
6✔
1069
    {
1070
        osVal = osNewVal;
2✔
1071
    }
1072
    else if (osVal != osNewVal)
4✔
1073
    {
1074
        CPLError(CE_Warning, CPLE_AppDefined,
2✔
1075
                 "%s defined in both %s "
1076
                 "and %s. The one of %s will be used",
1077
                 pszKeyword, osCredentials.c_str(), osConfig.c_str(),
1078
                 osCredentials.c_str());
1079
    }
1080
}
6✔
1081

1082
/************************************************************************/
1083
/*                         ReadAWSCredentials()                         */
1084
/************************************************************************/
1085

1086
static bool ReadAWSCredentials(const std::string &osProfile,
28✔
1087
                               const std::string &osCredentials,
1088
                               std::string &osSecretAccessKey,
1089
                               std::string &osAccessKeyId,
1090
                               std::string &osSessionToken)
1091
{
1092
    osSecretAccessKey.clear();
28✔
1093
    osAccessKeyId.clear();
28✔
1094
    osSessionToken.clear();
28✔
1095

1096
    VSILFILE *fp = VSIFOpenL(osCredentials.c_str(), "rb");
28✔
1097
    if (fp != nullptr)
28✔
1098
    {
1099
        const char *pszLine;
1100
        bool bInProfile = false;
9✔
1101
        const std::string osBracketedProfile("[" + osProfile + "]");
27✔
1102
        while ((pszLine = CPLReadLineL(fp)) != nullptr)
49✔
1103
        {
1104
            if (pszLine[0] == '[')
44✔
1105
            {
1106
                if (bInProfile)
15✔
1107
                    break;
4✔
1108
                if (std::string(pszLine) == osBracketedProfile)
11✔
1109
                    bInProfile = true;
6✔
1110
            }
1111
            else if (bInProfile)
29✔
1112
            {
1113
                char *pszKey = nullptr;
12✔
1114
                const char *pszValue = CPLParseNameValue(pszLine, &pszKey);
12✔
1115
                if (pszKey && pszValue)
12✔
1116
                {
1117
                    if (EQUAL(pszKey, "aws_access_key_id"))
12✔
1118
                        osAccessKeyId = pszValue;
6✔
1119
                    else if (EQUAL(pszKey, "aws_secret_access_key"))
6✔
1120
                        osSecretAccessKey = pszValue;
6✔
1121
                    else if (EQUAL(pszKey, "aws_session_token"))
×
1122
                        osSessionToken = pszValue;
×
1123
                }
1124
                CPLFree(pszKey);
12✔
1125
            }
1126
        }
1127
        VSIFCloseL(fp);
9✔
1128
    }
1129

1130
    return !osSecretAccessKey.empty() && !osAccessKeyId.empty();
28✔
1131
}
1132

1133
/************************************************************************/
1134
/*                         GetDirSeparator()                            */
1135
/************************************************************************/
1136

1137
static const char *GetDirSeparator()
52✔
1138
{
1139
#ifdef _WIN32
1140
    static const char SEP_STRING[] = "\\";
1141
#else
1142
    static const char SEP_STRING[] = "/";
1143
#endif
1144
    return SEP_STRING;
52✔
1145
}
1146

1147
/************************************************************************/
1148
/*                          GetAWSRootDirectory()                       */
1149
/************************************************************************/
1150

1151
static std::string GetAWSRootDirectory()
27✔
1152
{
1153
    const char *pszAWSRootDir = CPLGetConfigOption("CPL_AWS_ROOT_DIR", nullptr);
27✔
1154
    if (pszAWSRootDir)
27✔
1155
        return pszAWSRootDir;
×
1156
#ifdef _WIN32
1157
    const char *pszHome = CPLGetConfigOption("USERPROFILE", nullptr);
1158
#else
1159
    const char *pszHome = CPLGetConfigOption("HOME", nullptr);
27✔
1160
#endif
1161

1162
    return std::string(pszHome ? pszHome : "")
54✔
1163
        .append(GetDirSeparator())
27✔
1164
        .append(".aws");
27✔
1165
}
1166

1167
/************************************************************************/
1168
/*                GetConfigurationFromAWSConfigFiles()                  */
1169
/************************************************************************/
1170

1171
bool VSIS3HandleHelper::GetConfigurationFromAWSConfigFiles(
27✔
1172
    const std::string &osPathForOption, const char *pszProfile,
1173
    std::string &osSecretAccessKey, std::string &osAccessKeyId,
1174
    std::string &osSessionToken, std::string &osRegion,
1175
    std::string &osCredentials, std::string &osRoleArn,
1176
    std::string &osSourceProfile, std::string &osExternalId,
1177
    std::string &osMFASerial, std::string &osRoleSessionName,
1178
    std::string &osWebIdentityTokenFile, std::string &osSSOStartURL,
1179
    std::string &osSSOAccountID, std::string &osSSORoleName)
1180
{
1181
    // See http://docs.aws.amazon.com/cli/latest/userguide/cli-config-files.html
1182
    // If AWS_DEFAULT_PROFILE is set (obsolete, no longer documented), use it in
1183
    // priority Otherwise use AWS_PROFILE Otherwise fallback to "default"
1184
    const char *pszProfileOri = pszProfile;
27✔
1185
    if (pszProfile == nullptr)
27✔
1186
    {
1187
        pszProfile = VSIGetPathSpecificOption(osPathForOption.c_str(),
25✔
1188
                                              "AWS_DEFAULT_PROFILE", "");
1189
        if (pszProfile[0] == '\0')
25✔
1190
            pszProfile = VSIGetPathSpecificOption(osPathForOption.c_str(),
25✔
1191
                                                  "AWS_PROFILE", "");
1192
    }
1193
    const std::string osProfile(pszProfile[0] != '\0' ? pszProfile : "default");
54✔
1194

1195
    const std::string osDotAws(GetAWSRootDirectory());
54✔
1196

1197
    // Read first ~/.aws/credential file
1198

1199
    // GDAL specific config option (mostly for testing purpose, but also
1200
    // used in production in some cases)
1201
    const char *pszCredentials = VSIGetPathSpecificOption(
27✔
1202
        osPathForOption.c_str(), "CPL_AWS_CREDENTIALS_FILE", nullptr);
1203
    if (pszCredentials)
27✔
1204
    {
1205
        osCredentials = pszCredentials;
22✔
1206
    }
1207
    else
1208
    {
1209
        osCredentials = osDotAws;
5✔
1210
        osCredentials += GetDirSeparator();
5✔
1211
        osCredentials += "credentials";
5✔
1212
    }
1213

1214
    ReadAWSCredentials(osProfile, osCredentials, osSecretAccessKey,
27✔
1215
                       osAccessKeyId, osSessionToken);
1216

1217
    // And then ~/.aws/config file (unless AWS_CONFIG_FILE is defined)
1218
    const char *pszAWSConfigFileEnv = VSIGetPathSpecificOption(
27✔
1219
        osPathForOption.c_str(), "AWS_CONFIG_FILE", nullptr);
1220
    std::string osConfig;
27✔
1221
    if (pszAWSConfigFileEnv && pszAWSConfigFileEnv[0])
27✔
1222
    {
1223
        osConfig = pszAWSConfigFileEnv;
7✔
1224
    }
1225
    else
1226
    {
1227
        osConfig = osDotAws;
20✔
1228
        osConfig += GetDirSeparator();
20✔
1229
        osConfig += "config";
20✔
1230
    }
1231

1232
    VSILFILE *fp = VSIFOpenL(osConfig.c_str(), "rb");
27✔
1233
    if (fp != nullptr)
27✔
1234
    {
1235
        // Start by reading sso-session's
1236
        const char *pszLine;
1237
        std::map<std::string, std::map<std::string, std::string>>
1238
            oMapSSOSessions;
16✔
1239
        std::string osSSOSession;
16✔
1240
        while ((pszLine = CPLReadLineL(fp)) != nullptr)
80✔
1241
        {
1242
            if (STARTS_WITH(pszLine, "[sso-session ") &&
72✔
1243
                pszLine[strlen(pszLine) - 1] == ']')
×
1244
            {
1245
                osSSOSession = pszLine + strlen("[sso-session ");
×
1246
                osSSOSession.pop_back();
×
1247
            }
1248
            else if (pszLine[0] == '[')
72✔
1249
            {
1250
                osSSOSession.clear();
18✔
1251
            }
1252
            else if (!osSSOSession.empty())
54✔
1253
            {
1254
                char *pszKey = nullptr;
×
1255
                const char *pszValue = CPLParseNameValue(pszLine, &pszKey);
×
1256
                if (pszKey && pszValue)
×
1257
                {
1258
                    // CPLDebugOnly("S3", "oMapSSOSessions[%s][%s] = %s",
1259
                    //              osSSOSession.c_str(), pszKey, pszValue);
1260
                    oMapSSOSessions[osSSOSession][pszKey] = pszValue;
×
1261
                }
1262
                CPLFree(pszKey);
×
1263
            }
1264
        }
1265
        osSSOSession.clear();
8✔
1266

1267
        bool bInProfile = false;
8✔
1268
        const std::string osBracketedProfile("[" + osProfile + "]");
16✔
1269
        const std::string osBracketedProfileProfile("[profile " + osProfile +
8✔
1270
                                                    "]");
16✔
1271

1272
        VSIFSeekL(fp, 0, SEEK_SET);
8✔
1273

1274
        while ((pszLine = CPLReadLineL(fp)) != nullptr)
65✔
1275
        {
1276
            if (pszLine[0] == '[')
62✔
1277
            {
1278
                if (bInProfile)
18✔
1279
                    break;
5✔
1280
                // In config file, the section name is nominally [profile foo]
1281
                // for the non default profile.
1282
                if (std::string(pszLine) == osBracketedProfile ||
36✔
1283
                    std::string(pszLine) == osBracketedProfileProfile)
23✔
1284
                {
1285
                    bInProfile = true;
7✔
1286
                }
1287
            }
1288
            else if (bInProfile)
44✔
1289
            {
1290
                char *pszKey = nullptr;
20✔
1291
                const char *pszValue = CPLParseNameValue(pszLine, &pszKey);
20✔
1292
                if (pszKey && pszValue)
20✔
1293
                {
1294
                    if (EQUAL(pszKey, "aws_access_key_id"))
19✔
1295
                    {
1296
                        UpdateAndWarnIfInconsistent(pszKey, osAccessKeyId,
3✔
1297
                                                    pszValue, osCredentials,
1298
                                                    osConfig);
1299
                    }
1300
                    else if (EQUAL(pszKey, "aws_secret_access_key"))
16✔
1301
                    {
1302
                        UpdateAndWarnIfInconsistent(pszKey, osSecretAccessKey,
3✔
1303
                                                    pszValue, osCredentials,
1304
                                                    osConfig);
1305
                    }
1306
                    else if (EQUAL(pszKey, "aws_session_token"))
13✔
1307
                    {
1308
                        UpdateAndWarnIfInconsistent(pszKey, osSessionToken,
×
1309
                                                    pszValue, osCredentials,
1310
                                                    osConfig);
1311
                    }
1312
                    else if (EQUAL(pszKey, "region"))
13✔
1313
                    {
1314
                        osRegion = pszValue;
4✔
1315
                    }
1316
                    else if (strcmp(pszKey, "role_arn") == 0)
9✔
1317
                    {
1318
                        osRoleArn = pszValue;
3✔
1319
                    }
1320
                    else if (strcmp(pszKey, "source_profile") == 0)
6✔
1321
                    {
1322
                        osSourceProfile = pszValue;
2✔
1323
                    }
1324
                    else if (strcmp(pszKey, "external_id") == 0)
4✔
1325
                    {
1326
                        osExternalId = pszValue;
1✔
1327
                    }
1328
                    else if (strcmp(pszKey, "mfa_serial") == 0)
3✔
1329
                    {
1330
                        osMFASerial = pszValue;
1✔
1331
                    }
1332
                    else if (strcmp(pszKey, "role_session_name") == 0)
2✔
1333
                    {
1334
                        osRoleSessionName = pszValue;
1✔
1335
                    }
1336
                    else if (strcmp(pszKey, "web_identity_token_file") == 0)
1✔
1337
                    {
1338
                        osWebIdentityTokenFile = pszValue;
1✔
1339
                    }
1340
                    else if (strcmp(pszKey, "sso_session") == 0)
×
1341
                    {
1342
                        osSSOSession = pszValue;
×
1343
                    }
1344
                    else if (strcmp(pszKey, "sso_start_url") == 0)
×
1345
                    {
1346
                        osSSOStartURL = pszValue;
×
1347
                    }
1348
                    else if (strcmp(pszKey, "sso_account_id") == 0)
×
1349
                    {
1350
                        osSSOAccountID = pszValue;
×
1351
                    }
1352
                    else if (strcmp(pszKey, "sso_role_name") == 0)
×
1353
                    {
1354
                        osSSORoleName = pszValue;
×
1355
                    }
1356
                }
1357
                CPLFree(pszKey);
20✔
1358
            }
1359
        }
1360
        VSIFCloseL(fp);
8✔
1361

1362
        if (!osSSOSession.empty())
8✔
1363
        {
1364
            if (osSSOStartURL.empty())
×
1365
                osSSOStartURL = oMapSSOSessions[osSSOSession]["sso_start_url"];
×
1366
        }
1367
    }
1368
    else if (pszAWSConfigFileEnv != nullptr)
19✔
1369
    {
1370
        if (pszAWSConfigFileEnv[0] != '\0')
15✔
1371
        {
1372
            CPLError(CE_Warning, CPLE_AppDefined,
×
1373
                     "%s does not exist or cannot be open",
1374
                     pszAWSConfigFileEnv);
1375
        }
1376
    }
1377

1378
    return (!osAccessKeyId.empty() && !osSecretAccessKey.empty()) ||
33✔
1379
           (!osRoleArn.empty() && !osSourceProfile.empty()) ||
21✔
1380
           (pszProfileOri != nullptr && !osRoleArn.empty() &&
1✔
1381
            !osWebIdentityTokenFile.empty()) ||
54✔
1382
           (!osSSOStartURL.empty() && !osSSOAccountID.empty() &&
18✔
1383
            !osSSORoleName.empty());
54✔
1384
}
1385

1386
/************************************************************************/
1387
/*                     GetTemporaryCredentialsForRole()                 */
1388
/************************************************************************/
1389

1390
// Issue a STS AssumedRole operation to get temporary credentials for an assumed
1391
// role.
1392
static bool GetTemporaryCredentialsForRole(
5✔
1393
    const std::string &osRoleArn, const std::string &osExternalId,
1394
    const std::string &osMFASerial, const std::string &osRoleSessionName,
1395
    const std::string &osSecretAccessKey, const std::string &osAccessKeyId,
1396
    const std::string &osSessionToken, std::string &osTempSecretAccessKey,
1397
    std::string &osTempAccessKeyId, std::string &osTempSessionToken,
1398
    std::string &osExpiration)
1399
{
1400
    std::string osXAMZDate = CPLGetConfigOption("AWS_TIMESTAMP", "");
10✔
1401
    if (osXAMZDate.empty())
5✔
1402
        osXAMZDate = CPLGetAWS_SIGN4_Timestamp(time(nullptr));
×
1403
    std::string osDate(osXAMZDate);
10✔
1404
    osDate.resize(8);
5✔
1405

1406
    const std::string osVerb("GET");
10✔
1407
    const std::string osService("sts");
10✔
1408
    const std::string osRegion(
1409
        CPLGetConfigOption("AWS_STS_REGION", "us-east-1"));
10✔
1410
    const std::string osHost(
1411
        CPLGetConfigOption("AWS_STS_ENDPOINT", "sts.amazonaws.com"));
10✔
1412

1413
    std::map<std::string, std::string> oMap;
10✔
1414
    oMap["Version"] = "2011-06-15";
5✔
1415
    oMap["Action"] = "AssumeRole";
5✔
1416
    oMap["RoleArn"] = osRoleArn;
5✔
1417
    oMap["RoleSessionName"] =
10✔
1418
        !osRoleSessionName.empty()
5✔
1419
            ? osRoleSessionName.c_str()
3✔
1420
            : CPLGetConfigOption("AWS_ROLE_SESSION_NAME", "GDAL-session");
10✔
1421
    if (!osExternalId.empty())
5✔
1422
        oMap["ExternalId"] = osExternalId;
3✔
1423
    if (!osMFASerial.empty())
5✔
1424
        oMap["SerialNumber"] = osMFASerial;
3✔
1425

1426
    std::string osQueryString;
10✔
1427
    for (const auto &kv : oMap)
31✔
1428
    {
1429
        if (osQueryString.empty())
26✔
1430
            osQueryString += "?";
5✔
1431
        else
1432
            osQueryString += "&";
21✔
1433
        osQueryString += kv.first;
26✔
1434
        osQueryString += "=";
26✔
1435
        osQueryString += CPLAWSURLEncode(kv.second);
26✔
1436
    }
1437
    std::string osCanonicalQueryString(osQueryString.substr(1));
10✔
1438

1439
    const std::string osAuthorization = CPLGetAWS_SIGN4_Authorization(
1440
        osSecretAccessKey, osAccessKeyId, osSessionToken, osRegion,
1441
        std::string(),  // m_osRequestPayer,
10✔
1442
        osService, osVerb,
1443
        nullptr,  // psExistingHeaders,
1444
        osHost, "/", osCanonicalQueryString,
1445
        CPLGetLowerCaseHexSHA256(std::string()),
10✔
1446
        false,  // bAddHeaderAMZContentSHA256
1447
        osXAMZDate);
20✔
1448

1449
    bool bRet = false;
5✔
1450
    const bool bUseHTTPS = CPLTestBool(CPLGetConfigOption("AWS_HTTPS", "YES"));
5✔
1451

1452
    CPLStringList aosOptions;
10✔
1453
    std::string headers;
10✔
1454
    if (!osSessionToken.empty())
5✔
1455
        headers += "X-Amz-Security-Token: " + osSessionToken + "\r\n";
2✔
1456
    headers += "X-Amz-Date: " + osXAMZDate + "\r\n";
5✔
1457
    headers += "Authorization: " + osAuthorization;
5✔
1458
    aosOptions.AddNameValue("HEADERS", headers.c_str());
5✔
1459

1460
    const std::string osURL =
1461
        (bUseHTTPS ? "https://" : "http://") + osHost + "/" + osQueryString;
10✔
1462
    CPLHTTPResult *psResult = CPLHTTPFetch(osURL.c_str(), aosOptions.List());
5✔
1463
    if (psResult)
5✔
1464
    {
1465
        if (psResult->nStatus == 0 && psResult->pabyData != nullptr)
5✔
1466
        {
1467
            CPLXMLTreeCloser oTree(CPLParseXMLString(
1468
                reinterpret_cast<char *>(psResult->pabyData)));
10✔
1469
            if (oTree)
5✔
1470
            {
1471
                const auto psCredentials = CPLGetXMLNode(
5✔
1472
                    oTree.get(),
1473
                    "=AssumeRoleResponse.AssumeRoleResult.Credentials");
1474
                if (psCredentials)
5✔
1475
                {
1476
                    osTempAccessKeyId =
1477
                        CPLGetXMLValue(psCredentials, "AccessKeyId", "");
5✔
1478
                    osTempSecretAccessKey =
1479
                        CPLGetXMLValue(psCredentials, "SecretAccessKey", "");
5✔
1480
                    osTempSessionToken =
1481
                        CPLGetXMLValue(psCredentials, "SessionToken", "");
5✔
1482
                    osExpiration =
1483
                        CPLGetXMLValue(psCredentials, "Expiration", "");
5✔
1484
                    bRet = true;
5✔
1485
                }
1486
                else
1487
                {
1488
                    CPLDebug("S3", "%s",
×
1489
                             reinterpret_cast<char *>(psResult->pabyData));
×
1490
                }
1491
            }
1492
        }
1493
        CPLHTTPDestroyResult(psResult);
5✔
1494
    }
1495
    return bRet;
10✔
1496
}
1497

1498
/************************************************************************/
1499
/*                     GetTemporaryCredentialsForSSO()                  */
1500
/************************************************************************/
1501

1502
// Issue a GetRoleCredentials request
1503
static bool GetTemporaryCredentialsForSSO(const std::string &osSSOStartURL,
×
1504
                                          const std::string &osSSOAccountID,
1505
                                          const std::string &osSSORoleName,
1506
                                          std::string &osTempSecretAccessKey,
1507
                                          std::string &osTempAccessKeyId,
1508
                                          std::string &osTempSessionToken,
1509
                                          std::string &osExpirationEpochInMS)
1510
{
1511
    std::string osSSOFilename = GetAWSRootDirectory();
×
1512
    osSSOFilename += GetDirSeparator();
×
1513
    osSSOFilename += "sso";
×
1514
    osSSOFilename += GetDirSeparator();
×
1515
    osSSOFilename += "cache";
×
1516
    osSSOFilename += GetDirSeparator();
×
1517

1518
    GByte hash[CPL_SHA1_HASH_SIZE];
1519
    CPL_SHA1(osSSOStartURL.data(), osSSOStartURL.size(), hash);
×
1520
    osSSOFilename += CPLGetLowerCaseHex(hash, sizeof(hash));
×
1521
    osSSOFilename += ".json";
×
1522

1523
    CPLJSONDocument oDoc;
×
1524
    if (!oDoc.Load(osSSOFilename))
×
1525
    {
1526
        CPLError(CE_Failure, CPLE_AppDefined, "Cannot find file %s",
×
1527
                 osSSOFilename.c_str());
1528
        return false;
×
1529
    }
1530

1531
    const auto oRoot = oDoc.GetRoot();
×
1532
    const auto osGotStartURL = oRoot.GetString("startUrl");
×
1533
    if (osGotStartURL != osSSOStartURL)
×
1534
    {
1535
        CPLError(CE_Failure, CPLE_AppDefined,
×
1536
                 "startUrl in %s = '%s', but expected '%s'.",
1537
                 osSSOFilename.c_str(), osGotStartURL.c_str(),
1538
                 osSSOStartURL.c_str());
1539
        return false;
×
1540
    }
1541
    const std::string osAccessToken = oRoot.GetString("accessToken");
×
1542
    if (osAccessToken.empty())
×
1543
    {
1544
        CPLError(CE_Failure, CPLE_AppDefined, "Missing accessToken in %s",
×
1545
                 osSSOFilename.c_str());
1546
        return false;
×
1547
    }
1548

1549
    const std::string osExpiresAt = oRoot.GetString("expiresAt");
×
1550
    if (!osExpiresAt.empty())
×
1551
    {
1552
        GIntBig nExpirationUnix = 0;
×
1553
        if (Iso8601ToUnixTime(osExpiresAt.c_str(), &nExpirationUnix) &&
×
1554
            time(nullptr) > nExpirationUnix)
×
1555
        {
1556
            CPLError(CE_Failure, CPLE_AppDefined,
×
1557
                     "accessToken in %s is no longer valid since %s. You may "
1558
                     "need to sign again using aws cli",
1559
                     osSSOFilename.c_str(), osExpiresAt.c_str());
1560
            return false;
×
1561
        }
1562
    }
1563

1564
    std::string osResourceAndQueryString = "/federation/credentials?role_name=";
×
1565
    osResourceAndQueryString += osSSORoleName;
×
1566
    osResourceAndQueryString += "&account_id=";
×
1567
    osResourceAndQueryString += osSSOAccountID;
×
1568

1569
    CPLStringList aosOptions;
×
1570
    std::string headers;
×
1571
    headers += "x-amz-sso_bearer_token: " + osAccessToken;
×
1572
    aosOptions.AddNameValue("HEADERS", headers.c_str());
×
1573

1574
    const bool bUseHTTPS = CPLTestBool(CPLGetConfigOption("AWS_HTTPS", "YES"));
×
1575
    const std::string osHost(CPLGetConfigOption(
1576
        "CPL_AWS_SSO_ENDPOINT", "portal.sso.us-east-1.amazonaws.com"));
×
1577

1578
    const std::string osURL = (bUseHTTPS ? "https://" : "http://") + osHost +
×
1579
                              osResourceAndQueryString;
×
1580
    CPLHTTPResult *psResult = CPLHTTPFetch(osURL.c_str(), aosOptions.List());
×
1581
    bool bRet = false;
×
1582
    if (psResult)
×
1583
    {
1584
        if (psResult->nStatus == 0 && psResult->pabyData != nullptr &&
×
1585
            oDoc.LoadMemory(reinterpret_cast<char *>(psResult->pabyData)))
×
1586
        {
1587
            auto oRoleCredentials = oDoc.GetRoot().GetObj("roleCredentials");
×
1588
            osTempAccessKeyId = oRoleCredentials.GetString("accessKeyId");
×
1589
            osTempSecretAccessKey =
1590
                oRoleCredentials.GetString("secretAccessKey");
×
1591
            osTempSessionToken = oRoleCredentials.GetString("sessionToken");
×
1592
            osExpirationEpochInMS = oRoleCredentials.GetString("expiration");
×
1593
            bRet =
×
1594
                !osTempAccessKeyId.empty() && !osTempSecretAccessKey.empty() &&
×
1595
                !osTempSessionToken.empty() && !osExpirationEpochInMS.empty();
×
1596
        }
1597
        CPLHTTPDestroyResult(psResult);
×
1598
    }
1599
    if (!bRet)
×
1600
    {
1601
        CPLError(CE_Failure, CPLE_AppDefined,
×
1602
                 "Did not manage to get temporary credentials for SSO "
1603
                 "authentication");
1604
    }
1605
    return bRet;
×
1606
}
1607

1608
/************************************************************************/
1609
/*               GetOrRefreshTemporaryCredentialsForRole()              */
1610
/************************************************************************/
1611

1612
bool VSIS3HandleHelper::GetOrRefreshTemporaryCredentialsForRole(
10✔
1613
    bool bForceRefresh, std::string &osSecretAccessKey,
1614
    std::string &osAccessKeyId, std::string &osSessionToken,
1615
    std::string &osRegion)
1616
{
1617
    CPLMutexHolder oHolder(&ghMutex);
20✔
1618
    if (!bForceRefresh)
10✔
1619
    {
1620
        time_t nCurTime;
1621
        time(&nCurTime);
10✔
1622
        // Try to reuse credentials if they are still valid, but
1623
        // keep one minute of margin...
1624
        if (!gosGlobalAccessKeyId.empty() && nCurTime < gnGlobalExpiration - 60)
10✔
1625
        {
1626
            osAccessKeyId = gosGlobalAccessKeyId;
6✔
1627
            osSecretAccessKey = gosGlobalSecretAccessKey;
6✔
1628
            osSessionToken = gosGlobalSessionToken;
6✔
1629
            osRegion = gosRegion;
6✔
1630
            return true;
6✔
1631
        }
1632
    }
1633

1634
    if (!gosRoleArnWebIdentity.empty())
4✔
1635
    {
1636
        if (GetConfigurationFromAssumeRoleWithWebIdentity(
2✔
1637
                bForceRefresh, std::string(), gosRoleArnWebIdentity,
4✔
1638
                gosWebIdentityTokenFile, osSecretAccessKey, osAccessKeyId,
1639
                osSessionToken))
1640
        {
1641
            gosSourceProfileSecretAccessKey = osSecretAccessKey;
1✔
1642
            gosSourceProfileAccessKeyId = osAccessKeyId;
1✔
1643
            gosSourceProfileSessionToken = osSessionToken;
1✔
1644
        }
1645
        else
1646
        {
1647
            return false;
1✔
1648
        }
1649
    }
1650

1651
    if (!gosRoleArn.empty())
3✔
1652
    {
1653
        std::string osExpiration;
3✔
1654
        gosGlobalSecretAccessKey.clear();
3✔
1655
        gosGlobalAccessKeyId.clear();
3✔
1656
        gosGlobalSessionToken.clear();
3✔
1657
        if (GetTemporaryCredentialsForRole(
3✔
1658
                gosRoleArn, gosExternalId, gosMFASerial, gosRoleSessionName,
1659
                gosSourceProfileSecretAccessKey, gosSourceProfileAccessKeyId,
1660
                gosSourceProfileSessionToken, gosGlobalSecretAccessKey,
1661
                gosGlobalAccessKeyId, gosGlobalSessionToken, osExpiration))
1662
        {
1663
            Iso8601ToUnixTime(osExpiration.c_str(), &gnGlobalExpiration);
3✔
1664
            osAccessKeyId = gosGlobalAccessKeyId;
3✔
1665
            osSecretAccessKey = gosGlobalSecretAccessKey;
3✔
1666
            osSessionToken = gosGlobalSessionToken;
3✔
1667
            osRegion = gosRegion;
3✔
1668
            return true;
3✔
1669
        }
1670
    }
1671

1672
    return false;
×
1673
}
1674

1675
/************************************************************************/
1676
/*               GetOrRefreshTemporaryCredentialsForSSO()               */
1677
/************************************************************************/
1678

1679
bool VSIS3HandleHelper::GetOrRefreshTemporaryCredentialsForSSO(
×
1680
    bool bForceRefresh, std::string &osSecretAccessKey,
1681
    std::string &osAccessKeyId, std::string &osSessionToken,
1682
    std::string &osRegion)
1683
{
1684
    CPLMutexHolder oHolder(&ghMutex);
×
1685
    if (!bForceRefresh)
×
1686
    {
1687
        time_t nCurTime;
1688
        time(&nCurTime);
×
1689
        // Try to reuse credentials if they are still valid, but
1690
        // keep one minute of margin...
1691
        if (!gosGlobalAccessKeyId.empty() && nCurTime < gnGlobalExpiration - 60)
×
1692
        {
1693
            osAccessKeyId = gosGlobalAccessKeyId;
×
1694
            osSecretAccessKey = gosGlobalSecretAccessKey;
×
1695
            osSessionToken = gosGlobalSessionToken;
×
1696
            osRegion = gosRegion;
×
1697
            return true;
×
1698
        }
1699
    }
1700

1701
    if (!gosSSOStartURL.empty())
×
1702
    {
1703
        std::string osExpirationEpochInMS;
×
1704
        gosGlobalSecretAccessKey.clear();
×
1705
        gosGlobalAccessKeyId.clear();
×
1706
        gosGlobalSessionToken.clear();
×
1707
        if (GetTemporaryCredentialsForSSO(
×
1708
                gosSSOStartURL, gosSSOAccountID, gosSSORoleName,
1709
                gosGlobalSecretAccessKey, gosGlobalAccessKeyId,
1710
                gosGlobalSessionToken, osExpirationEpochInMS))
1711
        {
1712
            gnGlobalExpiration =
×
1713
                CPLAtoGIntBig(osExpirationEpochInMS.c_str()) / 1000;
×
1714
            osAccessKeyId = gosGlobalAccessKeyId;
×
1715
            osSecretAccessKey = gosGlobalSecretAccessKey;
×
1716
            osSessionToken = gosGlobalSessionToken;
×
1717
            osRegion = gosRegion;
×
1718
            return true;
×
1719
        }
1720
    }
1721

1722
    return false;
×
1723
}
1724

1725
/************************************************************************/
1726
/*                        GetConfiguration()                            */
1727
/************************************************************************/
1728

1729
bool VSIS3HandleHelper::GetConfiguration(
472✔
1730
    const std::string &osPathForOption, CSLConstList papszOptions,
1731
    std::string &osSecretAccessKey, std::string &osAccessKeyId,
1732
    std::string &osSessionToken, std::string &osRegion,
1733
    AWSCredentialsSource &eCredentialsSource)
1734
{
1735
    eCredentialsSource = AWSCredentialsSource::REGULAR;
472✔
1736

1737
    // AWS_REGION is GDAL specific. Later overloaded by standard
1738
    // AWS_DEFAULT_REGION
1739
    osRegion = CSLFetchNameValueDef(
1740
        papszOptions, "AWS_REGION",
1741
        VSIGetPathSpecificOption(osPathForOption.c_str(), "AWS_REGION",
1742
                                 "us-east-1"));
472✔
1743

1744
    if (CPLTestBool(VSIGetPathSpecificOption(osPathForOption.c_str(),
472✔
1745
                                             "AWS_NO_SIGN_REQUEST", "NO")))
1746
    {
1747
        osSecretAccessKey.clear();
28✔
1748
        osAccessKeyId.clear();
28✔
1749
        osSessionToken.clear();
28✔
1750
        return true;
28✔
1751
    }
1752

1753
    osSecretAccessKey = CSLFetchNameValueDef(
1754
        papszOptions, "AWS_SECRET_ACCESS_KEY",
1755
        VSIGetPathSpecificOption(osPathForOption.c_str(),
1756
                                 "AWS_SECRET_ACCESS_KEY", ""));
444✔
1757
    if (!osSecretAccessKey.empty())
444✔
1758
    {
1759
        osAccessKeyId = CSLFetchNameValueDef(
1760
            papszOptions, "AWS_ACCESS_KEY_ID",
1761
            VSIGetPathSpecificOption(osPathForOption.c_str(),
1762
                                     "AWS_ACCESS_KEY_ID", ""));
415✔
1763
        if (osAccessKeyId.empty())
415✔
1764
        {
1765
            VSIError(VSIE_AWSInvalidCredentials,
1✔
1766
                     "AWS_ACCESS_KEY_ID configuration option not defined");
1767
            return false;
1✔
1768
        }
1769

1770
        osSessionToken = CSLFetchNameValueDef(
1771
            papszOptions, "AWS_SESSION_TOKEN",
1772
            VSIGetPathSpecificOption(osPathForOption.c_str(),
1773
                                     "AWS_SESSION_TOKEN", ""));
414✔
1774
        return true;
414✔
1775
    }
1776

1777
    // Next try to see if we have a current assumed role
1778
    bool bAssumedRole = false;
29✔
1779
    bool bSSO = false;
29✔
1780
    {
1781
        CPLMutexHolder oHolder(&ghMutex);
29✔
1782
        bAssumedRole = !gosRoleArn.empty();
29✔
1783
        bSSO = !gosSSOStartURL.empty();
29✔
1784
    }
1785
    if (bAssumedRole && GetOrRefreshTemporaryCredentialsForRole(
29✔
1786
                            /* bForceRefresh = */ false, osSecretAccessKey,
1787
                            osAccessKeyId, osSessionToken, osRegion))
1788
    {
1789
        eCredentialsSource = AWSCredentialsSource::ASSUMED_ROLE;
4✔
1790
        return true;
4✔
1791
    }
1792
    else if (bSSO && GetOrRefreshTemporaryCredentialsForSSO(
25✔
1793
                         /* bForceRefresh = */ false, osSecretAccessKey,
1794
                         osAccessKeyId, osSessionToken, osRegion))
1795
    {
1796
        eCredentialsSource = AWSCredentialsSource::SSO;
×
1797
        return true;
×
1798
    }
1799

1800
    // Next try reading from ~/.aws/credentials and ~/.aws/config
1801
    std::string osCredentials;
50✔
1802
    std::string osRoleArn;
50✔
1803
    std::string osSourceProfile;
50✔
1804
    std::string osExternalId;
50✔
1805
    std::string osMFASerial;
50✔
1806
    std::string osRoleSessionName;
50✔
1807
    std::string osWebIdentityTokenFile;
50✔
1808
    std::string osSSOStartURL;
50✔
1809
    std::string osSSOAccountID;
50✔
1810
    std::string osSSORoleName;
50✔
1811
    // coverity[tainted_data]
1812
    if (GetConfigurationFromAWSConfigFiles(
25✔
1813
            osPathForOption,
1814
            /* pszProfile = */ nullptr, osSecretAccessKey, osAccessKeyId,
1815
            osSessionToken, osRegion, osCredentials, osRoleArn, osSourceProfile,
1816
            osExternalId, osMFASerial, osRoleSessionName,
1817
            osWebIdentityTokenFile, osSSOStartURL, osSSOAccountID,
1818
            osSSORoleName))
1819
    {
1820
        if (osSecretAccessKey.empty() && !osRoleArn.empty())
7✔
1821
        {
1822
            // Check if the default profile is pointing to another profile
1823
            // that has a role_arn and web_identity_token_file settings.
1824
            if (!osSourceProfile.empty())
2✔
1825
            {
1826
                std::string osSecretAccessKeySP;
4✔
1827
                std::string osAccessKeyIdSP;
4✔
1828
                std::string osSessionTokenSP;
4✔
1829
                std::string osRegionSP;
4✔
1830
                std::string osCredentialsSP;
4✔
1831
                std::string osRoleArnSP;
4✔
1832
                std::string osSourceProfileSP;
4✔
1833
                std::string osExternalIdSP;
4✔
1834
                std::string osMFASerialSP;
4✔
1835
                std::string osRoleSessionNameSP;
4✔
1836
                std::string osSSOStartURLSP;
4✔
1837
                std::string osSSOAccountIDSP;
4✔
1838
                std::string osSSORoleNameSP;
4✔
1839
                if (GetConfigurationFromAWSConfigFiles(
2✔
1840
                        osPathForOption, osSourceProfile.c_str(),
1841
                        osSecretAccessKeySP, osAccessKeyIdSP, osSessionTokenSP,
1842
                        osRegionSP, osCredentialsSP, osRoleArnSP,
1843
                        osSourceProfileSP, osExternalIdSP, osMFASerialSP,
1844
                        osRoleSessionNameSP, osWebIdentityTokenFile,
1845
                        osSSOStartURLSP, osSSOAccountIDSP, osSSORoleNameSP))
1846
                {
1847
                    if (GetConfigurationFromAssumeRoleWithWebIdentity(
2✔
1848
                            /* bForceRefresh = */ false, osPathForOption,
1849
                            osRoleArnSP, osWebIdentityTokenFile,
1850
                            osSecretAccessKey, osAccessKeyId, osSessionToken))
1851
                    {
1852
                        CPLMutexHolder oHolder(&ghMutex);
2✔
1853
                        gosRoleArnWebIdentity = std::move(osRoleArnSP);
1✔
1854
                        gosWebIdentityTokenFile =
1855
                            std::move(osWebIdentityTokenFile);
1✔
1856
                    }
1857
                }
1858
            }
1859

1860
            if (gosRoleArnWebIdentity.empty())
2✔
1861
            {
1862
                // Get the credentials for the source profile, that will be
1863
                // used to sign the STS AssumedRole request.
1864
                if (!ReadAWSCredentials(osSourceProfile, osCredentials,
1✔
1865
                                        osSecretAccessKey, osAccessKeyId,
1866
                                        osSessionToken))
1867
                {
1868
                    VSIError(
×
1869
                        VSIE_AWSInvalidCredentials,
1870
                        "Cannot retrieve credentials for source profile %s",
1871
                        osSourceProfile.c_str());
1872
                    return false;
×
1873
                }
1874
            }
1875

1876
            std::string osTempSecretAccessKey;
4✔
1877
            std::string osTempAccessKeyId;
4✔
1878
            std::string osTempSessionToken;
4✔
1879
            std::string osExpiration;
4✔
1880
            if (GetTemporaryCredentialsForRole(
2✔
1881
                    osRoleArn, osExternalId, osMFASerial, osRoleSessionName,
1882
                    osSecretAccessKey, osAccessKeyId, osSessionToken,
1883
                    osTempSecretAccessKey, osTempAccessKeyId,
1884
                    osTempSessionToken, osExpiration))
1885
            {
1886
                CPLDebug("S3", "Using assumed role %s", osRoleArn.c_str());
2✔
1887
                {
1888
                    // Store global variables to be able to reuse the
1889
                    // temporary credentials
1890
                    CPLMutexHolder oHolder(&ghMutex);
4✔
1891
                    Iso8601ToUnixTime(osExpiration.c_str(),
2✔
1892
                                      &gnGlobalExpiration);
1893
                    gosRoleArn = std::move(osRoleArn);
2✔
1894
                    gosExternalId = std::move(osExternalId);
2✔
1895
                    gosMFASerial = std::move(osMFASerial);
2✔
1896
                    gosRoleSessionName = std::move(osRoleSessionName);
2✔
1897
                    gosSourceProfileSecretAccessKey =
1898
                        std::move(osSecretAccessKey);
2✔
1899
                    gosSourceProfileAccessKeyId = std::move(osAccessKeyId);
2✔
1900
                    gosSourceProfileSessionToken = std::move(osSessionToken);
2✔
1901
                    gosGlobalAccessKeyId = osTempAccessKeyId;
2✔
1902
                    gosGlobalSecretAccessKey = osTempSecretAccessKey;
2✔
1903
                    gosGlobalSessionToken = osTempSessionToken;
2✔
1904
                    gosRegion = osRegion;
2✔
1905
                }
1906
                osSecretAccessKey = std::move(osTempSecretAccessKey);
2✔
1907
                osAccessKeyId = std::move(osTempAccessKeyId);
2✔
1908
                osSessionToken = std::move(osTempSessionToken);
2✔
1909
                eCredentialsSource = AWSCredentialsSource::ASSUMED_ROLE;
2✔
1910
                return true;
2✔
1911
            }
1912
            return false;
×
1913
        }
1914

1915
        if (!osSSOStartURL.empty())
5✔
1916
        {
1917
            std::string osTempSecretAccessKey;
×
1918
            std::string osTempAccessKeyId;
×
1919
            std::string osTempSessionToken;
×
1920
            std::string osExpirationEpochInMS;
×
1921
            if (GetTemporaryCredentialsForSSO(
×
1922
                    osSSOStartURL, osSSOAccountID, osSSORoleName,
1923
                    osTempSecretAccessKey, osTempAccessKeyId,
1924
                    osTempSessionToken, osExpirationEpochInMS))
1925
            {
1926
                CPLDebug("S3", "Using SSO %s", osSSOStartURL.c_str());
×
1927
                {
1928
                    // Store global variables to be able to reuse the
1929
                    // temporary credentials
1930
                    CPLMutexHolder oHolder(&ghMutex);
×
1931
                    gnGlobalExpiration =
×
1932
                        CPLAtoGIntBig(osExpirationEpochInMS.c_str()) / 1000;
×
1933
                    gosSSOStartURL = std::move(osSSOStartURL);
×
1934
                    gosSSOAccountID = std::move(osSSOAccountID);
×
1935
                    gosSSORoleName = std::move(osSSORoleName);
×
1936
                    gosGlobalAccessKeyId = osTempAccessKeyId;
×
1937
                    gosGlobalSecretAccessKey = osTempSecretAccessKey;
×
1938
                    gosGlobalSessionToken = osTempSessionToken;
×
1939
                    gosRegion = osRegion;
×
1940
                }
1941
                osSecretAccessKey = std::move(osTempSecretAccessKey);
×
1942
                osAccessKeyId = std::move(osTempAccessKeyId);
×
1943
                osSessionToken = std::move(osTempSessionToken);
×
1944
                eCredentialsSource = AWSCredentialsSource::SSO;
×
1945
                return true;
×
1946
            }
1947
            return false;
×
1948
        }
1949

1950
        return true;
5✔
1951
    }
1952

1953
    if (CPLTestBool(CPLGetConfigOption("CPL_AWS_WEB_IDENTITY_ENABLE", "YES")))
18✔
1954
    {
1955
        // WebIdentity method: use Web Identity Token
1956
        if (GetConfigurationFromAssumeRoleWithWebIdentity(
11✔
1957
                /* bForceRefresh = */ false, osPathForOption,
1958
                /* osRoleArnIn = */ std::string(),
22✔
1959
                /* osWebIdentityTokenFileIn = */ std::string(),
22✔
1960
                osSecretAccessKey, osAccessKeyId, osSessionToken))
1961
        {
1962
            eCredentialsSource = AWSCredentialsSource::WEB_IDENTITY;
1✔
1963
            return true;
1✔
1964
        }
1965
    }
1966

1967
    // Last method: use IAM role security credentials on EC2 instances
1968
    if (GetConfigurationFromEC2(/* bForceRefresh = */ false, osPathForOption,
17✔
1969
                                osSecretAccessKey, osAccessKeyId,
1970
                                osSessionToken))
1971
    {
1972
        eCredentialsSource = AWSCredentialsSource::EC2;
9✔
1973
        return true;
9✔
1974
    }
1975

1976
    CPLString osMsg;
8✔
1977
    osMsg.Printf(
1978
        "No valid AWS credentials found. "
1979
        "For authenticated requests, you need to set "
1980
        "AWS_SECRET_ACCESS_KEY, AWS_ACCESS_KEY_ID or other configuration "
1981
        "options, or create a %s file. Consult "
1982
        "https://gdal.org/en/stable/user/"
1983
        "virtual_file_systems.html#vsis3-aws-s3-files for more details. "
1984
        "For unauthenticated requests on public resources, set the "
1985
        "AWS_NO_SIGN_REQUEST configuration option to YES.",
1986
        osCredentials.c_str());
8✔
1987
    CPLDebug("GS", "%s", osMsg.c_str());
8✔
1988
    VSIError(VSIE_AWSInvalidCredentials, "%s", osMsg.c_str());
8✔
1989

1990
    return false;
8✔
1991
}
1992

1993
/************************************************************************/
1994
/*                          CleanMutex()                                */
1995
/************************************************************************/
1996

1997
void VSIS3HandleHelper::CleanMutex()
922✔
1998
{
1999
    if (ghMutex != nullptr)
922✔
2000
        CPLDestroyMutex(ghMutex);
922✔
2001
    ghMutex = nullptr;
922✔
2002
}
922✔
2003

2004
/************************************************************************/
2005
/*                          ClearCache()                                */
2006
/************************************************************************/
2007

2008
void VSIS3HandleHelper::ClearCache()
1,237✔
2009
{
2010
    CPLMutexHolder oHolder(&ghMutex);
2,474✔
2011

2012
    gosIAMRole.clear();
1,237✔
2013
    gosGlobalAccessKeyId.clear();
1,237✔
2014
    gosGlobalSecretAccessKey.clear();
1,237✔
2015
    gosGlobalSessionToken.clear();
1,237✔
2016
    gnGlobalExpiration = 0;
1,237✔
2017
    gosRoleArn.clear();
1,237✔
2018
    gosExternalId.clear();
1,237✔
2019
    gosMFASerial.clear();
1,237✔
2020
    gosRoleSessionName.clear();
1,237✔
2021
    gosSourceProfileAccessKeyId.clear();
1,237✔
2022
    gosSourceProfileSecretAccessKey.clear();
1,237✔
2023
    gosSourceProfileSessionToken.clear();
1,237✔
2024
    gosRegion.clear();
1,237✔
2025
    gosRoleArnWebIdentity.clear();
1,237✔
2026
    gosWebIdentityTokenFile.clear();
1,237✔
2027
    gosSSOStartURL.clear();
1,237✔
2028
    gosSSOAccountID.clear();
1,237✔
2029
    gosSSORoleName.clear();
1,237✔
2030
}
1,237✔
2031

2032
/************************************************************************/
2033
/*                          BuildFromURI()                              */
2034
/************************************************************************/
2035

2036
VSIS3HandleHelper *VSIS3HandleHelper::BuildFromURI(const char *pszURI,
472✔
2037
                                                   const char *pszFSPrefix,
2038
                                                   bool bAllowNoObject,
2039
                                                   CSLConstList papszOptions)
2040
{
2041
    std::string osPathForOption("/vsis3/");
944✔
2042
    if (pszURI)
472✔
2043
        osPathForOption += pszURI;
472✔
2044

2045
    std::string osSecretAccessKey;
944✔
2046
    std::string osAccessKeyId;
944✔
2047
    std::string osSessionToken;
944✔
2048
    std::string osRegion;
944✔
2049
    AWSCredentialsSource eCredentialsSource = AWSCredentialsSource::REGULAR;
472✔
2050
    if (!GetConfiguration(osPathForOption, papszOptions, osSecretAccessKey,
472✔
2051
                          osAccessKeyId, osSessionToken, osRegion,
2052
                          eCredentialsSource))
2053
    {
2054
        return nullptr;
9✔
2055
    }
2056

2057
    // According to
2058
    // http://docs.aws.amazon.com/cli/latest/userguide/cli-environment.html "
2059
    // This variable overrides the default region of the in-use profile, if
2060
    // set."
2061
    const std::string osDefaultRegion = CSLFetchNameValueDef(
2062
        papszOptions, "AWS_DEFAULT_REGION",
2063
        VSIGetPathSpecificOption(osPathForOption.c_str(), "AWS_DEFAULT_REGION",
2064
                                 ""));
926✔
2065
    if (!osDefaultRegion.empty())
463✔
2066
    {
2067
        osRegion = osDefaultRegion;
463✔
2068
    }
2069

2070
    std::string osEndpoint = VSIGetPathSpecificOption(
2071
        osPathForOption.c_str(), "AWS_S3_ENDPOINT", "s3.amazonaws.com");
926✔
2072
    bool bForceHTTP = false;
463✔
2073
    bool bForceHTTPS = false;
463✔
2074
    if (STARTS_WITH(osEndpoint.c_str(), "http://"))
463✔
2075
    {
2076
        bForceHTTP = true;
12✔
2077
        osEndpoint = osEndpoint.substr(strlen("http://"));
12✔
2078
    }
2079
    else if (STARTS_WITH(osEndpoint.c_str(), "https://"))
451✔
2080
    {
2081
        bForceHTTPS = true;
10✔
2082
        osEndpoint = osEndpoint.substr(strlen("https://"));
10✔
2083
    }
2084
    if (!osEndpoint.empty() && osEndpoint.back() == '/')
463✔
2085
        osEndpoint.pop_back();
×
2086

2087
    if (!osRegion.empty() && osEndpoint == "s3.amazonaws.com")
463✔
2088
    {
2089
        osEndpoint = "s3." + osRegion + ".amazonaws.com";
40✔
2090
    }
2091
    const std::string osRequestPayer = VSIGetPathSpecificOption(
2092
        osPathForOption.c_str(), "AWS_REQUEST_PAYER", "");
926✔
2093
    std::string osBucket;
926✔
2094
    std::string osObjectKey;
926✔
2095
    if (pszURI != nullptr && pszURI[0] != '\0' &&
922✔
2096
        !GetBucketAndObjectKey(pszURI, pszFSPrefix, bAllowNoObject, osBucket,
459✔
2097
                               osObjectKey))
2098
    {
2099
        return nullptr;
1✔
2100
    }
2101
    const bool bUseHTTPS =
2102
        bForceHTTPS ||
914✔
2103
        (!bForceHTTP && CPLTestBool(VSIGetPathSpecificOption(
452✔
2104
                            osPathForOption.c_str(), "AWS_HTTPS", "YES")));
462✔
2105
    const bool bIsValidNameForVirtualHosting =
2106
        osBucket.find('.') == std::string::npos;
462✔
2107
    const bool bUseVirtualHosting = CPLTestBool(CSLFetchNameValueDef(
462✔
2108
        papszOptions, "AWS_VIRTUAL_HOSTING",
2109
        VSIGetPathSpecificOption(osPathForOption.c_str(), "AWS_VIRTUAL_HOSTING",
2110
                                 bIsValidNameForVirtualHosting ? "TRUE"
2111
                                                               : "FALSE")));
2112
    return new VSIS3HandleHelper(
2113
        osSecretAccessKey, osAccessKeyId, osSessionToken, osEndpoint, osRegion,
2114
        osRequestPayer, osBucket, osObjectKey, bUseHTTPS, bUseVirtualHosting,
2115
        eCredentialsSource);
462✔
2116
}
2117

2118
/************************************************************************/
2119
/*                          GetQueryString()                            */
2120
/************************************************************************/
2121

2122
std::string
2123
IVSIS3LikeHandleHelper::GetQueryString(bool bAddEmptyValueAfterEqual) const
1,887✔
2124
{
2125
    std::string osQueryString;
1,887✔
2126
    std::map<std::string, std::string>::const_iterator oIter =
2127
        m_oMapQueryParameters.begin();
1,887✔
2128
    for (; oIter != m_oMapQueryParameters.end(); ++oIter)
3,642✔
2129
    {
2130
        if (oIter == m_oMapQueryParameters.begin())
1,755✔
2131
            osQueryString += "?";
873✔
2132
        else
2133
            osQueryString += "&";
882✔
2134
        osQueryString += oIter->first;
1,755✔
2135
        if (!oIter->second.empty() || bAddEmptyValueAfterEqual)
1,755✔
2136
        {
2137
            osQueryString += "=";
1,710✔
2138
            osQueryString += CPLAWSURLEncode(oIter->second);
1,710✔
2139
        }
2140
    }
2141
    return osQueryString;
3,774✔
2142
}
2143

2144
/************************************************************************/
2145
/*                       ResetQueryParameters()                         */
2146
/************************************************************************/
2147

2148
void IVSIS3LikeHandleHelper::ResetQueryParameters()
597✔
2149
{
2150
    m_oMapQueryParameters.clear();
597✔
2151
    RebuildURL();
597✔
2152
}
597✔
2153

2154
/************************************************************************/
2155
/*                         AddQueryParameter()                          */
2156
/************************************************************************/
2157

2158
void IVSIS3LikeHandleHelper::AddQueryParameter(const std::string &osKey,
719✔
2159
                                               const std::string &osValue)
2160
{
2161
    m_oMapQueryParameters[osKey] = osValue;
719✔
2162
    RebuildURL();
719✔
2163
}
719✔
2164

2165
/************************************************************************/
2166
/*                           GetURLNoKVP()                              */
2167
/************************************************************************/
2168

2169
std::string IVSIS3LikeHandleHelper::GetURLNoKVP() const
405✔
2170
{
2171
    std::string osURL(GetURL());
405✔
2172
    const auto nPos = osURL.find('?');
405✔
2173
    if (nPos != std::string::npos)
405✔
2174
        osURL.resize(nPos);
8✔
2175
    return osURL;
405✔
2176
}
2177

2178
/************************************************************************/
2179
/*                          RefreshCredentials()                        */
2180
/************************************************************************/
2181

2182
void VSIS3HandleHelper::RefreshCredentials(const std::string &osPathForOption,
357✔
2183
                                           bool bForceRefresh) const
2184
{
2185
    if (m_eCredentialsSource == AWSCredentialsSource::EC2)
357✔
2186
    {
2187
        std::string osSecretAccessKey, osAccessKeyId, osSessionToken;
16✔
2188
        if (GetConfigurationFromEC2(bForceRefresh, osPathForOption.c_str(),
8✔
2189
                                    osSecretAccessKey, osAccessKeyId,
2190
                                    osSessionToken))
2191
        {
2192
            m_osSecretAccessKey = std::move(osSecretAccessKey);
8✔
2193
            m_osAccessKeyId = std::move(osAccessKeyId);
8✔
2194
            m_osSessionToken = std::move(osSessionToken);
8✔
2195
        }
2196
    }
2197
    else if (m_eCredentialsSource == AWSCredentialsSource::ASSUMED_ROLE)
349✔
2198
    {
2199
        std::string osSecretAccessKey, osAccessKeyId, osSessionToken;
12✔
2200
        std::string osRegion;
12✔
2201
        if (GetOrRefreshTemporaryCredentialsForRole(
6✔
2202
                bForceRefresh, osSecretAccessKey, osAccessKeyId, osSessionToken,
2203
                osRegion))
2204
        {
2205
            m_osSecretAccessKey = std::move(osSecretAccessKey);
5✔
2206
            m_osAccessKeyId = std::move(osAccessKeyId);
5✔
2207
            m_osSessionToken = std::move(osSessionToken);
5✔
2208
        }
2209
    }
2210
    else if (m_eCredentialsSource == AWSCredentialsSource::WEB_IDENTITY)
343✔
2211
    {
2212
        std::string osSecretAccessKey, osAccessKeyId, osSessionToken;
2✔
2213
        if (GetConfigurationFromAssumeRoleWithWebIdentity(
1✔
2214
                bForceRefresh, osPathForOption.c_str(), std::string(),
2✔
2215
                std::string(), osSecretAccessKey, osAccessKeyId,
2✔
2216
                osSessionToken))
2217
        {
2218
            m_osSecretAccessKey = std::move(osSecretAccessKey);
1✔
2219
            m_osAccessKeyId = std::move(osAccessKeyId);
1✔
2220
            m_osSessionToken = std::move(osSessionToken);
1✔
2221
        }
2222
    }
2223
    else if (m_eCredentialsSource == AWSCredentialsSource::SSO)
342✔
2224
    {
2225
        std::string osSecretAccessKey, osAccessKeyId, osSessionToken;
×
2226
        std::string osRegion;
×
2227
        if (GetOrRefreshTemporaryCredentialsForSSO(
×
2228
                bForceRefresh, osSecretAccessKey, osAccessKeyId, osSessionToken,
2229
                osRegion))
2230
        {
2231
            m_osSecretAccessKey = std::move(osSecretAccessKey);
×
2232
            m_osAccessKeyId = std::move(osAccessKeyId);
×
2233
            m_osSessionToken = std::move(osSessionToken);
×
2234
        }
2235
    }
2236
}
357✔
2237

2238
/************************************************************************/
2239
/*                           GetCurlHeaders()                           */
2240
/************************************************************************/
2241

2242
struct curl_slist *VSIS3HandleHelper::GetCurlHeaders(
356✔
2243
    const std::string &osVerb, const struct curl_slist *psExistingHeaders,
2244
    const void *pabyDataContent, size_t nBytesContent) const
2245
{
2246
    std::string osPathForOption("/vsis3/");
712✔
2247
    osPathForOption += m_osBucket;
356✔
2248
    osPathForOption += '/';
356✔
2249
    osPathForOption += m_osObjectKey;
356✔
2250

2251
    RefreshCredentials(osPathForOption, /* bForceRefresh = */ false);
356✔
2252

2253
    std::string osXAMZDate =
2254
        VSIGetPathSpecificOption(osPathForOption.c_str(), "AWS_TIMESTAMP", "");
712✔
2255
    if (osXAMZDate.empty())
356✔
2256
        osXAMZDate = CPLGetAWS_SIGN4_Timestamp(time(nullptr));
×
2257

2258
    const std::string osXAMZContentSHA256 =
2259
        CPLGetLowerCaseHexSHA256(pabyDataContent, nBytesContent);
712✔
2260

2261
    std::string osCanonicalQueryString(GetQueryString(true));
712✔
2262
    if (!osCanonicalQueryString.empty())
356✔
2263
        osCanonicalQueryString = osCanonicalQueryString.substr(1);
145✔
2264

2265
    const std::string osHost(m_bUseVirtualHosting && !m_osBucket.empty()
2✔
2266
                                 ? std::string(m_osBucket + "." + m_osEndpoint)
358✔
2267
                                 : m_osEndpoint);
714✔
2268
    const std::string osAuthorization =
2269
        m_osSecretAccessKey.empty()
356✔
2270
            ? std::string()
2271
            : CPLGetAWS_SIGN4_Authorization(
2272
                  m_osSecretAccessKey, m_osAccessKeyId, m_osSessionToken,
342✔
2273
                  m_osRegion, m_osRequestPayer, "s3", osVerb, psExistingHeaders,
342✔
2274
                  osHost,
2275
                  m_bUseVirtualHosting
342✔
2276
                      ? CPLAWSURLEncode("/" + m_osObjectKey, false).c_str()
356✔
2277
                      : CPLAWSURLEncode("/" + m_osBucket + "/" + m_osObjectKey,
1,724✔
2278
                                        false)
2279
                            .c_str(),
342✔
2280
                  osCanonicalQueryString, osXAMZContentSHA256,
2281
                  true,  // bAddHeaderAMZContentSHA256
2282
                  osXAMZDate);
2,080✔
2283

2284
    struct curl_slist *headers = nullptr;
356✔
2285
    headers = curl_slist_append(
356✔
2286
        headers, CPLSPrintf("x-amz-date: %s", osXAMZDate.c_str()));
2287
    headers =
2288
        curl_slist_append(headers, CPLSPrintf("x-amz-content-sha256: %s",
356✔
2289
                                              osXAMZContentSHA256.c_str()));
2290
    if (!m_osSessionToken.empty())
356✔
2291
        headers =
2292
            curl_slist_append(headers, CPLSPrintf("X-Amz-Security-Token: %s",
9✔
2293
                                                  m_osSessionToken.c_str()));
2294
    if (!m_osRequestPayer.empty())
356✔
2295
        headers =
2296
            curl_slist_append(headers, CPLSPrintf("x-amz-request-payer: %s",
2✔
2297
                                                  m_osRequestPayer.c_str()));
2298
    if (!osAuthorization.empty())
356✔
2299
    {
2300
        headers = curl_slist_append(
342✔
2301
            headers, CPLSPrintf("Authorization: %s", osAuthorization.c_str()));
2302
    }
2303
    return headers;
712✔
2304
}
2305

2306
/************************************************************************/
2307
/*                          CanRestartOnError()                         */
2308
/************************************************************************/
2309

2310
bool VSIS3HandleHelper::CanRestartOnError(const char *pszErrorMsg,
43✔
2311
                                          const char *pszHeaders,
2312
                                          bool bSetError)
2313
{
2314
#ifdef DEBUG_VERBOSE
2315
    CPLDebug("S3", "%s", pszErrorMsg);
2316
    CPLDebug("S3", "%s", pszHeaders ? pszHeaders : "");
2317
#endif
2318

2319
    if (!STARTS_WITH(pszErrorMsg, "<?xml") &&
43✔
2320
        !STARTS_WITH(pszErrorMsg, "<Error>"))
9✔
2321
    {
2322
        if (bSetError)
9✔
2323
        {
2324
            VSIError(VSIE_AWSError, "Invalid AWS response: %s", pszErrorMsg);
2✔
2325
        }
2326
        return false;
9✔
2327
    }
2328

2329
    CPLXMLNode *psTree = CPLParseXMLString(pszErrorMsg);
34✔
2330
    if (psTree == nullptr)
34✔
2331
    {
2332
        if (bSetError)
2✔
2333
        {
2334
            VSIError(VSIE_AWSError, "Malformed AWS XML response: %s",
2✔
2335
                     pszErrorMsg);
2336
        }
2337
        return false;
2✔
2338
    }
2339

2340
    const char *pszCode = CPLGetXMLValue(psTree, "=Error.Code", nullptr);
32✔
2341
    if (pszCode == nullptr)
32✔
2342
    {
2343
        CPLDestroyXMLNode(psTree);
2✔
2344
        if (bSetError)
2✔
2345
        {
2346
            VSIError(VSIE_AWSError, "Malformed AWS XML response: %s",
2✔
2347
                     pszErrorMsg);
2348
        }
2349
        return false;
2✔
2350
    }
2351

2352
    if (EQUAL(pszCode, "AuthorizationHeaderMalformed"))
30✔
2353
    {
2354
        const char *pszRegion =
2355
            CPLGetXMLValue(psTree, "=Error.Region", nullptr);
10✔
2356
        if (pszRegion == nullptr)
10✔
2357
        {
2358
            CPLDestroyXMLNode(psTree);
2✔
2359
            if (bSetError)
2✔
2360
            {
2361
                VSIError(VSIE_AWSError, "Malformed AWS XML response: %s",
2✔
2362
                         pszErrorMsg);
2363
            }
2364
            return false;
2✔
2365
        }
2366
        SetRegion(pszRegion);
8✔
2367
        CPLDebug("S3", "Switching to region %s", m_osRegion.c_str());
8✔
2368
        CPLDestroyXMLNode(psTree);
8✔
2369

2370
        VSIS3UpdateParams::UpdateMapFromHandle(this);
8✔
2371

2372
        return true;
8✔
2373
    }
2374

2375
    if (EQUAL(pszCode, "PermanentRedirect") ||
20✔
2376
        EQUAL(pszCode, "TemporaryRedirect"))
12✔
2377
    {
2378
        const bool bIsTemporaryRedirect = EQUAL(pszCode, "TemporaryRedirect");
14✔
2379
        const char *pszEndpoint =
2380
            CPLGetXMLValue(psTree, "=Error.Endpoint", nullptr);
14✔
2381
        if (pszEndpoint == nullptr ||
26✔
2382
            (m_bUseVirtualHosting && (strncmp(pszEndpoint, m_osBucket.c_str(),
12✔
2383
                                              m_osBucket.size()) != 0 ||
×
2384
                                      pszEndpoint[m_osBucket.size()] != '.')))
×
2385
        {
2386
            CPLDestroyXMLNode(psTree);
2✔
2387
            if (bSetError)
2✔
2388
            {
2389
                VSIError(VSIE_AWSError, "Malformed AWS XML response: %s",
2✔
2390
                         pszErrorMsg);
2391
            }
2392
            return false;
2✔
2393
        }
2394
        if (!m_bUseVirtualHosting &&
36✔
2395
            strncmp(pszEndpoint, m_osBucket.c_str(), m_osBucket.size()) == 0 &&
13✔
2396
            pszEndpoint[m_osBucket.size()] == '.')
1✔
2397
        {
2398
            /* If we have a body with
2399
            <Error><Code>PermanentRedirect</Code><Message>The bucket you are
2400
            attempting to access must be addressed using the specified endpoint.
2401
            Please send all future requests to this
2402
            endpoint.</Message><Bucket>bucket.with.dot</Bucket><Endpoint>bucket.with.dot.s3.amazonaws.com</Endpoint></Error>
2403
            and headers like
2404
            x-amz-bucket-region: eu-west-1
2405
            and the bucket name has dot in it,
2406
            then we must use s3.$(x-amz-bucket-region).amazon.com as endpoint.
2407
            See #7154 */
2408
            const char *pszRegionPtr =
1✔
2409
                (pszHeaders != nullptr)
2410
                    ? strstr(pszHeaders, "x-amz-bucket-region: ")
1✔
2411
                    : nullptr;
2412
            if (strchr(m_osBucket.c_str(), '.') != nullptr &&
1✔
2413
                pszRegionPtr != nullptr)
2414
            {
2415
                std::string osRegion(pszRegionPtr +
2416
                                     strlen("x-amz-bucket-region: "));
1✔
2417
                size_t nPos = osRegion.find('\r');
1✔
2418
                if (nPos != std::string::npos)
1✔
2419
                    osRegion.resize(nPos);
1✔
2420
                SetEndpoint(
1✔
2421
                    CPLSPrintf("s3.%s.amazonaws.com", osRegion.c_str()));
2422
                SetRegion(osRegion.c_str());
1✔
2423
                CPLDebug("S3", "Switching to endpoint %s",
1✔
2424
                         m_osEndpoint.c_str());
2425
                CPLDebug("S3", "Switching to region %s", m_osRegion.c_str());
1✔
2426
                CPLDestroyXMLNode(psTree);
1✔
2427
                if (!bIsTemporaryRedirect)
1✔
2428
                    VSIS3UpdateParams::UpdateMapFromHandle(this);
1✔
2429
                return true;
1✔
2430
            }
2431

2432
            m_bUseVirtualHosting = true;
×
2433
            CPLDebug("S3", "Switching to virtual hosting");
×
2434
        }
2435
        SetEndpoint(m_bUseVirtualHosting ? pszEndpoint + m_osBucket.size() + 1
11✔
2436
                                         : pszEndpoint);
2437
        CPLDebug("S3", "Switching to endpoint %s", m_osEndpoint.c_str());
11✔
2438
        CPLDestroyXMLNode(psTree);
11✔
2439

2440
        if (!bIsTemporaryRedirect)
11✔
2441
            VSIS3UpdateParams::UpdateMapFromHandle(this);
5✔
2442

2443
        return true;
11✔
2444
    }
2445

2446
    if (bSetError)
6✔
2447
    {
2448
        // Translate AWS errors into VSI errors.
2449
        const char *pszMessage =
2450
            CPLGetXMLValue(psTree, "=Error.Message", nullptr);
4✔
2451

2452
        if (pszMessage == nullptr)
4✔
2453
        {
2454
            VSIError(VSIE_AWSError, "%s", pszErrorMsg);
2✔
2455
        }
2456
        else if (EQUAL(pszCode, "AccessDenied"))
2✔
2457
        {
2458
            VSIError(VSIE_AWSAccessDenied, "%s", pszMessage);
×
2459
        }
2460
        else if (EQUAL(pszCode, "NoSuchBucket"))
2✔
2461
        {
2462
            VSIError(VSIE_AWSBucketNotFound, "%s", pszMessage);
×
2463
        }
2464
        else if (EQUAL(pszCode, "NoSuchKey"))
2✔
2465
        {
2466
            VSIError(VSIE_AWSObjectNotFound, "%s", pszMessage);
×
2467
        }
2468
        else if (EQUAL(pszCode, "SignatureDoesNotMatch"))
2✔
2469
        {
2470
            VSIError(VSIE_AWSSignatureDoesNotMatch, "%s", pszMessage);
×
2471
        }
2472
        else
2473
        {
2474
            VSIError(VSIE_AWSError, "%s", pszMessage);
2✔
2475
        }
2476
    }
2477

2478
    CPLDestroyXMLNode(psTree);
6✔
2479

2480
    return false;
6✔
2481
}
2482

2483
/************************************************************************/
2484
/*                          SetEndpoint()                          */
2485
/************************************************************************/
2486

2487
void VSIS3HandleHelper::SetEndpoint(const std::string &osStr)
64✔
2488
{
2489
    m_osEndpoint = osStr;
64✔
2490
    RebuildURL();
64✔
2491
}
64✔
2492

2493
/************************************************************************/
2494
/*                           SetRegion()                             */
2495
/************************************************************************/
2496

2497
void VSIS3HandleHelper::SetRegion(const std::string &osStr)
61✔
2498
{
2499
    m_osRegion = osStr;
61✔
2500
}
61✔
2501

2502
/************************************************************************/
2503
/*                           SetRequestPayer()                          */
2504
/************************************************************************/
2505

2506
void VSIS3HandleHelper::SetRequestPayer(const std::string &osStr)
52✔
2507
{
2508
    m_osRequestPayer = osStr;
52✔
2509
}
52✔
2510

2511
/************************************************************************/
2512
/*                         SetVirtualHosting()                          */
2513
/************************************************************************/
2514

2515
void VSIS3HandleHelper::SetVirtualHosting(bool b)
52✔
2516
{
2517
    m_bUseVirtualHosting = b;
52✔
2518
    RebuildURL();
52✔
2519
}
52✔
2520

2521
/************************************************************************/
2522
/*                           GetSignedURL()                             */
2523
/************************************************************************/
2524

2525
std::string VSIS3HandleHelper::GetSignedURL(CSLConstList papszOptions)
5✔
2526
{
2527
    std::string osPathForOption("/vsis3/");
10✔
2528
    osPathForOption += m_osBucket;
5✔
2529
    osPathForOption += '/';
5✔
2530
    osPathForOption += m_osObjectKey;
5✔
2531

2532
    std::string osXAMZDate = CSLFetchNameValueDef(
2533
        papszOptions, "START_DATE",
2534
        VSIGetPathSpecificOption(osPathForOption.c_str(), "AWS_TIMESTAMP", ""));
10✔
2535
    if (osXAMZDate.empty())
5✔
2536
        osXAMZDate = CPLGetAWS_SIGN4_Timestamp(time(nullptr));
×
2537
    std::string osDate(osXAMZDate);
10✔
2538
    osDate.resize(8);
5✔
2539

2540
    std::string osXAMZExpires =
2541
        CSLFetchNameValueDef(papszOptions, "EXPIRATION_DELAY", "3600");
10✔
2542

2543
    if (m_eCredentialsSource != AWSCredentialsSource::REGULAR)
5✔
2544
    {
2545
        // For credentials that have an expiration, we must check their
2546
        // expiration compared to the expiration of the signed URL, since
2547
        // if the effective expiration is min(desired_expiration,
2548
        // credential_expiration) Cf
2549
        // https://aws.amazon.com/premiumsupport/knowledge-center/presigned-url-s3-bucket-expiration
2550
        int nYear, nMonth, nDay, nHour = 0, nMin = 0, nSec = 0;
2✔
2551
        if (sscanf(osXAMZDate.c_str(), "%04d%02d%02dT%02d%02d%02dZ", &nYear,
2✔
2552
                   &nMonth, &nDay, &nHour, &nMin, &nSec) < 3)
2✔
2553
        {
2554
            CPLError(CE_Failure, CPLE_AppDefined, "Bad format for START_DATE");
×
2555
            return std::string();
×
2556
        }
2557
        struct tm brokendowntime;
2558
        brokendowntime.tm_year = nYear - 1900;
2✔
2559
        brokendowntime.tm_mon = nMonth - 1;
2✔
2560
        brokendowntime.tm_mday = nDay;
2✔
2561
        brokendowntime.tm_hour = nHour;
2✔
2562
        brokendowntime.tm_min = nMin;
2✔
2563
        brokendowntime.tm_sec = nSec;
2✔
2564
        const GIntBig nStartDate = CPLYMDHMSToUnixTime(&brokendowntime);
2✔
2565

2566
        {
2567
            CPLMutexHolder oHolder(&ghMutex);
4✔
2568

2569
            // Try to reuse credentials if they will still be valid after the
2570
            // desired end of the validity of the signed URL,
2571
            // with one minute of margin
2572
            if (nStartDate + CPLAtoGIntBig(osXAMZExpires.c_str()) >=
2✔
2573
                gnGlobalExpiration - 60)
2✔
2574
            {
2575
                RefreshCredentials(osPathForOption, /* bForceRefresh = */ true);
1✔
2576
            }
2577
        }
2578
    }
2579

2580
    std::string osVerb(CSLFetchNameValueDef(papszOptions, "VERB", "GET"));
10✔
2581

2582
    ResetQueryParameters();
5✔
2583
    AddQueryParameter("X-Amz-Algorithm", "AWS4-HMAC-SHA256");
5✔
2584
    AddQueryParameter("X-Amz-Credential", m_osAccessKeyId + "/" + osDate + "/" +
15✔
2585
                                              m_osRegion + "/s3/aws4_request");
15✔
2586
    AddQueryParameter("X-Amz-Date", osXAMZDate);
5✔
2587
    AddQueryParameter("X-Amz-Expires", osXAMZExpires);
5✔
2588
    if (!m_osSessionToken.empty())
5✔
2589
        AddQueryParameter("X-Amz-Security-Token", m_osSessionToken);
1✔
2590
    AddQueryParameter("X-Amz-SignedHeaders", "host");
5✔
2591

2592
    std::string osCanonicalQueryString(GetQueryString(true).substr(1));
10✔
2593

2594
    const std::string osHost(m_bUseVirtualHosting && !m_osBucket.empty()
×
2595
                                 ? std::string(m_osBucket + "." + m_osEndpoint)
5✔
2596
                                 : m_osEndpoint);
10✔
2597
    std::string osSignedHeaders;
10✔
2598
    const std::string osSignature = CPLGetAWS_SIGN4_Signature(
2599
        m_osSecretAccessKey,
5✔
2600
        std::string(),  // sessionToken set to empty as we include it in query
5✔
2601
                        // parameters
2602
        m_osRegion, m_osRequestPayer, "s3", osVerb,
5✔
2603
        nullptr, /* existing headers */
2604
        osHost,
2605
        m_bUseVirtualHosting
5✔
2606
            ? CPLAWSURLEncode("/" + m_osObjectKey, false).c_str()
5✔
2607
            : CPLAWSURLEncode("/" + m_osBucket + "/" + m_osObjectKey, false)
25✔
2608
                  .c_str(),
5✔
2609
        osCanonicalQueryString, "UNSIGNED-PAYLOAD",
2610
        false,  // bAddHeaderAMZContentSHA256
2611
        osXAMZDate, osSignedHeaders);
30✔
2612

2613
    AddQueryParameter("X-Amz-Signature", osSignature);
5✔
2614
    return m_osURL;
5✔
2615
}
2616

2617
/************************************************************************/
2618
/*                        UpdateMapFromHandle()                         */
2619
/************************************************************************/
2620

2621
std::mutex VSIS3UpdateParams::gsMutex{};
2622

2623
std::map<std::string, VSIS3UpdateParams>
2624
    VSIS3UpdateParams::goMapBucketsToS3Params{};
2625

2626
void VSIS3UpdateParams::UpdateMapFromHandle(VSIS3HandleHelper *poS3HandleHelper)
14✔
2627
{
2628
    std::lock_guard<std::mutex> guard(gsMutex);
14✔
2629

2630
    goMapBucketsToS3Params[poS3HandleHelper->GetBucket()] =
14✔
2631
        VSIS3UpdateParams(poS3HandleHelper);
28✔
2632
}
14✔
2633

2634
/************************************************************************/
2635
/*                         UpdateHandleFromMap()                        */
2636
/************************************************************************/
2637

2638
void VSIS3UpdateParams::UpdateHandleFromMap(VSIS3HandleHelper *poS3HandleHelper)
462✔
2639
{
2640
    std::lock_guard<std::mutex> guard(gsMutex);
924✔
2641

2642
    std::map<std::string, VSIS3UpdateParams>::iterator oIter =
2643
        goMapBucketsToS3Params.find(poS3HandleHelper->GetBucket());
462✔
2644
    if (oIter != goMapBucketsToS3Params.end())
462✔
2645
    {
2646
        oIter->second.UpdateHandlerHelper(poS3HandleHelper);
52✔
2647
    }
2648
}
462✔
2649

2650
/************************************************************************/
2651
/*                            ClearCache()                              */
2652
/************************************************************************/
2653

2654
void VSIS3UpdateParams::ClearCache()
1,552✔
2655
{
2656
    std::lock_guard<std::mutex> guard(gsMutex);
3,104✔
2657

2658
    goMapBucketsToS3Params.clear();
1,552✔
2659
}
1,552✔
2660

2661
#endif
2662

2663
//! @endcond
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc