Coverage Report

Created: 2025-08-11 09:23

/src/gdal/port/cpl_google_cloud.cpp
Line
Count
Source (jump to first uncovered line)
1
/**********************************************************************
2
 * Project:  CPL - Common Portability Library
3
 * Purpose:  Google Cloud Storage routines
4
 * Author:   Even Rouault <even.rouault at spatialys.com>
5
 *
6
 **********************************************************************
7
 * Copyright (c) 2017, Even Rouault <even.rouault at spatialys.com>
8
 *
9
 * SPDX-License-Identifier: MIT
10
 ****************************************************************************/
11
12
#include "cpl_google_cloud.h"
13
#include "cpl_vsi_error.h"
14
#include "cpl_sha1.h"
15
#include "cpl_sha256.h"
16
#include "cpl_time.h"
17
#include "cpl_http.h"
18
#include "cpl_mem_cache.h"
19
#include "cpl_aws.h"
20
#include "cpl_json.h"
21
22
#include <mutex>
23
#include <utility>
24
25
#ifdef HAVE_CURL
26
27
static bool bFirstTimeForDebugMessage = true;
28
29
struct GOA2ManagerCache
30
{
31
    struct ManagerWithMutex
32
    {
33
        std::mutex oMutex{};
34
        GOA2Manager oManager{};
35
36
        explicit ManagerWithMutex(const GOA2Manager &oManagerIn)
37
19
            : oManager(oManagerIn)
38
19
        {
39
19
        }
40
    };
41
42
    std::mutex oMutexGOA2ManagerCache{};
43
    lru11::Cache<std::string, std::shared_ptr<ManagerWithMutex>>
44
        oGOA2ManagerCache{};
45
46
    std::string GetBearer(const GOA2Manager &oManager)
47
397k
    {
48
397k
        const std::string osKey(oManager.GetKey());
49
397k
        std::shared_ptr<ManagerWithMutex> poSharedManager;
50
397k
        {
51
397k
            std::lock_guard oLock(oMutexGOA2ManagerCache);
52
397k
            if (!oGOA2ManagerCache.tryGet(osKey, poSharedManager))
53
19
            {
54
19
                poSharedManager = std::make_shared<ManagerWithMutex>(oManager);
55
19
                oGOA2ManagerCache.insert(osKey, poSharedManager);
56
19
            }
57
397k
        }
58
397k
        {
59
397k
            std::lock_guard oLock(poSharedManager->oMutex);
60
397k
            const char *pszBearer = poSharedManager->oManager.GetBearer();
61
397k
            return std::string(pszBearer ? pszBearer : "");
62
397k
        }
63
397k
    }
64
65
    void clear()
66
0
    {
67
0
        std::lock_guard oLock(oMutexGOA2ManagerCache);
68
0
        oGOA2ManagerCache.clear();
69
0
    }
70
71
    static GOA2ManagerCache &GetSingleton()
72
397k
    {
73
397k
        static GOA2ManagerCache goGOA2ManagerCache;
74
397k
        return goGOA2ManagerCache;
75
397k
    }
76
};
77
78
/************************************************************************/
79
/*                    CPLIsMachineForSureGCEInstance()                  */
80
/************************************************************************/
81
82
/** Returns whether the current machine is surely a Google Compute Engine
83
 * instance.
84
 *
85
 * This does a very quick check without network access.
86
 * Note: only works for Linux GCE instances.
87
 *
88
 * @return true if the current machine is surely a GCE instance.
89
 * @since GDAL 2.3
90
 */
91
bool CPLIsMachineForSureGCEInstance()
92
388k
{
93
388k
    if (CPLTestBool(CPLGetConfigOption("CPL_MACHINE_IS_GCE", "NO")))
94
0
    {
95
0
        return true;
96
0
    }
97
388k
#ifdef __linux
98
    // If /sys/class/dmi/id/product_name exists, it contains "Google Compute
99
    // Engine"
100
388k
    bool bIsGCEInstance = false;
101
388k
    if (CPLTestBool(CPLGetConfigOption("CPL_GCE_CHECK_LOCAL_FILES", "YES")))
102
388k
    {
103
388k
        static bool bIsGCEInstanceStatic = []()
104
388k
        {
105
19
            bool bIsGCE = false;
106
19
            VSILFILE *fp = VSIFOpenL("/sys/class/dmi/id/product_name", "rb");
107
19
            if (fp)
108
19
            {
109
19
                const char *pszLine = CPLReadLineL(fp);
110
19
                bIsGCE =
111
19
                    pszLine && STARTS_WITH_CI(pszLine, "Google Compute Engine");
112
19
                VSIFCloseL(fp);
113
19
            }
114
19
            return bIsGCE;
115
19
        }();
116
388k
        bIsGCEInstance = bIsGCEInstanceStatic;
117
388k
    }
118
388k
    return bIsGCEInstance;
119
#else
120
    return false;
121
#endif
122
388k
}
123
124
/************************************************************************/
125
/*                 CPLIsMachinePotentiallyGCEInstance()                 */
126
/************************************************************************/
127
128
/** Returns whether the current machine is potentially a Google Compute Engine
129
 * instance.
130
 *
131
 * This does a very quick check without network access. To confirm if the
132
 * machine is effectively a GCE instance, metadata.google.internal must be
133
 * queried.
134
 *
135
 * @return true if the current machine is potentially a GCE instance.
136
 * @since GDAL 2.3
137
 */
138
bool CPLIsMachinePotentiallyGCEInstance()
139
388k
{
140
388k
#ifdef __linux
141
388k
    bool bIsMachinePotentialGCEInstance = true;
142
388k
    if (CPLTestBool(CPLGetConfigOption("CPL_GCE_CHECK_LOCAL_FILES", "YES")))
143
388k
    {
144
388k
        bIsMachinePotentialGCEInstance = CPLIsMachineForSureGCEInstance();
145
388k
    }
146
388k
    return bIsMachinePotentialGCEInstance;
147
#elif defined(_WIN32)
148
    // We might add later a way of detecting if we run on GCE using WMI
149
    // See https://cloud.google.com/compute/docs/instances/managing-instances
150
    // For now, unconditionally try
151
    return true;
152
#else
153
    // At time of writing GCE instances can be only Linux or Windows
154
    return false;
155
#endif
156
388k
}
157
158
//! @cond Doxygen_Suppress
159
160
/************************************************************************/
161
/*                            GetGSHeaders()                            */
162
/************************************************************************/
163
164
static struct curl_slist *GetGSHeaders(const std::string &osPathForOption,
165
                                       const std::string &osVerb,
166
                                       struct curl_slist *psHeaders,
167
                                       const std::string &osCanonicalResource,
168
                                       const std::string &osSecretAccessKey,
169
                                       const std::string &osAccessKeyId)
170
0
{
171
0
    if (osSecretAccessKey.empty())
172
0
    {
173
        // GS_NO_SIGN_REQUEST=YES case
174
0
        return nullptr;
175
0
    }
176
177
0
    std::string osDate = VSIGetPathSpecificOption(osPathForOption.c_str(),
178
0
                                                  "CPL_GS_TIMESTAMP", "");
179
0
    if (osDate.empty())
180
0
    {
181
0
        osDate = IVSIS3LikeHandleHelper::GetRFC822DateTime();
182
0
    }
183
184
0
    std::map<std::string, std::string> oSortedMapHeaders;
185
0
    std::string osCanonicalizedHeaders(
186
0
        IVSIS3LikeHandleHelper::BuildCanonicalizedHeaders(
187
0
            oSortedMapHeaders, psHeaders, "x-goog-"));
188
189
    // See https://cloud.google.com/storage/docs/migrating
190
0
    std::string osStringToSign;
191
0
    osStringToSign += osVerb + "\n";
192
0
    osStringToSign += CPLAWSGetHeaderVal(psHeaders, "Content-MD5") + "\n";
193
0
    osStringToSign += CPLAWSGetHeaderVal(psHeaders, "Content-Type") + "\n";
194
0
    osStringToSign += osDate + "\n";
195
0
    osStringToSign += osCanonicalizedHeaders;
196
0
    osStringToSign += osCanonicalResource;
197
#ifdef DEBUG_VERBOSE
198
    CPLDebug("GS", "osStringToSign = %s", osStringToSign.c_str());
199
#endif
200
201
0
    GByte abySignature[CPL_SHA1_HASH_SIZE] = {};
202
0
    CPL_HMAC_SHA1(osSecretAccessKey.c_str(), osSecretAccessKey.size(),
203
0
                  osStringToSign.c_str(), osStringToSign.size(), abySignature);
204
205
0
    char *pszBase64 = CPLBase64Encode(sizeof(abySignature), abySignature);
206
0
    std::string osAuthorization("GOOG1 ");
207
0
    osAuthorization += osAccessKeyId;
208
0
    osAuthorization += ":";
209
0
    osAuthorization += pszBase64;
210
0
    CPLFree(pszBase64);
211
212
0
    psHeaders =
213
0
        curl_slist_append(psHeaders, CPLSPrintf("Date: %s", osDate.c_str()));
214
0
    psHeaders = curl_slist_append(
215
0
        psHeaders, CPLSPrintf("Authorization: %s", osAuthorization.c_str()));
216
217
0
    return psHeaders;
218
0
}
219
220
/************************************************************************/
221
/*                         VSIGSHandleHelper()                          */
222
/************************************************************************/
223
VSIGSHandleHelper::VSIGSHandleHelper(const std::string &osEndpoint,
224
                                     const std::string &osBucketObjectKey,
225
                                     const std::string &osSecretAccessKey,
226
                                     const std::string &osAccessKeyId,
227
                                     bool bUseAuthenticationHeader,
228
                                     const GOA2Manager &oManager,
229
                                     const std::string &osUserProject)
230
387k
    : m_osURL(osEndpoint + CPLAWSURLEncode(osBucketObjectKey, false)),
231
387k
      m_osEndpoint(osEndpoint), m_osBucketObjectKey(osBucketObjectKey),
232
387k
      m_osSecretAccessKey(osSecretAccessKey), m_osAccessKeyId(osAccessKeyId),
233
387k
      m_bUseAuthenticationHeader(bUseAuthenticationHeader),
234
387k
      m_oManager(oManager), m_osUserProject(osUserProject)
235
387k
{
236
387k
    if (m_osBucketObjectKey.find('/') == std::string::npos)
237
28.8k
        m_osURL += "/";
238
387k
}
239
240
/************************************************************************/
241
/*                        ~VSIGSHandleHelper()                          */
242
/************************************************************************/
243
244
VSIGSHandleHelper::~VSIGSHandleHelper()
245
387k
{
246
387k
}
247
248
/************************************************************************/
249
/*                GetConfigurationFromAWSConfigFiles()                  */
250
/************************************************************************/
251
252
bool VSIGSHandleHelper::GetConfigurationFromConfigFile(
253
    std::string &osSecretAccessKey, std::string &osAccessKeyId,
254
    std::string &osOAuth2RefreshToken, std::string &osOAuth2ClientId,
255
    std::string &osOAuth2ClientSecret, std::string &osCredentials)
256
387k
{
257
#ifdef _WIN32
258
    const char *pszHome = CPLGetConfigOption("USERPROFILE", nullptr);
259
    constexpr char SEP_STRING[] = "\\";
260
#else
261
387k
    const char *pszHome = CPLGetConfigOption("HOME", nullptr);
262
387k
    constexpr char SEP_STRING[] = "/";
263
387k
#endif
264
265
    // GDAL specific config option (mostly for testing purpose, but also
266
    // used in production in some cases)
267
387k
    const char *pszCredentials =
268
387k
        CPLGetConfigOption("CPL_GS_CREDENTIALS_FILE", nullptr);
269
387k
    if (pszCredentials)
270
0
    {
271
0
        osCredentials = pszCredentials;
272
0
    }
273
387k
    else
274
387k
    {
275
387k
        osCredentials = pszHome ? pszHome : "";
276
387k
        osCredentials += SEP_STRING;
277
387k
        osCredentials += ".boto";
278
387k
    }
279
280
387k
    VSILFILE *fp = VSIFOpenL(osCredentials.c_str(), "rb");
281
387k
    if (fp != nullptr)
282
0
    {
283
0
        const char *pszLine;
284
0
        bool bInCredentials = false;
285
0
        bool bInOAuth2 = false;
286
0
        while ((pszLine = CPLReadLineL(fp)) != nullptr)
287
0
        {
288
0
            if (pszLine[0] == '[')
289
0
            {
290
0
                bInCredentials = false;
291
0
                bInOAuth2 = false;
292
293
0
                if (std::string(pszLine) == "[Credentials]")
294
0
                    bInCredentials = true;
295
0
                else if (std::string(pszLine) == "[OAuth2]")
296
0
                    bInOAuth2 = true;
297
0
            }
298
0
            else if (bInCredentials)
299
0
            {
300
0
                char *pszKey = nullptr;
301
0
                const char *pszValue = CPLParseNameValue(pszLine, &pszKey);
302
0
                if (pszKey && pszValue)
303
0
                {
304
0
                    if (EQUAL(pszKey, "gs_access_key_id"))
305
0
                        osAccessKeyId = CPLString(pszValue).Trim();
306
0
                    else if (EQUAL(pszKey, "gs_secret_access_key"))
307
0
                        osSecretAccessKey = CPLString(pszValue).Trim();
308
0
                    else if (EQUAL(pszKey, "gs_oauth2_refresh_token"))
309
0
                        osOAuth2RefreshToken = CPLString(pszValue).Trim();
310
0
                }
311
0
                CPLFree(pszKey);
312
0
            }
313
0
            else if (bInOAuth2)
314
0
            {
315
0
                char *pszKey = nullptr;
316
0
                const char *pszValue = CPLParseNameValue(pszLine, &pszKey);
317
0
                if (pszKey && pszValue)
318
0
                {
319
0
                    if (EQUAL(pszKey, "client_id"))
320
0
                        osOAuth2ClientId = CPLString(pszValue).Trim();
321
0
                    else if (EQUAL(pszKey, "client_secret"))
322
0
                        osOAuth2ClientSecret = CPLString(pszValue).Trim();
323
0
                }
324
0
                CPLFree(pszKey);
325
0
            }
326
0
        }
327
0
        VSIFCloseL(fp);
328
0
    }
329
330
387k
    return (!osAccessKeyId.empty() && !osSecretAccessKey.empty()) ||
331
387k
           !osOAuth2RefreshToken.empty();
332
387k
}
333
334
/************************************************************************/
335
/*                        GetConfiguration()                            */
336
/************************************************************************/
337
338
bool VSIGSHandleHelper::GetConfiguration(const std::string &osPathForOption,
339
                                         CSLConstList papszOptions,
340
                                         std::string &osSecretAccessKey,
341
                                         std::string &osAccessKeyId,
342
                                         bool &bUseAuthenticationHeader,
343
                                         GOA2Manager &oManager)
344
387k
{
345
387k
    osSecretAccessKey.clear();
346
387k
    osAccessKeyId.clear();
347
387k
    bUseAuthenticationHeader = false;
348
349
387k
    if (CPLTestBool(VSIGetPathSpecificOption(osPathForOption.c_str(),
350
387k
                                             "GS_NO_SIGN_REQUEST", "NO")))
351
0
    {
352
0
        return true;
353
0
    }
354
355
387k
    osSecretAccessKey = VSIGetPathSpecificOption(osPathForOption.c_str(),
356
387k
                                                 "GS_SECRET_ACCESS_KEY", "");
357
387k
    if (!osSecretAccessKey.empty())
358
0
    {
359
0
        osAccessKeyId = VSIGetPathSpecificOption(osPathForOption.c_str(),
360
0
                                                 "GS_ACCESS_KEY_ID", "");
361
0
        if (osAccessKeyId.empty())
362
0
        {
363
0
            VSIError(VSIE_InvalidCredentials,
364
0
                     "GS_ACCESS_KEY_ID configuration option not defined");
365
0
            bFirstTimeForDebugMessage = false;
366
0
            return false;
367
0
        }
368
369
0
        if (bFirstTimeForDebugMessage)
370
0
        {
371
0
            CPLDebug("GS", "Using GS_SECRET_ACCESS_KEY and "
372
0
                           "GS_ACCESS_KEY_ID configuration options");
373
0
        }
374
0
        bFirstTimeForDebugMessage = false;
375
0
        return true;
376
0
    }
377
378
387k
    const std::string osHeaderFile = VSIGetPathSpecificOption(
379
387k
        osPathForOption.c_str(), "GDAL_HTTP_HEADER_FILE", "");
380
387k
    bool bMayWarnDidNotFindAuth = false;
381
387k
    if (!osHeaderFile.empty())
382
0
    {
383
0
        bool bFoundAuth = false;
384
0
        VSILFILE *fp = nullptr;
385
        // Do not allow /vsicurl/ access from /vsicurl because of
386
        // GetCurlHandleFor() e.g. "/vsicurl/,HEADER_FILE=/vsicurl/,url= " would
387
        // cause use of memory after free
388
0
        if (strstr(osHeaderFile.c_str(), "/vsicurl/") == nullptr &&
389
0
            strstr(osHeaderFile.c_str(), "/vsicurl?") == nullptr &&
390
0
            strstr(osHeaderFile.c_str(), "/vsis3/") == nullptr &&
391
0
            strstr(osHeaderFile.c_str(), "/vsigs/") == nullptr &&
392
0
            strstr(osHeaderFile.c_str(), "/vsiaz/") == nullptr &&
393
0
            strstr(osHeaderFile.c_str(), "/vsioss/") == nullptr &&
394
0
            strstr(osHeaderFile.c_str(), "/vsiswift/") == nullptr)
395
0
        {
396
0
            fp = VSIFOpenL(osHeaderFile.c_str(), "rb");
397
0
        }
398
0
        if (fp == nullptr)
399
0
        {
400
0
            CPLError(CE_Failure, CPLE_FileIO, "Cannot read %s",
401
0
                     osHeaderFile.c_str());
402
0
        }
403
0
        else
404
0
        {
405
0
            const char *pszLine = nullptr;
406
0
            while ((pszLine = CPLReadLineL(fp)) != nullptr)
407
0
            {
408
0
                if (STARTS_WITH_CI(pszLine, "Authorization:"))
409
0
                {
410
0
                    bFoundAuth = true;
411
0
                    break;
412
0
                }
413
0
            }
414
0
            VSIFCloseL(fp);
415
0
            if (!bFoundAuth)
416
0
                bMayWarnDidNotFindAuth = true;
417
0
        }
418
0
        if (bFoundAuth)
419
0
        {
420
0
            if (bFirstTimeForDebugMessage)
421
0
            {
422
0
                CPLDebug("GS", "Using GDAL_HTTP_HEADER_FILE=%s",
423
0
                         osHeaderFile.c_str());
424
0
            }
425
0
            bFirstTimeForDebugMessage = false;
426
0
            bUseAuthenticationHeader = true;
427
0
            return true;
428
0
        }
429
0
    }
430
431
387k
    const char *pszHeaders = VSIGetPathSpecificOption(
432
387k
        osPathForOption.c_str(), "GDAL_HTTP_HEADERS", nullptr);
433
387k
    if (pszHeaders && strstr(pszHeaders, "Authorization:") != nullptr)
434
0
    {
435
0
        bUseAuthenticationHeader = true;
436
0
        return true;
437
0
    }
438
439
387k
    std::string osRefreshToken(VSIGetPathSpecificOption(
440
387k
        osPathForOption.c_str(), "GS_OAUTH2_REFRESH_TOKEN", ""));
441
387k
    if (!osRefreshToken.empty())
442
0
    {
443
0
        std::string osClientId = VSIGetPathSpecificOption(
444
0
            osPathForOption.c_str(), "GS_OAUTH2_CLIENT_ID", "");
445
0
        std::string osClientSecret = VSIGetPathSpecificOption(
446
0
            osPathForOption.c_str(), "GS_OAUTH2_CLIENT_SECRET", "");
447
448
0
        int nCount =
449
0
            (!osClientId.empty() ? 1 : 0) + (!osClientSecret.empty() ? 1 : 0);
450
0
        if (nCount == 1)
451
0
        {
452
0
            CPLError(CE_Failure, CPLE_NotSupported,
453
0
                     "Either both or none of GS_OAUTH2_CLIENT_ID and "
454
0
                     "GS_OAUTH2_CLIENT_SECRET must be set");
455
0
            return false;
456
0
        }
457
458
0
        if (bFirstTimeForDebugMessage)
459
0
        {
460
0
            std::string osMsg(
461
0
                "Using GS_OAUTH2_REFRESH_TOKEN configuration option");
462
0
            if (osClientId.empty())
463
0
                osMsg += " and GDAL default client_id/client_secret";
464
0
            else
465
0
                osMsg += " and GS_OAUTH2_CLIENT_ID and GS_OAUTH2_CLIENT_SECRET";
466
0
            CPLDebug("GS", "%s", osMsg.c_str());
467
0
        }
468
0
        bFirstTimeForDebugMessage = false;
469
470
0
        return oManager.SetAuthFromRefreshToken(
471
0
            osRefreshToken.c_str(), osClientId.c_str(), osClientSecret.c_str(),
472
0
            nullptr);
473
0
    }
474
475
387k
    std::string osJsonFile(CSLFetchNameValueDef(
476
387k
        papszOptions, "GOOGLE_APPLICATION_CREDENTIALS",
477
387k
        VSIGetPathSpecificOption(osPathForOption.c_str(),
478
387k
                                 "GOOGLE_APPLICATION_CREDENTIALS", "")));
479
387k
    if (!osJsonFile.empty())
480
0
    {
481
0
        CPLJSONDocument oDoc;
482
0
        if (!oDoc.Load(osJsonFile))
483
0
        {
484
0
            return false;
485
0
        }
486
487
        // JSON file can be of type 'service_account' or 'authorized_user'
488
0
        std::string osJsonFileType = oDoc.GetRoot().GetString("type");
489
490
0
        if (strcmp(osJsonFileType.c_str(), "service_account") == 0)
491
0
        {
492
0
            CPLString osPrivateKey = oDoc.GetRoot().GetString("private_key");
493
0
            osPrivateKey.replaceAll("\\n", "\n")
494
0
                .replaceAll("\n\n", "\n")
495
0
                .replaceAll("\r", "");
496
0
            std::string osClientEmail =
497
0
                oDoc.GetRoot().GetString("client_email");
498
0
            const char *pszScope = CSLFetchNameValueDef(
499
0
                papszOptions, "GS_OAUTH2_SCOPE",
500
0
                VSIGetPathSpecificOption(
501
0
                    osPathForOption.c_str(), "GS_OAUTH2_SCOPE",
502
0
                    "https://www.googleapis.com/auth/devstorage.read_write"));
503
504
0
            return oManager.SetAuthFromServiceAccount(
505
0
                osPrivateKey.c_str(), osClientEmail.c_str(), pszScope, nullptr,
506
0
                nullptr);
507
0
        }
508
0
        else if (strcmp(osJsonFileType.c_str(), "authorized_user") == 0)
509
0
        {
510
0
            std::string osClientId = oDoc.GetRoot().GetString("client_id");
511
0
            std::string osClientSecret =
512
0
                oDoc.GetRoot().GetString("client_secret");
513
0
            osRefreshToken = oDoc.GetRoot().GetString("refresh_token");
514
515
0
            return oManager.SetAuthFromRefreshToken(
516
0
                osRefreshToken.c_str(), osClientId.c_str(),
517
0
                osClientSecret.c_str(), nullptr);
518
0
        }
519
0
        return false;
520
0
    }
521
522
387k
    CPLString osPrivateKey = CSLFetchNameValueDef(
523
387k
        papszOptions, "GS_OAUTH2_PRIVATE_KEY",
524
387k
        VSIGetPathSpecificOption(osPathForOption.c_str(),
525
387k
                                 "GS_OAUTH2_PRIVATE_KEY", ""));
526
387k
    std::string osPrivateKeyFile = CSLFetchNameValueDef(
527
387k
        papszOptions, "GS_OAUTH2_PRIVATE_KEY_FILE",
528
387k
        VSIGetPathSpecificOption(osPathForOption.c_str(),
529
387k
                                 "GS_OAUTH2_PRIVATE_KEY_FILE", ""));
530
387k
    if (!osPrivateKey.empty() || !osPrivateKeyFile.empty())
531
0
    {
532
0
        if (!osPrivateKeyFile.empty())
533
0
        {
534
0
            VSILFILE *fp = VSIFOpenL(osPrivateKeyFile.c_str(), "rb");
535
0
            if (fp == nullptr)
536
0
            {
537
0
                CPLError(CE_Failure, CPLE_FileIO, "Cannot open %s",
538
0
                         osPrivateKeyFile.c_str());
539
0
                bFirstTimeForDebugMessage = false;
540
0
                return false;
541
0
            }
542
0
            else
543
0
            {
544
0
                char *pabyBuffer = static_cast<char *>(CPLMalloc(32768));
545
0
                size_t nRead = VSIFReadL(pabyBuffer, 1, 32768, fp);
546
0
                osPrivateKey.assign(pabyBuffer, nRead);
547
0
                VSIFCloseL(fp);
548
0
                CPLFree(pabyBuffer);
549
0
            }
550
0
        }
551
0
        osPrivateKey.replaceAll("\\n", "\n")
552
0
            .replaceAll("\n\n", "\n")
553
0
            .replaceAll("\r", "");
554
555
0
        std::string osClientEmail = CSLFetchNameValueDef(
556
0
            papszOptions, "GS_OAUTH2_CLIENT_EMAIL",
557
0
            VSIGetPathSpecificOption(osPathForOption.c_str(),
558
0
                                     "GS_OAUTH2_CLIENT_EMAIL", ""));
559
0
        if (osClientEmail.empty())
560
0
        {
561
0
            CPLError(CE_Failure, CPLE_AppDefined,
562
0
                     "GS_OAUTH2_CLIENT_EMAIL not defined");
563
0
            bFirstTimeForDebugMessage = false;
564
0
            return false;
565
0
        }
566
0
        const char *pszScope = CSLFetchNameValueDef(
567
0
            papszOptions, "GS_OAUTH2_SCOPE",
568
0
            VSIGetPathSpecificOption(
569
0
                osPathForOption.c_str(), "GS_OAUTH2_SCOPE",
570
0
                "https://www.googleapis.com/auth/devstorage.read_write"));
571
572
0
        if (bFirstTimeForDebugMessage)
573
0
        {
574
0
            CPLDebug("GS",
575
0
                     "Using %s, GS_OAUTH2_CLIENT_EMAIL and GS_OAUTH2_SCOPE=%s "
576
0
                     "configuration options",
577
0
                     !osPrivateKeyFile.empty() ? "GS_OAUTH2_PRIVATE_KEY_FILE"
578
0
                                               : "GS_OAUTH2_PRIVATE_KEY",
579
0
                     pszScope);
580
0
        }
581
0
        bFirstTimeForDebugMessage = false;
582
583
0
        return oManager.SetAuthFromServiceAccount(osPrivateKey.c_str(),
584
0
                                                  osClientEmail.c_str(),
585
0
                                                  pszScope, nullptr, nullptr);
586
0
    }
587
588
    // Next try reading from ~/.boto
589
387k
    std::string osCredentials;
590
387k
    std::string osOAuth2RefreshToken;
591
387k
    std::string osOAuth2ClientId;
592
387k
    std::string osOAuth2ClientSecret;
593
387k
    if (GetConfigurationFromConfigFile(osSecretAccessKey, osAccessKeyId,
594
387k
                                       osOAuth2RefreshToken, osOAuth2ClientId,
595
387k
                                       osOAuth2ClientSecret, osCredentials))
596
0
    {
597
0
        if (!osOAuth2RefreshToken.empty())
598
0
        {
599
0
            std::string osClientId =
600
0
                CPLGetConfigOption("GS_OAUTH2_CLIENT_ID", "");
601
0
            std::string osClientSecret =
602
0
                CPLGetConfigOption("GS_OAUTH2_CLIENT_SECRET", "");
603
0
            bool bClientInfoFromEnv = false;
604
0
            bool bClientInfoFromFile = false;
605
606
0
            const int nCountClientIdSecret = (!osClientId.empty() ? 1 : 0) +
607
0
                                             (!osClientSecret.empty() ? 1 : 0);
608
0
            if (nCountClientIdSecret == 1)
609
0
            {
610
0
                CPLError(CE_Failure, CPLE_NotSupported,
611
0
                         "Either both or none of GS_OAUTH2_CLIENT_ID and "
612
0
                         "GS_OAUTH2_CLIENT_SECRET must be set");
613
0
                return false;
614
0
            }
615
0
            else if (nCountClientIdSecret == 2)
616
0
            {
617
0
                bClientInfoFromEnv = true;
618
0
            }
619
0
            else if (nCountClientIdSecret == 0)
620
0
            {
621
0
                int nCountOAuth2IdSecret = (!osOAuth2ClientId.empty() ? 1 : 0);
622
0
                nCountOAuth2IdSecret += (!osOAuth2ClientSecret.empty() ? 1 : 0);
623
0
                if (nCountOAuth2IdSecret == 1)
624
0
                {
625
0
                    CPLError(CE_Failure, CPLE_NotSupported,
626
0
                             "Either both or none of client_id and "
627
0
                             "client_secret from %s must be set",
628
0
                             osCredentials.c_str());
629
0
                    return false;
630
0
                }
631
0
                else if (nCountOAuth2IdSecret == 2)
632
0
                {
633
0
                    osClientId = std::move(osOAuth2ClientId);
634
0
                    osClientSecret = std::move(osOAuth2ClientSecret);
635
0
                    bClientInfoFromFile = true;
636
0
                }
637
0
            }
638
639
0
            if (bFirstTimeForDebugMessage)
640
0
            {
641
0
                CPLString osMsg;
642
0
                osMsg.Printf("Using gs_oauth2_refresh_token from %s",
643
0
                             osCredentials.c_str());
644
0
                if (bClientInfoFromEnv)
645
0
                    osMsg += " and GS_OAUTH2_CLIENT_ID and "
646
0
                             "GS_OAUTH2_CLIENT_SECRET configuration options";
647
0
                else if (bClientInfoFromFile)
648
0
                    osMsg +=
649
0
                        CPLSPrintf(" and client_id and client_secret from %s",
650
0
                                   osCredentials.c_str());
651
0
                else
652
0
                    osMsg += " and GDAL default client_id/client_secret";
653
0
                CPLDebug("GS", "%s", osMsg.c_str());
654
0
            }
655
0
            bFirstTimeForDebugMessage = false;
656
657
0
            return oManager.SetAuthFromRefreshToken(
658
0
                osOAuth2RefreshToken.c_str(), osClientId.c_str(),
659
0
                osClientSecret.c_str(), nullptr);
660
0
        }
661
0
        else
662
0
        {
663
0
            if (bFirstTimeForDebugMessage)
664
0
            {
665
0
                CPLDebug(
666
0
                    "GS",
667
0
                    "Using gs_access_key_id and gs_secret_access_key from %s",
668
0
                    osCredentials.c_str());
669
0
            }
670
0
            bFirstTimeForDebugMessage = false;
671
0
            return true;
672
0
        }
673
0
    }
674
675
    // Some Travis-CI workers are GCE machines, and for some tests, we don't
676
    // want this code path to be taken. And on AppVeyor/Window, we would also
677
    // attempt a network access
678
387k
    if (!CPLTestBool(CPLGetConfigOption("CPL_GCE_SKIP", "NO")) &&
679
387k
        CPLIsMachinePotentiallyGCEInstance())
680
387k
    {
681
387k
        oManager.SetAuthFromGCE(nullptr);
682
683
387k
        if (!GOA2ManagerCache::GetSingleton().GetBearer(oManager).empty())
684
387k
        {
685
387k
            CPLDebug("GS", "Using GCE inherited permissions");
686
687
387k
            bFirstTimeForDebugMessage = false;
688
387k
            return true;
689
387k
        }
690
387k
    }
691
692
0
    if (bMayWarnDidNotFindAuth)
693
0
    {
694
0
        CPLDebug("GS", "Cannot find Authorization header in %s",
695
0
                 CPLGetConfigOption("GDAL_HTTP_HEADER_FILE", ""));
696
0
    }
697
698
0
    CPLString osMsg;
699
0
    osMsg.Printf(
700
0
        "No valid GCS credentials found. "
701
0
        "For authenticated requests, you need to set "
702
0
        "GS_SECRET_ACCESS_KEY, GS_ACCESS_KEY_ID, GS_OAUTH2_REFRESH_TOKEN, "
703
0
        "GOOGLE_APPLICATION_CREDENTIALS, or other configuration "
704
0
        "options, or create a %s file. Consult "
705
0
        "https://gdal.org/en/stable/user/"
706
0
        "virtual_file_systems.html#vsigs-google-cloud-storage-files "
707
0
        "for more details. "
708
0
        "For unauthenticated requests on public resources, set the "
709
0
        "GS_NO_SIGN_REQUEST configuration option to YES.",
710
0
        osCredentials.c_str());
711
712
0
    CPLDebug("GS", "%s", osMsg.c_str());
713
0
    VSIError(VSIE_InvalidCredentials, "%s", osMsg.c_str());
714
0
    return false;
715
387k
}
716
717
/************************************************************************/
718
/*                          BuildFromURI()                              */
719
/************************************************************************/
720
721
VSIGSHandleHelper *VSIGSHandleHelper::BuildFromURI(
722
    const char *pszURI, const char * /*pszFSPrefix*/,
723
    const char *pszURIForPathSpecificOption, CSLConstList papszOptions)
724
387k
{
725
387k
    std::string osPathForOption("/vsigs/");
726
387k
    osPathForOption +=
727
387k
        pszURIForPathSpecificOption ? pszURIForPathSpecificOption : pszURI;
728
729
    // pszURI == bucket/object
730
387k
    const std::string osBucketObject(pszURI);
731
387k
    std::string osEndpoint(VSIGetPathSpecificOption(osPathForOption.c_str(),
732
387k
                                                    "CPL_GS_ENDPOINT", ""));
733
387k
    if (osEndpoint.empty())
734
387k
        osEndpoint = "https://storage.googleapis.com/";
735
736
387k
    std::string osSecretAccessKey;
737
387k
    std::string osAccessKeyId;
738
387k
    bool bUseAuthenticationHeader;
739
387k
    GOA2Manager oManager;
740
741
387k
    if (!GetConfiguration(osPathForOption, papszOptions, osSecretAccessKey,
742
387k
                          osAccessKeyId, bUseAuthenticationHeader, oManager))
743
0
    {
744
0
        return nullptr;
745
0
    }
746
747
    // https://cloud.google.com/storage/docs/xml-api/reference-headers#xgooguserproject
748
    // The Project ID for an existing Google Cloud project to bill for access
749
    // charges associated with the request.
750
387k
    const std::string osUserProject = VSIGetPathSpecificOption(
751
387k
        osPathForOption.c_str(), "GS_USER_PROJECT", "");
752
753
387k
    return new VSIGSHandleHelper(osEndpoint, osBucketObject, osSecretAccessKey,
754
387k
                                 osAccessKeyId, bUseAuthenticationHeader,
755
387k
                                 oManager, osUserProject);
756
387k
}
757
758
/************************************************************************/
759
/*                           RebuildURL()                               */
760
/************************************************************************/
761
762
void VSIGSHandleHelper::RebuildURL()
763
18.5k
{
764
18.5k
    m_osURL = m_osEndpoint + CPLAWSURLEncode(m_osBucketObjectKey, false);
765
18.5k
    if (!m_osBucketObjectKey.empty() &&
766
18.5k
        m_osBucketObjectKey.find('/') == std::string::npos)
767
15.4k
        m_osURL += "/";
768
18.5k
    m_osURL += GetQueryString(false);
769
18.5k
}
770
771
/************************************************************************/
772
/*                           UsesHMACKey()                              */
773
/************************************************************************/
774
775
bool VSIGSHandleHelper::UsesHMACKey() const
776
0
{
777
0
    return m_oManager.GetAuthMethod() == GOA2Manager::NONE;
778
0
}
779
780
/************************************************************************/
781
/*                           GetCurlHeaders()                           */
782
/************************************************************************/
783
784
struct curl_slist *
785
VSIGSHandleHelper::GetCurlHeaders(const std::string &osVerb,
786
                                  struct curl_slist *psHeaders, const void *,
787
                                  size_t) const
788
9.51k
{
789
9.51k
    if (m_bUseAuthenticationHeader)
790
0
        return psHeaders;
791
792
9.51k
    if (!m_osUserProject.empty())
793
0
    {
794
0
        psHeaders =
795
0
            curl_slist_append(psHeaders, CPLSPrintf("x-goog-user-project: %s",
796
0
                                                    m_osUserProject.c_str()));
797
0
    }
798
799
9.51k
    if (m_oManager.GetAuthMethod() != GOA2Manager::NONE)
800
9.51k
    {
801
9.51k
        const std::string osBearer =
802
9.51k
            GOA2ManagerCache::GetSingleton().GetBearer(m_oManager);
803
9.51k
        if (!osBearer.empty())
804
9.51k
        {
805
9.51k
            psHeaders = curl_slist_append(
806
9.51k
                psHeaders,
807
9.51k
                CPLSPrintf("Authorization: Bearer %s", osBearer.c_str()));
808
9.51k
        }
809
9.51k
        return psHeaders;
810
9.51k
    }
811
812
0
    std::string osCanonicalResource(
813
0
        "/" + CPLAWSURLEncode(m_osBucketObjectKey, false));
814
0
    if (!m_osBucketObjectKey.empty() &&
815
0
        m_osBucketObjectKey.find('/') == std::string::npos)
816
0
        osCanonicalResource += "/";
817
0
    else
818
0
    {
819
0
        const auto osQueryString(GetQueryString(false));
820
0
        if (osQueryString == "?uploads" || osQueryString == "?acl")
821
0
            osCanonicalResource += osQueryString;
822
0
    }
823
824
0
    return GetGSHeaders("/vsigs/" + m_osBucketObjectKey, osVerb, psHeaders,
825
0
                        osCanonicalResource, m_osSecretAccessKey,
826
0
                        m_osAccessKeyId);
827
9.51k
}
828
829
/************************************************************************/
830
/*                          ClearCache()                                */
831
/************************************************************************/
832
833
void VSIGSHandleHelper::ClearCache()
834
0
{
835
0
    GOA2ManagerCache::GetSingleton().clear();
836
0
    bFirstTimeForDebugMessage = true;
837
0
}
838
839
/************************************************************************/
840
/*                           GetSignedURL()                             */
841
/************************************************************************/
842
843
std::string VSIGSHandleHelper::GetSignedURL(CSLConstList papszOptions)
844
0
{
845
0
    if (!((!m_osAccessKeyId.empty() && !m_osSecretAccessKey.empty()) ||
846
0
          m_oManager.GetAuthMethod() == GOA2Manager::SERVICE_ACCOUNT))
847
0
    {
848
0
        CPLError(CE_Failure, CPLE_NotSupported,
849
0
                 "Signed URL for Google Cloud Storage is only available with "
850
0
                 "AWS style authentication with "
851
0
                 "GS_ACCESS_KEY_ID+GS_SECRET_ACCESS_KEY, "
852
0
                 "or with service account authentication");
853
0
        return std::string();
854
0
    }
855
856
0
    GIntBig nStartDate = static_cast<GIntBig>(time(nullptr));
857
0
    const char *pszStartDate = CSLFetchNameValue(papszOptions, "START_DATE");
858
0
    if (pszStartDate)
859
0
    {
860
0
        int nYear, nMonth, nDay, nHour, nMin, nSec;
861
0
        if (sscanf(pszStartDate, "%04d%02d%02dT%02d%02d%02dZ", &nYear, &nMonth,
862
0
                   &nDay, &nHour, &nMin, &nSec) == 6)
863
0
        {
864
0
            struct tm brokendowntime;
865
0
            brokendowntime.tm_year = nYear - 1900;
866
0
            brokendowntime.tm_mon = nMonth - 1;
867
0
            brokendowntime.tm_mday = nDay;
868
0
            brokendowntime.tm_hour = nHour;
869
0
            brokendowntime.tm_min = nMin;
870
0
            brokendowntime.tm_sec = nSec;
871
0
            nStartDate = CPLYMDHMSToUnixTime(&brokendowntime);
872
0
        }
873
0
    }
874
0
    GIntBig nExpiresIn =
875
0
        nStartDate +
876
0
        atoi(CSLFetchNameValueDef(papszOptions, "EXPIRATION_DELAY", "3600"));
877
0
    std::string osExpires(CSLFetchNameValueDef(
878
0
        papszOptions, "EXPIRES", CPLSPrintf(CPL_FRMT_GIB, nExpiresIn)));
879
880
0
    std::string osVerb(CSLFetchNameValueDef(papszOptions, "VERB", "GET"));
881
882
0
    std::string osCanonicalizedResource(
883
0
        "/" + CPLAWSURLEncode(m_osBucketObjectKey, false));
884
885
0
    std::string osStringToSign;
886
0
    osStringToSign += osVerb + "\n";
887
0
    osStringToSign += "\n";  // Content_MD5
888
0
    osStringToSign += "\n";  // Content_Type
889
0
    osStringToSign += osExpires + "\n";
890
    // osStringToSign += // Canonicalized_Extension_Headers
891
0
    osStringToSign += osCanonicalizedResource;
892
#ifdef DEBUG_VERBOSE
893
    CPLDebug("GS", "osStringToSign = %s", osStringToSign.c_str());
894
#endif
895
896
0
    if (!m_osAccessKeyId.empty())
897
0
    {
898
        // No longer documented but actually works !
899
0
        GByte abySignature[CPL_SHA1_HASH_SIZE] = {};
900
0
        CPL_HMAC_SHA1(m_osSecretAccessKey.c_str(), m_osSecretAccessKey.size(),
901
0
                      osStringToSign.c_str(), osStringToSign.size(),
902
0
                      abySignature);
903
904
0
        char *pszBase64 = CPLBase64Encode(sizeof(abySignature), abySignature);
905
0
        std::string osSignature(pszBase64);
906
0
        CPLFree(pszBase64);
907
908
0
        ResetQueryParameters();
909
0
        AddQueryParameter("GoogleAccessId", m_osAccessKeyId);
910
0
        AddQueryParameter("Expires", osExpires);
911
0
        AddQueryParameter("Signature", osSignature);
912
0
    }
913
0
    else
914
0
    {
915
0
        unsigned nSignatureLen = 0;
916
0
        GByte *pabySignature = CPL_RSA_SHA256_Sign(
917
0
            m_oManager.GetPrivateKey().c_str(), osStringToSign.data(),
918
0
            static_cast<unsigned>(osStringToSign.size()), &nSignatureLen);
919
0
        if (pabySignature == nullptr)
920
0
            return std::string();
921
0
        char *pszBase64 = CPLBase64Encode(nSignatureLen, pabySignature);
922
0
        CPLFree(pabySignature);
923
0
        std::string osSignature(pszBase64);
924
0
        CPLFree(pszBase64);
925
926
0
        ResetQueryParameters();
927
0
        AddQueryParameter("GoogleAccessId", m_oManager.GetClientEmail());
928
0
        AddQueryParameter("Expires", osExpires);
929
0
        AddQueryParameter("Signature", osSignature);
930
0
    }
931
0
    return m_osURL;
932
0
}
933
934
#endif
935
936
//! @endcond