You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@libcloud.apache.org by cl...@apache.org on 2020/01/14 13:56:28 UTC
[libcloud] branch trunk updated: Implement SAS URL generation for
Azure Storage (#1408)
This is an automated email from the ASF dual-hosted git repository.
clewolff pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/libcloud.git
The following commit(s) were added to refs/heads/trunk by this push:
new bb865b1 Implement SAS URL generation for Azure Storage (#1408)
bb865b1 is described below
commit bb865b1ee029b9bceaa83917c9b9f7b16033547e
Author: Clemens Wolff <cl...@apache.org>
AuthorDate: Tue Jan 14 08:56:18 2020 -0500
Implement SAS URL generation for Azure Storage (#1408)
---
CHANGES.rst | 13 +++++
libcloud/storage/drivers/azure_blobs.py | 80 ++++++++++++++++++++++++++++++-
libcloud/test/storage/test_azure_blobs.py | 10 ++++
3 files changed, 102 insertions(+), 1 deletion(-)
diff --git a/CHANGES.rst b/CHANGES.rst
index 4c003f0..c558f77 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -113,6 +113,19 @@ Storage
(GITHUB-1397)
[Clemens Wolff - @c-w]
+- [Azure Blobs] Implement ``get_object_cdn_url`` for the Azure Storage driver.
+
+ Leveraging Azure storage service shared access signatures, the Azure Storage
+ driver can now be used to generate temporary URLs that grant clients read
+ access to objects. The URLs expire after a certain period of time, either
+ configured via the ``ex_expiry`` argument or the
+ ``LIBCLOUD_AZURE_STORAGE_CDN_URL_EXPIRY_HOURS`` environment variable
+ (default: 24 hours).
+
+ Reported by @rvolykh.
+ (GITHUB-1403, GITHUB-1408)
+ [Clemens Wolff - @c-w]
+
Changes in Apache Libcloud v2.8.0
---------------------------------
diff --git a/libcloud/storage/drivers/azure_blobs.py b/libcloud/storage/drivers/azure_blobs.py
index 3a2e2a3..a7cc601 100644
--- a/libcloud/storage/drivers/azure_blobs.py
+++ b/libcloud/storage/drivers/azure_blobs.py
@@ -16,11 +16,15 @@
from __future__ import with_statement
import base64
+import hashlib
+import hmac
import os
import binascii
+from datetime import datetime, timedelta
from libcloud.utils.py3 import ET
from libcloud.utils.py3 import httplib
+from libcloud.utils.py3 import urlencode
from libcloud.utils.py3 import urlquote
from libcloud.utils.py3 import tostring
from libcloud.utils.py3 import b
@@ -65,6 +69,16 @@ AZURE_LEASE_PERIOD = int(
AZURE_STORAGE_HOST_SUFFIX = 'blob.core.windows.net'
+AZURE_STORAGE_CDN_URL_DATE_FORMAT = '%Y-%m-%dT%H:%M:%SZ'
+
+AZURE_STORAGE_CDN_URL_START_MINUTES = float(
+ os.getenv('LIBCLOUD_AZURE_STORAGE_CDN_URL_START_MINUTES', '5')
+)
+
+AZURE_STORAGE_CDN_URL_EXPIRY_HOURS = float(
+ os.getenv('LIBCLOUD_AZURE_STORAGE_CDN_URL_EXPIRY_HOURS', '24')
+)
+
class AzureBlobLease(object):
"""
@@ -180,7 +194,7 @@ class AzureBlobsConnection(AzureConnection):
return action
- API_VERSION = '2016-05-31'
+ API_VERSION = '2018-11-09'
class AzureBlobsStorageDriver(StorageDriver):
@@ -489,6 +503,70 @@ class AzureBlobsStorageDriver(StorageDriver):
raise ObjectDoesNotExistError(value=None, driver=self,
object_name=object_name)
+ def get_object_cdn_url(self, obj,
+ ex_expiry=AZURE_STORAGE_CDN_URL_EXPIRY_HOURS):
+ """
+ Return a SAS URL that enables reading the given object.
+
+ :param obj: Object instance.
+ :type obj: :class:`Object`
+
+ :param ex_expiry: The number of hours after which the URL expires.
+ Defaults to 24 hours.
+ :type ex_expiry: ``float``
+
+ :return: A SAS URL for the object.
+ :rtype: ``str``
+ """
+ object_path = self._get_object_path(obj.container, obj.name)
+
+ now = datetime.utcnow()
+ start = now - timedelta(minutes=AZURE_STORAGE_CDN_URL_START_MINUTES)
+ expiry = now + timedelta(hours=ex_expiry)
+
+ params = {
+ 'st': start.strftime(AZURE_STORAGE_CDN_URL_DATE_FORMAT),
+ 'se': expiry.strftime(AZURE_STORAGE_CDN_URL_DATE_FORMAT),
+ 'sp': 'r',
+ 'spr': 'https' if self.secure else 'http,https',
+ 'sv': self.connectionCls.API_VERSION,
+ 'sr': 'b',
+ }
+
+ string_to_sign = '\n'.join((
+ params['sp'],
+ params['st'],
+ params['se'],
+ '/blob/{}{}'.format(self.key, object_path),
+ '', # signedIdentifier
+ '', # signedIP
+ params['spr'],
+ params['sv'],
+ params['sr'],
+ '', # snapshot
+ '', # rscc
+ '', # rscd
+ '', # rsce
+ '', # rscl
+ '', # rsct
+ ))
+
+ params['sig'] = base64.b64encode(
+ hmac.new(
+ self.secret,
+ string_to_sign.encode('utf-8'),
+ hashlib.sha256
+ ).digest()
+ ).decode('utf-8')
+
+ return '{scheme}://{host}:{port}{action}?{sas_token}'.format(
+ scheme='https' if self.secure else 'http',
+ host=self.connection.host,
+ port=self.connection.port,
+ action=self.connection.morph_action_hook(object_path),
+ sas_token=urlencode(params),
+ )
+
def _get_container_path(self, container):
"""
Return a container path
diff --git a/libcloud/test/storage/test_azure_blobs.py b/libcloud/test/storage/test_azure_blobs.py
index 389d581..acd10d1 100644
--- a/libcloud/test/storage/test_azure_blobs.py
+++ b/libcloud/test/storage/test_azure_blobs.py
@@ -502,6 +502,16 @@ class AzureBlobsTests(unittest.TestCase):
self.assertTrue(container.extra['lease']['state'], 'available')
self.assertTrue(container.extra['meta_data']['meta1'], 'value1')
+ def test_get_object_cdn_url(self):
+ obj = self.driver.get_object(container_name='test_container200',
+ object_name='test')
+
+ url = urlparse.urlparse(self.driver.get_object_cdn_url(obj))
+ query = urlparse.parse_qs(url.query)
+
+ self.assertEqual(len(query['sig']), 1)
+ self.assertGreater(len(query['sig'][0]), 0)
+
def test_get_object_container_doesnt_exist(self):
# This method makes two requests which makes mocking the response a bit
# trickier