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