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