Coverage Report

Created: 2026-06-30 08:33

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