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