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