Line data Source code
1 : /**********************************************************************
2 : * Project: CPL - Common Portability Library
3 : * Purpose: Microsoft Azure Storage Blob 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 : * DEALINAzureBlob IN THE SOFTWARE.
26 : ****************************************************************************/
27 :
28 : #include "cpl_azure.h"
29 : #include "cpl_json.h"
30 : #include "cpl_minixml.h"
31 : #include "cpl_vsi_error.h"
32 : #include "cpl_sha256.h"
33 : #include "cpl_time.h"
34 : #include "cpl_http.h"
35 : #include "cpl_multiproc.h"
36 : #include "cpl_vsi_virtual.h"
37 :
38 : #include <mutex>
39 :
40 : //! @cond Doxygen_Suppress
41 :
42 : #ifdef HAVE_CURL
43 :
44 : constexpr const char *X_MS_VERSION = "2019-12-12";
45 :
46 : /************************************************************************/
47 : /* RemoveTrailingSlash() */
48 : /************************************************************************/
49 :
50 592 : static std::string RemoveTrailingSlash(const std::string &osStr)
51 : {
52 592 : std::string osRet(osStr);
53 592 : if (!osRet.empty() && osRet.back() == '/')
54 1 : osRet.pop_back();
55 592 : return osRet;
56 : }
57 :
58 : /************************************************************************/
59 : /* CPLAzureGetSignature() */
60 : /************************************************************************/
61 :
62 218 : static std::string CPLAzureGetSignature(const std::string &osStringToSign,
63 : const std::string &osStorageKeyB64)
64 : {
65 :
66 : /* -------------------------------------------------------------------- */
67 : /* Compute signature. */
68 : /* -------------------------------------------------------------------- */
69 :
70 436 : std::string osStorageKeyUnbase64(osStorageKeyB64);
71 436 : int nB64Length = CPLBase64DecodeInPlace(
72 218 : reinterpret_cast<GByte *>(&osStorageKeyUnbase64[0]));
73 218 : osStorageKeyUnbase64.resize(nB64Length);
74 : #ifdef DEBUG_VERBOSE
75 : CPLDebug("AZURE", "signing key size: %d", nB64Length);
76 : #endif
77 :
78 218 : GByte abySignature[CPL_SHA256_HASH_SIZE] = {};
79 436 : CPL_HMAC_SHA256(osStorageKeyUnbase64.c_str(), nB64Length,
80 218 : osStringToSign.c_str(), osStringToSign.size(),
81 : abySignature);
82 :
83 218 : char *pszB64Signature = CPLBase64Encode(CPL_SHA256_HASH_SIZE, abySignature);
84 218 : std::string osSignature(pszB64Signature);
85 218 : CPLFree(pszB64Signature);
86 436 : return osSignature;
87 : }
88 :
89 : /************************************************************************/
90 : /* GetAzureBlobHeaders() */
91 : /************************************************************************/
92 :
93 224 : static struct curl_slist *GetAzureBlobHeaders(
94 : const std::string &osVerb, struct curl_slist *psHeaders,
95 : const std::string &osResource,
96 : const std::map<std::string, std::string> &oMapQueryParameters,
97 : const std::string &osStorageAccount, const std::string &osStorageKeyB64,
98 : bool bIncludeMSVersion)
99 : {
100 : /* See
101 : * https://docs.microsoft.com/en-us/rest/api/storageservices/authentication-for-the-azure-storage-services
102 : */
103 :
104 224 : const auto AddHeaders = [bIncludeMSVersion](struct curl_slist *l_psHeaders,
105 224 : const std::string &osDate)
106 : {
107 224 : l_psHeaders = curl_slist_append(
108 : l_psHeaders, CPLSPrintf("x-ms-date: %s", osDate.c_str()));
109 224 : if (bIncludeMSVersion)
110 : {
111 217 : l_psHeaders = curl_slist_append(
112 : l_psHeaders, CPLSPrintf("x-ms-version: %s", X_MS_VERSION));
113 : }
114 224 : return l_psHeaders;
115 224 : };
116 :
117 448 : std::string osDate = CPLGetConfigOption("CPL_AZURE_TIMESTAMP", "");
118 224 : if (osDate.empty())
119 : {
120 7 : osDate = IVSIS3LikeHandleHelper::GetRFC822DateTime();
121 : }
122 :
123 224 : if (osStorageKeyB64.empty())
124 : {
125 10 : psHeaders = AddHeaders(psHeaders, osDate);
126 10 : return psHeaders;
127 : }
128 :
129 428 : std::map<std::string, std::string> oSortedMapMSHeaders;
130 214 : if (bIncludeMSVersion)
131 207 : oSortedMapMSHeaders["x-ms-version"] = X_MS_VERSION;
132 214 : oSortedMapMSHeaders["x-ms-date"] = osDate;
133 : std::string osCanonicalizedHeaders(
134 : IVSIS3LikeHandleHelper::BuildCanonicalizedHeaders(oSortedMapMSHeaders,
135 428 : psHeaders, "x-ms-"));
136 :
137 428 : std::string osCanonicalizedResource;
138 214 : osCanonicalizedResource += "/" + osStorageAccount;
139 214 : osCanonicalizedResource += osResource;
140 :
141 : // We assume query parameters are in lower case and they are not repeated
142 : std::map<std::string, std::string>::const_iterator oIter =
143 214 : oMapQueryParameters.begin();
144 551 : for (; oIter != oMapQueryParameters.end(); ++oIter)
145 : {
146 337 : osCanonicalizedResource += "\n";
147 337 : osCanonicalizedResource += oIter->first;
148 337 : osCanonicalizedResource += ":";
149 337 : osCanonicalizedResource += oIter->second;
150 : }
151 :
152 428 : std::string osStringToSign;
153 214 : osStringToSign += osVerb + "\n";
154 214 : osStringToSign += CPLAWSGetHeaderVal(psHeaders, "Content-Encoding") + "\n";
155 214 : osStringToSign += CPLAWSGetHeaderVal(psHeaders, "Content-Language") + "\n";
156 : std::string osContentLength(
157 428 : CPLAWSGetHeaderVal(psHeaders, "Content-Length"));
158 214 : if (osContentLength == "0")
159 34 : osContentLength.clear(); // since x-ms-version 2015-02-21
160 214 : osStringToSign += osContentLength + "\n";
161 214 : osStringToSign += CPLAWSGetHeaderVal(psHeaders, "Content-MD5") + "\n";
162 214 : osStringToSign += CPLAWSGetHeaderVal(psHeaders, "Content-Type") + "\n";
163 214 : osStringToSign += CPLAWSGetHeaderVal(psHeaders, "Date") + "\n";
164 214 : osStringToSign += CPLAWSGetHeaderVal(psHeaders, "If-Modified-Since") + "\n";
165 214 : osStringToSign += CPLAWSGetHeaderVal(psHeaders, "If-Match") + "\n";
166 214 : osStringToSign += CPLAWSGetHeaderVal(psHeaders, "If-None-Match") + "\n";
167 : osStringToSign +=
168 214 : CPLAWSGetHeaderVal(psHeaders, "If-Unmodified-Since") + "\n";
169 214 : osStringToSign += CPLAWSGetHeaderVal(psHeaders, "Range") + "\n";
170 214 : osStringToSign += osCanonicalizedHeaders;
171 214 : osStringToSign += osCanonicalizedResource;
172 :
173 : #ifdef DEBUG_VERBOSE
174 : CPLDebug("AZURE", "osStringToSign = '%s'", osStringToSign.c_str());
175 : #endif
176 :
177 : /* -------------------------------------------------------------------- */
178 : /* Compute signature. */
179 : /* -------------------------------------------------------------------- */
180 :
181 : std::string osAuthorization(
182 428 : "SharedKey " + osStorageAccount + ":" +
183 428 : CPLAzureGetSignature(osStringToSign, osStorageKeyB64));
184 :
185 214 : psHeaders = AddHeaders(psHeaders, osDate);
186 214 : psHeaders = curl_slist_append(
187 : psHeaders, CPLSPrintf("Authorization: %s", osAuthorization.c_str()));
188 :
189 214 : return psHeaders;
190 : }
191 :
192 : /************************************************************************/
193 : /* VSIAzureBlobHandleHelper() */
194 : /************************************************************************/
195 303 : VSIAzureBlobHandleHelper::VSIAzureBlobHandleHelper(
196 : const std::string &osPathForOption, const std::string &osEndpoint,
197 : const std::string &osBucket, const std::string &osObjectKey,
198 : const std::string &osStorageAccount, const std::string &osStorageKey,
199 : const std::string &osSAS, const std::string &osAccessToken,
200 303 : bool bFromManagedIdentities)
201 : : m_osPathForOption(osPathForOption),
202 : m_osURL(BuildURL(osEndpoint, osBucket, osObjectKey, osSAS)),
203 : m_osEndpoint(osEndpoint), m_osBucket(osBucket),
204 : m_osObjectKey(osObjectKey), m_osStorageAccount(osStorageAccount),
205 : m_osStorageKey(osStorageKey), m_osSAS(osSAS),
206 : m_osAccessToken(osAccessToken),
207 303 : m_bFromManagedIdentities(bFromManagedIdentities)
208 : {
209 303 : }
210 :
211 : /************************************************************************/
212 : /* ~VSIAzureBlobHandleHelper() */
213 : /************************************************************************/
214 :
215 606 : VSIAzureBlobHandleHelper::~VSIAzureBlobHandleHelper()
216 : {
217 606 : }
218 :
219 : /************************************************************************/
220 : /* AzureCSGetParameter() */
221 : /************************************************************************/
222 :
223 1095 : static std::string AzureCSGetParameter(const std::string &osStr,
224 : const char *pszKey, bool bErrorIfMissing)
225 : {
226 3285 : std::string osKey(pszKey + std::string("="));
227 1095 : size_t nPos = osStr.find(osKey);
228 1095 : if (nPos == std::string::npos)
229 : {
230 : const char *pszMsg =
231 11 : CPLSPrintf("%s missing in AZURE_STORAGE_CONNECTION_STRING", pszKey);
232 11 : if (bErrorIfMissing)
233 : {
234 0 : CPLDebug("AZURE", "%s", pszMsg);
235 0 : VSIError(VSIE_InvalidCredentials, "%s", pszMsg);
236 : }
237 11 : return std::string();
238 : }
239 1084 : size_t nPos2 = osStr.find(";", nPos);
240 1084 : return osStr.substr(nPos + osKey.size(), nPos2 == std::string::npos
241 : ? nPos2
242 2168 : : nPos2 - nPos - osKey.size());
243 : }
244 :
245 : /************************************************************************/
246 : /* CPLAzureCachedToken */
247 : /************************************************************************/
248 :
249 : std::mutex gMutex;
250 :
251 : struct CPLAzureCachedToken
252 : {
253 : std::string osAccessToken{};
254 : GIntBig nExpiresOn = 0;
255 : };
256 :
257 : static std::map<std::string, CPLAzureCachedToken> goMapIMDSURLToCachedToken;
258 :
259 : /************************************************************************/
260 : /* GetConfigurationFromIMDSCredentials() */
261 : /************************************************************************/
262 :
263 : static bool
264 15 : GetConfigurationFromIMDSCredentials(const std::string &osPathForOption,
265 : std::string &osAccessToken)
266 : {
267 : // coverity[tainted_data]
268 : const std::string osRootURL(CPLGetConfigOption("CPL_AZURE_VM_API_ROOT_URL",
269 30 : "http://169.254.169.254"));
270 15 : if (osRootURL == "disabled")
271 1 : return false;
272 :
273 : std::string osURLResource("/metadata/identity/oauth2/"
274 : "token?api-version=2018-02-01&resource=https%"
275 28 : "3A%2F%2Fstorage.azure.com%2F");
276 14 : const char *pszObjectId = VSIGetPathSpecificOption(
277 : osPathForOption.c_str(), "AZURE_IMDS_OBJECT_ID", nullptr);
278 14 : if (pszObjectId)
279 8 : osURLResource += "&object_id=" + CPLAWSURLEncode(pszObjectId, false);
280 14 : const char *pszClientId = VSIGetPathSpecificOption(
281 : osPathForOption.c_str(), "AZURE_IMDS_CLIENT_ID", nullptr);
282 14 : if (pszClientId)
283 8 : osURLResource += "&client_id=" + CPLAWSURLEncode(pszClientId, false);
284 14 : const char *pszMsiResId = VSIGetPathSpecificOption(
285 : osPathForOption.c_str(), "AZURE_IMDS_MSI_RES_ID", nullptr);
286 14 : if (pszMsiResId)
287 8 : osURLResource += "&msi_res_id=" + CPLAWSURLEncode(pszMsiResId, false);
288 :
289 28 : std::lock_guard<std::mutex> guard(gMutex);
290 :
291 : // Look for cached token corresponding to this IMDS request URL
292 14 : auto oIter = goMapIMDSURLToCachedToken.find(osURLResource);
293 14 : if (oIter != goMapIMDSURLToCachedToken.end())
294 : {
295 10 : const auto &oCachedToken = oIter->second;
296 : time_t nCurTime;
297 10 : time(&nCurTime);
298 : // Try to reuse credentials if they are still valid, but
299 : // keep one minute of margin...
300 10 : if (nCurTime < oCachedToken.nExpiresOn - 60)
301 : {
302 9 : osAccessToken = oCachedToken.osAccessToken;
303 9 : return true;
304 : }
305 : }
306 :
307 : // Fetch credentials
308 5 : CPLStringList oResponse;
309 5 : const char *const apszOptions[] = {"HEADERS=Metadata: true", nullptr};
310 : CPLHTTPResult *psResult =
311 5 : CPLHTTPFetch((osRootURL + osURLResource).c_str(), apszOptions);
312 5 : if (psResult)
313 : {
314 5 : if (psResult->nStatus == 0 && psResult->pabyData != nullptr)
315 : {
316 : const std::string osJSon =
317 10 : reinterpret_cast<char *>(psResult->pabyData);
318 5 : oResponse = CPLParseKeyValueJson(osJSon.c_str());
319 5 : if (oResponse.FetchNameValue("error"))
320 : {
321 0 : CPLDebug("AZURE",
322 : "Cannot retrieve managed identities credentials: %s",
323 : osJSon.c_str());
324 : }
325 : }
326 5 : CPLHTTPDestroyResult(psResult);
327 : }
328 5 : osAccessToken = oResponse.FetchNameValueDef("access_token", "");
329 : const GIntBig nExpiresOn =
330 5 : CPLAtoGIntBig(oResponse.FetchNameValueDef("expires_on", ""));
331 5 : if (!osAccessToken.empty() && nExpiresOn > 0)
332 : {
333 10 : CPLAzureCachedToken cachedToken;
334 5 : cachedToken.osAccessToken = osAccessToken;
335 5 : cachedToken.nExpiresOn = nExpiresOn;
336 5 : goMapIMDSURLToCachedToken[osURLResource] = std::move(cachedToken);
337 5 : CPLDebug("AZURE", "Storing credentials for %s until " CPL_FRMT_GIB,
338 : osURLResource.c_str(), nExpiresOn);
339 : }
340 :
341 5 : return !osAccessToken.empty();
342 : }
343 :
344 : /************************************************************************/
345 : /* GetConfigurationFromWorkloadIdentity() */
346 : /************************************************************************/
347 :
348 : // Last timestamp AZURE_FEDERATED_TOKEN_FILE was read
349 : static GIntBig gnLastReadFederatedTokenFile = 0;
350 : static std::string gosFederatedToken{};
351 :
352 : // Azure Active Directory Workload Identity, typically for Azure Kubernetes
353 : // Cf https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/identity/azure-identity/azure/identity/_credentials/workload_identity.py
354 23 : static bool GetConfigurationFromWorkloadIdentity(std::string &osAccessToken)
355 : {
356 : const std::string AZURE_CLIENT_ID(
357 46 : CPLGetConfigOption("AZURE_CLIENT_ID", ""));
358 : const std::string AZURE_TENANT_ID(
359 46 : CPLGetConfigOption("AZURE_TENANT_ID", ""));
360 : const std::string AZURE_AUTHORITY_HOST(
361 46 : CPLGetConfigOption("AZURE_AUTHORITY_HOST", ""));
362 : const std::string AZURE_FEDERATED_TOKEN_FILE(
363 46 : CPLGetConfigOption("AZURE_FEDERATED_TOKEN_FILE", ""));
364 39 : if (AZURE_CLIENT_ID.empty() || AZURE_TENANT_ID.empty() ||
365 39 : AZURE_AUTHORITY_HOST.empty() || AZURE_FEDERATED_TOKEN_FILE.empty())
366 : {
367 15 : return false;
368 : }
369 :
370 16 : std::lock_guard<std::mutex> guard(gMutex);
371 :
372 : time_t nCurTime;
373 8 : time(&nCurTime);
374 :
375 : // Look for cached token corresponding to this request URL
376 8 : const std::string osURL(AZURE_AUTHORITY_HOST + AZURE_TENANT_ID +
377 16 : "/oauth2/v2.0/token");
378 8 : auto oIter = goMapIMDSURLToCachedToken.find(osURL);
379 8 : if (oIter != goMapIMDSURLToCachedToken.end())
380 : {
381 6 : const auto &oCachedToken = oIter->second;
382 : // Try to reuse credentials if they are still valid, but
383 : // keep one minute of margin...
384 6 : if (nCurTime < oCachedToken.nExpiresOn - 60)
385 : {
386 4 : osAccessToken = oCachedToken.osAccessToken;
387 4 : return true;
388 : }
389 : }
390 :
391 : // Ingest content of AZURE_FEDERATED_TOKEN_FILE if last time was more than
392 : // 600 seconds.
393 4 : if (nCurTime - gnLastReadFederatedTokenFile > 600)
394 : {
395 : auto fp = VSIVirtualHandleUniquePtr(
396 2 : VSIFOpenL(AZURE_FEDERATED_TOKEN_FILE.c_str(), "rb"));
397 2 : if (!fp)
398 : {
399 0 : CPLDebug("AZURE", "Cannot open AZURE_FEDERATED_TOKEN_FILE = %s",
400 : AZURE_FEDERATED_TOKEN_FILE.c_str());
401 0 : return false;
402 : }
403 2 : fp->Seek(0, SEEK_END);
404 2 : const auto nSize = fp->Tell();
405 2 : if (nSize == 0 || nSize > 100 * 1024)
406 : {
407 0 : CPLDebug(
408 : "AZURE",
409 : "Invalid size for AZURE_FEDERATED_TOKEN_FILE = " CPL_FRMT_GUIB,
410 : static_cast<GUIntBig>(nSize));
411 0 : return false;
412 : }
413 2 : fp->Seek(0, SEEK_SET);
414 2 : gosFederatedToken.resize(static_cast<size_t>(nSize));
415 2 : if (fp->Read(&gosFederatedToken[0], gosFederatedToken.size(), 1) != 1)
416 : {
417 0 : CPLDebug("AZURE", "Cannot read AZURE_FEDERATED_TOKEN_FILE");
418 0 : return false;
419 : }
420 2 : gnLastReadFederatedTokenFile = nCurTime;
421 : }
422 :
423 : /* -------------------------------------------------------------------- */
424 : /* Prepare POST request. */
425 : /* -------------------------------------------------------------------- */
426 8 : CPLStringList aosOptions;
427 :
428 : aosOptions.AddString(
429 4 : "HEADERS=Content-Type: application/x-www-form-urlencoded");
430 :
431 8 : std::string osItem("POSTFIELDS=client_assertion=");
432 4 : osItem += CPLAWSURLEncode(gosFederatedToken);
433 : osItem += "&client_assertion_type=urn:ietf:params:oauth:client-assertion-"
434 4 : "type:jwt-bearer";
435 4 : osItem += "&client_id=";
436 4 : osItem += CPLAWSURLEncode(AZURE_CLIENT_ID);
437 4 : osItem += "&grant_type=client_credentials";
438 4 : osItem += "&scope=https://storage.azure.com/.default";
439 4 : aosOptions.AddString(osItem.c_str());
440 :
441 : /* -------------------------------------------------------------------- */
442 : /* Submit request by HTTP. */
443 : /* -------------------------------------------------------------------- */
444 4 : CPLHTTPResult *psResult = CPLHTTPFetch(osURL.c_str(), aosOptions.List());
445 4 : if (!psResult)
446 0 : return false;
447 :
448 4 : if (!psResult->pabyData || psResult->pszErrBuf)
449 : {
450 0 : if (psResult->pszErrBuf)
451 0 : CPLDebug("AZURE", "%s", psResult->pszErrBuf);
452 0 : if (psResult->pabyData)
453 0 : CPLDebug("AZURE", "%s", psResult->pabyData);
454 :
455 0 : CPLDebug("AZURE",
456 : "Fetching OAuth2 access code from workload identity failed.");
457 0 : CPLHTTPDestroyResult(psResult);
458 0 : return false;
459 : }
460 :
461 : CPLStringList oResponse =
462 4 : CPLParseKeyValueJson(reinterpret_cast<char *>(psResult->pabyData));
463 4 : CPLHTTPDestroyResult(psResult);
464 :
465 4 : osAccessToken = oResponse.FetchNameValueDef("access_token", "");
466 4 : const int nExpiresIn = atoi(oResponse.FetchNameValueDef("expires_in", ""));
467 4 : if (!osAccessToken.empty() && nExpiresIn > 0)
468 : {
469 8 : CPLAzureCachedToken cachedToken;
470 4 : cachedToken.osAccessToken = osAccessToken;
471 4 : cachedToken.nExpiresOn = nCurTime + nExpiresIn;
472 4 : goMapIMDSURLToCachedToken[osURL] = cachedToken;
473 4 : CPLDebug("AZURE", "Storing credentials for %s until " CPL_FRMT_GIB,
474 : osURL.c_str(), cachedToken.nExpiresOn);
475 : }
476 :
477 4 : return !osAccessToken.empty();
478 : }
479 :
480 : /************************************************************************/
481 : /* GetConfigurationFromManagedIdentities() */
482 : /************************************************************************/
483 :
484 : static bool
485 23 : GetConfigurationFromManagedIdentities(const std::string &osPathForOption,
486 : std::string &osAccessToken)
487 : {
488 23 : if (GetConfigurationFromWorkloadIdentity(osAccessToken))
489 8 : return true;
490 15 : return GetConfigurationFromIMDSCredentials(osPathForOption, osAccessToken);
491 : }
492 :
493 : /************************************************************************/
494 : /* ClearCache() */
495 : /************************************************************************/
496 :
497 736 : void VSIAzureBlobHandleHelper::ClearCache()
498 : {
499 1472 : std::lock_guard<std::mutex> guard(gMutex);
500 736 : goMapIMDSURLToCachedToken.clear();
501 736 : gnLastReadFederatedTokenFile = 0;
502 736 : gosFederatedToken.clear();
503 736 : }
504 :
505 : /************************************************************************/
506 : /* ParseStorageConnectionString() */
507 : /************************************************************************/
508 :
509 : static bool
510 273 : ParseStorageConnectionString(const std::string &osStorageConnectionString,
511 : const std::string &osServicePrefix,
512 : bool &bUseHTTPS, std::string &osEndpoint,
513 : std::string &osStorageAccount,
514 : std::string &osStorageKey, std::string &osSAS)
515 : {
516 : osStorageAccount =
517 273 : AzureCSGetParameter(osStorageConnectionString, "AccountName", false);
518 : osStorageKey =
519 273 : AzureCSGetParameter(osStorageConnectionString, "AccountKey", false);
520 :
521 : const std::string osProtocol(AzureCSGetParameter(
522 546 : osStorageConnectionString, "DefaultEndpointsProtocol", false));
523 273 : bUseHTTPS = (osProtocol != "http");
524 :
525 273 : if (osStorageAccount.empty() || osStorageKey.empty())
526 : {
527 3 : osStorageAccount.clear();
528 3 : osStorageKey.clear();
529 :
530 3 : std::string osBlobEndpoint = RemoveTrailingSlash(AzureCSGetParameter(
531 6 : osStorageConnectionString, "BlobEndpoint", false));
532 6 : osSAS = AzureCSGetParameter(osStorageConnectionString,
533 3 : "SharedAccessSignature", false);
534 3 : if (!osBlobEndpoint.empty() && !osSAS.empty())
535 : {
536 2 : osEndpoint = std::move(osBlobEndpoint);
537 2 : return true;
538 : }
539 :
540 1 : return false;
541 : }
542 :
543 : const std::string osBlobEndpoint =
544 270 : AzureCSGetParameter(osStorageConnectionString, "BlobEndpoint", false);
545 270 : if (!osBlobEndpoint.empty())
546 : {
547 270 : osEndpoint = RemoveTrailingSlash(osBlobEndpoint);
548 : }
549 : else
550 : {
551 : const std::string osEndpointSuffix(AzureCSGetParameter(
552 0 : osStorageConnectionString, "EndpointSuffix", false));
553 0 : if (!osEndpointSuffix.empty())
554 0 : osEndpoint = (bUseHTTPS ? "https://" : "http://") +
555 0 : osStorageAccount + "." + osServicePrefix + "." +
556 0 : RemoveTrailingSlash(osEndpointSuffix);
557 : }
558 :
559 270 : return true;
560 : }
561 :
562 : /************************************************************************/
563 : /* GetConfigurationFromCLIConfigFile() */
564 : /************************************************************************/
565 :
566 17 : static bool GetConfigurationFromCLIConfigFile(
567 : const std::string &osPathForOption, const std::string &osServicePrefix,
568 : bool &bUseHTTPS, std::string &osEndpoint, std::string &osStorageAccount,
569 : std::string &osStorageKey, std::string &osSAS, std::string &osAccessToken,
570 : bool &bFromManagedIdentities)
571 : {
572 : #ifdef _WIN32
573 : const char *pszHome = CPLGetConfigOption("USERPROFILE", nullptr);
574 : constexpr char SEP_STRING[] = "\\";
575 : #else
576 17 : const char *pszHome = CPLGetConfigOption("HOME", nullptr);
577 17 : constexpr char SEP_STRING[] = "/";
578 : #endif
579 :
580 34 : std::string osDotAzure(pszHome ? pszHome : "");
581 17 : osDotAzure += SEP_STRING;
582 17 : osDotAzure += ".azure";
583 :
584 : const char *pszAzureConfigDir =
585 17 : CPLGetConfigOption("AZURE_CONFIG_DIR", osDotAzure.c_str());
586 17 : if (pszAzureConfigDir[0] == '\0')
587 5 : return false;
588 :
589 24 : std::string osConfigFilename = pszAzureConfigDir;
590 12 : osConfigFilename += SEP_STRING;
591 12 : osConfigFilename += "config";
592 :
593 12 : VSILFILE *fp = VSIFOpenL(osConfigFilename.c_str(), "rb");
594 24 : std::string osStorageConnectionString;
595 12 : if (fp == nullptr)
596 8 : return false;
597 :
598 4 : bool bInStorageSection = false;
599 25 : while (const char *pszLine = CPLReadLineL(fp))
600 : {
601 21 : if (pszLine[0] == '#' || pszLine[0] == ';')
602 : {
603 : // comment line
604 : }
605 21 : else if (strcmp(pszLine, "[storage]") == 0)
606 : {
607 4 : bInStorageSection = true;
608 : }
609 17 : else if (pszLine[0] == '[')
610 : {
611 4 : bInStorageSection = false;
612 : }
613 13 : else if (bInStorageSection)
614 : {
615 5 : char *pszKey = nullptr;
616 5 : const char *pszValue = CPLParseNameValue(pszLine, &pszKey);
617 5 : if (pszKey && pszValue)
618 : {
619 5 : if (EQUAL(pszKey, "account"))
620 : {
621 2 : osStorageAccount = pszValue;
622 : }
623 3 : else if (EQUAL(pszKey, "connection_string"))
624 : {
625 1 : osStorageConnectionString = pszValue;
626 : }
627 2 : else if (EQUAL(pszKey, "key"))
628 : {
629 1 : osStorageKey = pszValue;
630 : }
631 1 : else if (EQUAL(pszKey, "sas_token"))
632 : {
633 1 : osSAS = pszValue;
634 : // Az CLI apparently uses configparser with
635 : // BasicInterpolation where the % character has a special
636 : // meaning See
637 : // https://docs.python.org/3/library/configparser.html#configparser.BasicInterpolation
638 : // A token might end with %%3D which must be transformed to
639 : // %3D
640 1 : osSAS = CPLString(osSAS).replaceAll("%%", '%');
641 : }
642 : }
643 5 : CPLFree(pszKey);
644 : }
645 21 : }
646 4 : VSIFCloseL(fp);
647 :
648 4 : if (!osStorageConnectionString.empty())
649 : {
650 1 : return ParseStorageConnectionString(
651 : osStorageConnectionString, osServicePrefix, bUseHTTPS, osEndpoint,
652 1 : osStorageAccount, osStorageKey, osSAS);
653 : }
654 :
655 3 : if (osStorageAccount.empty())
656 : {
657 1 : CPLDebug("AZURE", "Missing storage.account in %s",
658 : osConfigFilename.c_str());
659 1 : return false;
660 : }
661 :
662 2 : if (osEndpoint.empty())
663 0 : osEndpoint = (bUseHTTPS ? "https://" : "http://") + osStorageAccount +
664 0 : "." + osServicePrefix + ".core.windows.net";
665 :
666 2 : osAccessToken = CPLGetConfigOption("AZURE_STORAGE_ACCESS_TOKEN", "");
667 2 : if (!osAccessToken.empty())
668 0 : return true;
669 :
670 2 : if (osStorageKey.empty() && osSAS.empty())
671 : {
672 0 : if (CPLTestBool(CPLGetConfigOption("AZURE_NO_SIGN_REQUEST", "NO")))
673 : {
674 0 : return true;
675 : }
676 :
677 0 : std::string osTmpAccessToken;
678 0 : if (GetConfigurationFromManagedIdentities(osPathForOption,
679 : osTmpAccessToken))
680 : {
681 0 : bFromManagedIdentities = true;
682 0 : return true;
683 : }
684 :
685 0 : CPLDebug("AZURE", "Missing storage.key or storage.sas_token in %s",
686 : osConfigFilename.c_str());
687 0 : return false;
688 : }
689 :
690 2 : return true;
691 : }
692 :
693 : /************************************************************************/
694 : /* GetSAS() */
695 : /************************************************************************/
696 :
697 : /* static */
698 31 : std::string VSIAzureBlobHandleHelper::GetSAS(const char *pszFilename)
699 : {
700 : return VSIGetPathSpecificOption(
701 : pszFilename, "AZURE_STORAGE_SAS_TOKEN",
702 : CPLGetConfigOption("AZURE_SAS",
703 31 : "")); // AZURE_SAS for GDAL < 3.5
704 : }
705 :
706 : /************************************************************************/
707 : /* IsNoSignRequest() */
708 : /************************************************************************/
709 :
710 : /* static */
711 326 : bool VSIAzureBlobHandleHelper::IsNoSignRequest(const char *pszFilename)
712 : {
713 326 : return CPLTestBool(
714 326 : VSIGetPathSpecificOption(pszFilename, "AZURE_NO_SIGN_REQUEST", "NO"));
715 : }
716 :
717 : /************************************************************************/
718 : /* GetConfiguration() */
719 : /************************************************************************/
720 :
721 319 : bool VSIAzureBlobHandleHelper::GetConfiguration(
722 : const std::string &osPathForOption, CSLConstList papszOptions,
723 : Service eService, bool &bUseHTTPS, std::string &osEndpoint,
724 : std::string &osStorageAccount, std::string &osStorageKey,
725 : std::string &osSAS, std::string &osAccessToken,
726 : bool &bFromManagedIdentities)
727 : {
728 319 : bFromManagedIdentities = false;
729 :
730 : const std::string osServicePrefix(
731 638 : eService == Service::SERVICE_BLOB ? "blob" : "dfs");
732 319 : bUseHTTPS = CPLTestBool(VSIGetPathSpecificOption(
733 : osPathForOption.c_str(), "CPL_AZURE_USE_HTTPS", "YES"));
734 638 : osEndpoint = RemoveTrailingSlash(VSIGetPathSpecificOption(
735 319 : osPathForOption.c_str(), "CPL_AZURE_ENDPOINT", ""));
736 :
737 : const std::string osStorageConnectionString(CSLFetchNameValueDef(
738 : papszOptions, "AZURE_STORAGE_CONNECTION_STRING",
739 : VSIGetPathSpecificOption(osPathForOption.c_str(),
740 638 : "AZURE_STORAGE_CONNECTION_STRING", "")));
741 319 : if (!osStorageConnectionString.empty())
742 : {
743 272 : return ParseStorageConnectionString(
744 : osStorageConnectionString, osServicePrefix, bUseHTTPS, osEndpoint,
745 272 : osStorageAccount, osStorageKey, osSAS);
746 : }
747 : else
748 : {
749 : osStorageAccount = CSLFetchNameValueDef(
750 : papszOptions, "AZURE_STORAGE_ACCOUNT",
751 : VSIGetPathSpecificOption(osPathForOption.c_str(),
752 47 : "AZURE_STORAGE_ACCOUNT", ""));
753 47 : if (!osStorageAccount.empty())
754 : {
755 30 : if (osEndpoint.empty())
756 30 : osEndpoint = (bUseHTTPS ? "https://" : "http://") +
757 30 : osStorageAccount + "." + osServicePrefix +
758 15 : ".core.windows.net";
759 :
760 : osAccessToken = CSLFetchNameValueDef(
761 : papszOptions, "AZURE_STORAGE_ACCESS_TOKEN",
762 : VSIGetPathSpecificOption(osPathForOption.c_str(),
763 30 : "AZURE_STORAGE_ACCESS_TOKEN", ""));
764 30 : if (!osAccessToken.empty())
765 1 : return true;
766 :
767 : osStorageKey = CSLFetchNameValueDef(
768 : papszOptions, "AZURE_STORAGE_ACCESS_KEY",
769 : VSIGetPathSpecificOption(osPathForOption.c_str(),
770 29 : "AZURE_STORAGE_ACCESS_KEY", ""));
771 29 : if (osStorageKey.empty())
772 : {
773 25 : osSAS = GetSAS(osPathForOption.c_str());
774 25 : if (osSAS.empty())
775 : {
776 17 : if (IsNoSignRequest(osPathForOption.c_str()))
777 : {
778 5 : return true;
779 : }
780 :
781 24 : std::string osTmpAccessToken;
782 12 : if (GetConfigurationFromManagedIdentities(osPathForOption,
783 : osTmpAccessToken))
784 : {
785 11 : bFromManagedIdentities = true;
786 11 : return true;
787 : }
788 :
789 1 : const char *pszMsg =
790 : "AZURE_STORAGE_ACCESS_KEY or AZURE_STORAGE_SAS_TOKEN "
791 : "or AZURE_NO_SIGN_REQUEST configuration option "
792 : "not defined";
793 1 : CPLDebug("AZURE", "%s", pszMsg);
794 1 : VSIError(VSIE_InvalidCredentials, "%s", pszMsg);
795 1 : return false;
796 : }
797 : }
798 12 : return true;
799 : }
800 : }
801 :
802 17 : if (GetConfigurationFromCLIConfigFile(
803 : osPathForOption, osServicePrefix, bUseHTTPS, osEndpoint,
804 : osStorageAccount, osStorageKey, osSAS, osAccessToken,
805 : bFromManagedIdentities))
806 : {
807 3 : return true;
808 : }
809 :
810 14 : const char *pszMsg =
811 : "No valid Azure credentials found. "
812 : "For authenticated requests, you need to set "
813 : "AZURE_STORAGE_ACCOUNT, AZURE_STORAGE_ACCESS_KEY, "
814 : "AZURE_STORAGE_SAS_TOKEN, "
815 : "AZURE_STORAGE_CONNECTION_STRING, or other configuration "
816 : "options. Consult "
817 : "https://gdal.org/en/stable/user/"
818 : "virtual_file_systems.html#vsiaz-microsoft-azure-blob-files "
819 : "for more details. "
820 : "For unauthenticated requests on public resources, set the "
821 : "AZURE_NO_SIGN_REQUEST configuration option to YES.";
822 14 : CPLDebug("AZURE", "%s", pszMsg);
823 14 : VSIError(VSIE_InvalidCredentials, "%s", pszMsg);
824 14 : return false;
825 : }
826 :
827 : /************************************************************************/
828 : /* BuildFromURI() */
829 : /************************************************************************/
830 :
831 319 : VSIAzureBlobHandleHelper *VSIAzureBlobHandleHelper::BuildFromURI(
832 : const char *pszURI, const char *pszFSPrefix,
833 : const char *pszURIForPathSpecificOption, CSLConstList papszOptions)
834 : {
835 319 : if (strcmp(pszFSPrefix, "/vsiaz/") != 0 &&
836 101 : strcmp(pszFSPrefix, "/vsiaz_streaming/") != 0 &&
837 91 : strcmp(pszFSPrefix, "/vsiadls/") != 0)
838 : {
839 0 : CPLError(CE_Failure, CPLE_AppDefined, "Unsupported FS prefix");
840 0 : return nullptr;
841 : }
842 :
843 638 : const auto eService = strcmp(pszFSPrefix, "/vsiaz/") == 0 ||
844 101 : strcmp(pszFSPrefix, "/vsiaz_streaming/") == 0
845 319 : ? Service::SERVICE_BLOB
846 : : Service::SERVICE_ADLS;
847 :
848 : std::string osPathForOption(
849 638 : eService == Service::SERVICE_BLOB ? "/vsiaz/" : "/vsiadls/");
850 : osPathForOption +=
851 319 : pszURIForPathSpecificOption ? pszURIForPathSpecificOption : pszURI;
852 :
853 319 : bool bUseHTTPS = true;
854 638 : std::string osStorageAccount;
855 638 : std::string osStorageKey;
856 638 : std::string osEndpoint;
857 638 : std::string osSAS;
858 638 : std::string osAccessToken;
859 319 : bool bFromManagedIdentities = false;
860 :
861 319 : if (!GetConfiguration(osPathForOption, papszOptions, eService, bUseHTTPS,
862 : osEndpoint, osStorageAccount, osStorageKey, osSAS,
863 : osAccessToken, bFromManagedIdentities))
864 : {
865 16 : return nullptr;
866 : }
867 :
868 303 : if (IsNoSignRequest(osPathForOption.c_str()))
869 : {
870 13 : osStorageKey.clear();
871 13 : osSAS.clear();
872 13 : osAccessToken.clear();
873 : }
874 :
875 : // pszURI == bucket/object
876 606 : const std::string osBucketObject(pszURI);
877 606 : std::string osBucket(osBucketObject);
878 303 : std::string osObjectKey;
879 303 : size_t nSlashPos = osBucketObject.find('/');
880 303 : if (nSlashPos != std::string::npos)
881 : {
882 213 : osBucket = osBucketObject.substr(0, nSlashPos);
883 213 : osObjectKey = osBucketObject.substr(nSlashPos + 1);
884 : }
885 :
886 : return new VSIAzureBlobHandleHelper(
887 : osPathForOption, osEndpoint, osBucket, osObjectKey, osStorageAccount,
888 303 : osStorageKey, osSAS, osAccessToken, bFromManagedIdentities);
889 : }
890 :
891 : /************************************************************************/
892 : /* BuildURL() */
893 : /************************************************************************/
894 :
895 951 : std::string VSIAzureBlobHandleHelper::BuildURL(const std::string &osEndpoint,
896 : const std::string &osBucket,
897 : const std::string &osObjectKey,
898 : const std::string &osSAS)
899 : {
900 951 : std::string osURL = osEndpoint;
901 951 : osURL += "/";
902 951 : osURL += CPLAWSURLEncode(osBucket, false);
903 951 : if (!osObjectKey.empty())
904 453 : osURL += "/" + CPLAWSURLEncode(osObjectKey, false);
905 951 : if (!osSAS.empty())
906 11 : osURL += '?' + osSAS;
907 951 : return osURL;
908 : }
909 :
910 : /************************************************************************/
911 : /* RebuildURL() */
912 : /************************************************************************/
913 :
914 648 : void VSIAzureBlobHandleHelper::RebuildURL()
915 : {
916 648 : m_osURL = BuildURL(m_osEndpoint, m_osBucket, m_osObjectKey, std::string());
917 648 : m_osURL += GetQueryString(false);
918 648 : if (!m_osSAS.empty())
919 12 : m_osURL += (m_oMapQueryParameters.empty() ? '?' : '&') + m_osSAS;
920 648 : }
921 :
922 : /************************************************************************/
923 : /* GetSASQueryString() */
924 : /************************************************************************/
925 :
926 73 : std::string VSIAzureBlobHandleHelper::GetSASQueryString() const
927 : {
928 73 : if (!m_osSAS.empty())
929 4 : return '?' + m_osSAS;
930 69 : return std::string();
931 : }
932 :
933 : /************************************************************************/
934 : /* GetCurlHeaders() */
935 : /************************************************************************/
936 :
937 : struct curl_slist *
938 236 : VSIAzureBlobHandleHelper::GetCurlHeaders(const std::string &osVerb,
939 : struct curl_slist *psHeaders,
940 : const void *, size_t) const
941 : {
942 236 : if (m_bFromManagedIdentities || !m_osAccessToken.empty())
943 : {
944 12 : psHeaders = curl_slist_append(
945 : psHeaders,
946 24 : std::string("x-ms-version: ").append(X_MS_VERSION).c_str());
947 :
948 24 : std::string osAccessToken;
949 12 : if (m_bFromManagedIdentities)
950 : {
951 11 : if (!GetConfigurationFromManagedIdentities(m_osPathForOption,
952 : osAccessToken))
953 0 : return psHeaders;
954 : }
955 : else
956 : {
957 1 : osAccessToken = m_osAccessToken;
958 : }
959 :
960 : // Do not use CPLSPrintf() as we could get over the 8K character limit
961 : // with very large SAS tokens
962 12 : std::string osAuthorization = "Authorization: Bearer ";
963 12 : osAuthorization += osAccessToken;
964 12 : psHeaders = curl_slist_append(psHeaders, osAuthorization.c_str());
965 12 : return psHeaders;
966 : }
967 :
968 448 : std::string osResource;
969 224 : const auto nSlashSlashPos = m_osEndpoint.find("//");
970 224 : if (nSlashSlashPos != std::string::npos)
971 : {
972 224 : const auto nResourcePos = m_osEndpoint.find('/', nSlashSlashPos + 2);
973 224 : if (nResourcePos != std::string::npos)
974 217 : osResource = m_osEndpoint.substr(nResourcePos);
975 : }
976 224 : osResource += "/" + m_osBucket;
977 224 : if (!m_osObjectKey.empty())
978 141 : osResource += "/" + CPLAWSURLEncode(m_osObjectKey, false);
979 :
980 : // If accessing a Microsoft Azure account from a Azure VM, check that
981 : // Microsoft is still a sponsor, and if not, make some (kind) noise.
982 0 : if ((m_bFromManagedIdentities &&
983 0 : m_osEndpoint.find("core.windows.net") != std::string::npos)
984 : #ifdef DEBUG
985 224 : || CPLTestBool(CPLGetConfigOption("GDAL_TEST_NAME_AND_SHAME", "NO"))
986 : #endif
987 : )
988 : {
989 0 : static const bool bCheckSponsoring = []()
990 : {
991 0 : if (!CPLTestBool(CPLGetConfigOption("GDAL_NAME_AND_SHAME", "YES")))
992 0 : return true;
993 :
994 0 : const std::string osCacheDir = []()
995 : {
996 : #ifdef _WIN32
997 : const char *pszHome =
998 : CPLGetConfigOption("USERPROFILE", nullptr);
999 : #else
1000 0 : const char *pszHome = CPLGetConfigOption("HOME", nullptr);
1001 : #endif
1002 0 : if (pszHome != nullptr)
1003 : {
1004 0 : return CPLFormFilenameSafe(pszHome, ".gdal", nullptr);
1005 : }
1006 : else
1007 : {
1008 0 : const char *pszDir = CPLGetConfigOption("TEMP", "/tmp");
1009 : VSIStatBufL sStat;
1010 0 : if (VSIStatL(pszDir, &sStat) == 0)
1011 : {
1012 : const char *pszUsername =
1013 0 : CPLGetConfigOption("USERNAME", nullptr);
1014 0 : if (pszUsername == nullptr)
1015 0 : pszUsername = CPLGetConfigOption("USER", nullptr);
1016 :
1017 0 : if (pszUsername != nullptr)
1018 : {
1019 : return CPLFormFilenameSafe(
1020 : pszDir, CPLSPrintf(".gdal_%s", pszUsername),
1021 0 : nullptr);
1022 : }
1023 : }
1024 : }
1025 0 : return std::string();
1026 0 : }();
1027 0 : if (!osCacheDir.empty())
1028 : {
1029 : VSIStatBufL sStat;
1030 0 : if (VSIStatL(osCacheDir.c_str(), &sStat) != 0)
1031 0 : VSIMkdir(osCacheDir.c_str(), 0755);
1032 : const std::string osCloudCheck = CPLFormFilenameSafe(
1033 0 : osCacheDir.c_str(), "cloud_check_ms.txt", nullptr);
1034 : // Sideral day, why not? "Aim for the stars, expect dust"
1035 0 : constexpr int ONE_DAY_IN_SECS = 86164;
1036 0 : if (VSIStatL(osCloudCheck.c_str(), &sStat) == 0 &&
1037 0 : sStat.st_mtime + ONE_DAY_IN_SECS >= time(nullptr))
1038 : {
1039 0 : CPLDebugOnly("GDAL", "%s checked", osCloudCheck.c_str());
1040 : }
1041 : else
1042 : {
1043 0 : FILE *f = fopen(osCloudCheck.c_str(), "wb");
1044 0 : if (f)
1045 0 : fclose(f);
1046 :
1047 0 : const auto PingURL = [](const char *pszURL)
1048 : {
1049 0 : CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
1050 0 : const char *const apszOptions[] = {
1051 : "CUSTOMREQUEST=HEAD", "TIMEOUT=1", nullptr};
1052 0 : auto res = CPLHTTPFetch(pszURL, apszOptions);
1053 0 : const bool bOK = res && !res->pszErrBuf;
1054 0 : CPLHTTPDestroyResult(res);
1055 0 : return bOK;
1056 : };
1057 0 : if (!PingURL("https://gdal.org/en/latest/sponsors/"
1058 0 : "did_microsoft_sponsor.html") &&
1059 : // check that gdal.org is responding to avoid false positive
1060 0 : PingURL("https://gdal.org/en/latest/index.html"))
1061 : {
1062 0 : const auto CPLE_NonCooperativeSponsor = CPLE_AppDefined;
1063 0 : CPLError(
1064 : CE_Warning, CPLE_NonCooperativeSponsor,
1065 : "Due to lack of resources, Azure Cloud Storage "
1066 : "access is undergoing minimal maintenance and may "
1067 : "be removed in the future unless Microsoft Azure "
1068 : "re-evaluates its decision to stop sponsoring "
1069 : "GDAL. If you are interested in keeping this "
1070 : "functionality please get in touch with your "
1071 : "Microsoft Azure representative.");
1072 : }
1073 : }
1074 : }
1075 0 : return true;
1076 0 : }();
1077 0 : CPL_IGNORE_RET_VAL(bCheckSponsoring);
1078 : }
1079 :
1080 448 : return GetAzureBlobHeaders(osVerb, psHeaders, osResource,
1081 224 : m_oMapQueryParameters, m_osStorageAccount,
1082 224 : m_osStorageKey, m_bIncludeMSVersion);
1083 : }
1084 :
1085 : /************************************************************************/
1086 : /* CanRestartOnError() */
1087 : /************************************************************************/
1088 :
1089 18 : bool VSIAzureBlobHandleHelper::CanRestartOnError(const char *pszErrorMsg,
1090 : const char *pszHeaders,
1091 : bool bSetError)
1092 : {
1093 18 : if (pszErrorMsg[0] == '\xEF' && pszErrorMsg[1] == '\xBB' &&
1094 2 : pszErrorMsg[2] == '\xBF')
1095 2 : pszErrorMsg += 3;
1096 :
1097 : #ifdef DEBUG_VERBOSE
1098 : CPLDebug("AZURE", "%s", pszErrorMsg);
1099 : CPLDebug("AZURE", "%s", pszHeaders ? pszHeaders : "");
1100 : #endif
1101 :
1102 18 : if (STARTS_WITH(pszErrorMsg, "HTTP/") && pszHeaders &&
1103 16 : STARTS_WITH(pszHeaders, "HTTP/"))
1104 : {
1105 16 : if (bSetError)
1106 : {
1107 32 : std::string osMessage;
1108 32 : std::string osTmpMessage(pszHeaders);
1109 16 : auto nPos = osTmpMessage.find(' ');
1110 16 : if (nPos != std::string::npos)
1111 : {
1112 16 : nPos = osTmpMessage.find(' ', nPos + 1);
1113 16 : if (nPos != std::string::npos)
1114 : {
1115 16 : auto nPos2 = osTmpMessage.find('\r', nPos + 1);
1116 16 : if (nPos2 != std::string::npos)
1117 : osMessage =
1118 16 : osTmpMessage.substr(nPos + 1, nPos2 - nPos - 1);
1119 : }
1120 : }
1121 16 : if (strstr(pszHeaders, "x-ms-error-code: BlobNotFound") || // vsiaz
1122 16 : strstr(pszHeaders, "x-ms-error-code: PathNotFound") // vsiadls
1123 : )
1124 : {
1125 0 : VSIError(VSIE_ObjectNotFound, "%s", osMessage.c_str());
1126 : }
1127 16 : else if (strstr(pszHeaders,
1128 16 : "x-ms-error-code: InvalidAuthenticationInfo") ||
1129 16 : strstr(pszHeaders,
1130 : "x-ms-error-code: AuthenticationFailed"))
1131 : {
1132 1 : VSIError(VSIE_InvalidCredentials, "%s", osMessage.c_str());
1133 : }
1134 : // /vsiadls
1135 15 : else if (strstr(pszHeaders, "x-ms-error-code: FilesystemNotFound"))
1136 : {
1137 0 : VSIError(VSIE_BucketNotFound, "%s", osMessage.c_str());
1138 : }
1139 : else
1140 : {
1141 15 : CPLDebug("AZURE", "%s", pszHeaders);
1142 : }
1143 : }
1144 16 : return false;
1145 : }
1146 :
1147 2 : if (!STARTS_WITH(pszErrorMsg, "<?xml") &&
1148 0 : !STARTS_WITH(pszErrorMsg, "<Error>"))
1149 : {
1150 0 : if (bSetError)
1151 : {
1152 0 : VSIError(VSIE_ObjectStorageGenericError,
1153 : "Invalid Azure response: %s", pszErrorMsg);
1154 : }
1155 0 : return false;
1156 : }
1157 :
1158 4 : auto psTree = CPLXMLTreeCloser(CPLParseXMLString(pszErrorMsg));
1159 2 : if (psTree == nullptr)
1160 : {
1161 0 : if (bSetError)
1162 : {
1163 0 : VSIError(VSIE_ObjectStorageGenericError,
1164 : "Malformed Azure XML response: %s", pszErrorMsg);
1165 : }
1166 0 : return false;
1167 : }
1168 :
1169 2 : const char *pszCode = CPLGetXMLValue(psTree.get(), "=Error.Code", nullptr);
1170 2 : if (pszCode == nullptr)
1171 : {
1172 0 : if (bSetError)
1173 : {
1174 0 : VSIError(VSIE_ObjectStorageGenericError,
1175 : "Malformed Azure XML response: %s", pszErrorMsg);
1176 : }
1177 0 : return false;
1178 : }
1179 :
1180 2 : if (bSetError)
1181 : {
1182 : // Translate AWS errors into VSI errors.
1183 : const char *pszMessage =
1184 2 : CPLGetXMLValue(psTree.get(), "=Error.Message", nullptr);
1185 4 : std::string osMessage;
1186 2 : if (pszMessage)
1187 : {
1188 2 : osMessage = pszMessage;
1189 2 : const auto nPos = osMessage.find("\nRequestId:");
1190 2 : if (nPos != std::string::npos)
1191 2 : osMessage.resize(nPos);
1192 : }
1193 :
1194 2 : if (pszMessage == nullptr)
1195 : {
1196 0 : VSIError(VSIE_ObjectStorageGenericError, "%s", pszErrorMsg);
1197 : }
1198 2 : else if (EQUAL(pszCode, "ContainerNotFound"))
1199 : {
1200 0 : VSIError(VSIE_BucketNotFound, "%s", osMessage.c_str());
1201 : }
1202 : else
1203 : {
1204 2 : VSIError(VSIE_ObjectStorageGenericError, "%s: %s", pszCode,
1205 : pszMessage);
1206 : }
1207 : }
1208 :
1209 2 : return false;
1210 : }
1211 :
1212 : /************************************************************************/
1213 : /* GetSignedURL() */
1214 : /************************************************************************/
1215 :
1216 7 : std::string VSIAzureBlobHandleHelper::GetSignedURL(CSLConstList papszOptions)
1217 : {
1218 7 : if (m_osStorageKey.empty())
1219 3 : return m_osURL;
1220 :
1221 8 : std::string osStartDate(CPLGetAWS_SIGN4_Timestamp(time(nullptr)));
1222 4 : const char *pszStartDate = CSLFetchNameValue(papszOptions, "START_DATE");
1223 4 : if (pszStartDate)
1224 2 : osStartDate = pszStartDate;
1225 4 : int nYear, nMonth, nDay, nHour = 0, nMin = 0, nSec = 0;
1226 4 : if (sscanf(osStartDate.c_str(), "%04d%02d%02dT%02d%02d%02dZ", &nYear,
1227 4 : &nMonth, &nDay, &nHour, &nMin, &nSec) < 3)
1228 : {
1229 0 : return std::string();
1230 : }
1231 : osStartDate = CPLSPrintf("%04d-%02d-%02dT%02d:%02d:%02dZ", nYear, nMonth,
1232 4 : nDay, nHour, nMin, nSec);
1233 :
1234 : struct tm brokendowntime;
1235 4 : brokendowntime.tm_year = nYear - 1900;
1236 4 : brokendowntime.tm_mon = nMonth - 1;
1237 4 : brokendowntime.tm_mday = nDay;
1238 4 : brokendowntime.tm_hour = nHour;
1239 4 : brokendowntime.tm_min = nMin;
1240 4 : brokendowntime.tm_sec = nSec;
1241 4 : GIntBig nStartDate = CPLYMDHMSToUnixTime(&brokendowntime);
1242 : GIntBig nEndDate =
1243 : nStartDate +
1244 4 : atoi(CSLFetchNameValueDef(papszOptions, "EXPIRATION_DELAY", "3600"));
1245 4 : CPLUnixTimeToYMDHMS(nEndDate, &brokendowntime);
1246 4 : nYear = brokendowntime.tm_year + 1900;
1247 4 : nMonth = brokendowntime.tm_mon + 1;
1248 4 : nDay = brokendowntime.tm_mday;
1249 4 : nHour = brokendowntime.tm_hour;
1250 4 : nMin = brokendowntime.tm_min;
1251 4 : nSec = brokendowntime.tm_sec;
1252 : std::string osEndDate = CPLSPrintf("%04d-%02d-%02dT%02d:%02d:%02dZ", nYear,
1253 8 : nMonth, nDay, nHour, nMin, nSec);
1254 :
1255 8 : std::string osVerb(CSLFetchNameValueDef(papszOptions, "VERB", "GET"));
1256 : std::string osSignedPermissions(CSLFetchNameValueDef(
1257 : papszOptions, "SIGNEDPERMISSIONS",
1258 4 : (EQUAL(osVerb.c_str(), "GET") || EQUAL(osVerb.c_str(), "HEAD")) ? "r"
1259 12 : : "w"));
1260 :
1261 : std::string osSignedIdentifier(
1262 8 : CSLFetchNameValueDef(papszOptions, "SIGNEDIDENTIFIER", ""));
1263 :
1264 8 : const std::string osSignedVersion("2020-12-06");
1265 8 : const std::string osSignedProtocol("https");
1266 8 : const std::string osSignedResource("b"); // blob
1267 :
1268 8 : std::string osCanonicalizedResource("/blob/");
1269 4 : osCanonicalizedResource += CPLAWSURLEncode(m_osStorageAccount, false);
1270 4 : osCanonicalizedResource += '/';
1271 4 : osCanonicalizedResource += CPLAWSURLEncode(m_osBucket, false);
1272 4 : osCanonicalizedResource += '/';
1273 4 : osCanonicalizedResource += CPLAWSURLEncode(m_osObjectKey, false);
1274 :
1275 : // Cf https://learn.microsoft.com/en-us/rest/api/storageservices/create-service-sas
1276 8 : std::string osStringToSign;
1277 4 : osStringToSign += osSignedPermissions + "\n";
1278 4 : osStringToSign += osStartDate + "\n";
1279 4 : osStringToSign += osEndDate + "\n";
1280 4 : osStringToSign += osCanonicalizedResource + "\n";
1281 4 : osStringToSign += osSignedIdentifier + "\n";
1282 4 : osStringToSign += "\n"; // signedIP
1283 4 : osStringToSign += osSignedProtocol + "\n";
1284 4 : osStringToSign += osSignedVersion + "\n";
1285 4 : osStringToSign += osSignedResource + "\n";
1286 4 : osStringToSign += "\n"; // signedSnapshotTime
1287 4 : osStringToSign += "\n"; // signedEncryptionScope
1288 4 : osStringToSign += "\n"; // rscc
1289 4 : osStringToSign += "\n"; // rscd
1290 4 : osStringToSign += "\n"; // rsce
1291 4 : osStringToSign += "\n"; // rscl
1292 :
1293 : #ifdef DEBUG_VERBOSE
1294 : CPLDebug("AZURE", "osStringToSign = %s", osStringToSign.c_str());
1295 : #endif
1296 :
1297 : /* -------------------------------------------------------------------- */
1298 : /* Compute signature. */
1299 : /* -------------------------------------------------------------------- */
1300 : std::string osSignature(
1301 8 : CPLAzureGetSignature(osStringToSign, m_osStorageKey));
1302 :
1303 4 : ResetQueryParameters();
1304 4 : AddQueryParameter("sv", osSignedVersion);
1305 4 : AddQueryParameter("st", osStartDate);
1306 4 : AddQueryParameter("se", osEndDate);
1307 4 : AddQueryParameter("sr", osSignedResource);
1308 4 : AddQueryParameter("sp", osSignedPermissions);
1309 4 : AddQueryParameter("spr", osSignedProtocol);
1310 4 : AddQueryParameter("sig", osSignature);
1311 4 : if (!osSignedIdentifier.empty())
1312 0 : AddQueryParameter("si", osSignedIdentifier);
1313 4 : return m_osURL;
1314 : }
1315 :
1316 : #endif // HAVE_CURL
1317 :
1318 : //! @endcond
|