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