You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@libcloud.apache.org by to...@apache.org on 2015/03/28 15:51:27 UTC

[10/16] libcloud git commit: Fix aws request signing so it also works with the "region" argument.

Fix aws request signing so it also works with the "region" argument.

Move away from class per version approach to "signature_version" argument.


Project: http://git-wip-us.apache.org/repos/asf/libcloud/repo
Commit: http://git-wip-us.apache.org/repos/asf/libcloud/commit/a84f5782
Tree: http://git-wip-us.apache.org/repos/asf/libcloud/tree/a84f5782
Diff: http://git-wip-us.apache.org/repos/asf/libcloud/diff/a84f5782

Branch: refs/heads/trunk
Commit: a84f57826fdf73c34b8ef6e98738f971dcc9a912
Parents: 882fdf8
Author: Tomaz Muraus <to...@apache.org>
Authored: Sat Mar 7 16:36:32 2015 +0100
Committer: Tomaz Muraus <to...@apache.org>
Committed: Sat Mar 7 16:54:53 2015 +0100

----------------------------------------------------------------------
 libcloud/common/aws.py          | 171 +++++++++++++++++++++++++++--------
 libcloud/compute/drivers/ec2.py |  19 ++++
 2 files changed, 154 insertions(+), 36 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/libcloud/blob/a84f5782/libcloud/common/aws.py
----------------------------------------------------------------------
diff --git a/libcloud/common/aws.py b/libcloud/common/aws.py
index f1b9a11..513e821 100644
--- a/libcloud/common/aws.py
+++ b/libcloud/common/aws.py
@@ -30,6 +30,21 @@ from libcloud.common.types import InvalidCredsError, MalformedResponseError
 from libcloud.utils.py3 import b, httplib, urlquote
 from libcloud.utils.xml import findtext, findall
 
+__all__ = [
+    'AWSBaseResponse',
+    'AWSGenericResponse',
+
+    'AWSTokenConnection',
+    'SignedAWSConnection',
+
+    'AWSRequestSignerAlgorithmV2',
+    'AWSRequestSignerAlgorithmV4',
+
+    'AWSDriver'
+]
+
+DEFAULT_SIGNATURE_VERSION = '2'
+
 
 class AWSBaseResponse(XmlResponse):
     namespace = None
@@ -132,17 +147,49 @@ class AWSTokenConnection(ConnectionUserAndKey):
         return super(AWSTokenConnection, self).add_default_headers(headers)
 
 
-class SignedAWSConnection(AWSTokenConnection):
+class AWSRequestSigner(object):
+    """
+    Class which handles signing the outgoing AWS requests.
+    """
 
-    def add_default_params(self, params):
+    def __init__(self, access_key, access_secret, version, connection):
+        """
+        :param access_key: Access key.
+        :type access_key: ``str``
+
+        :param access_secret: Access secret.
+        :type access_secret: ``str``
+
+        :param version: API version.
+        :type version: ``str``
+
+        :param connection: Connection instance.
+        :type connection: :class:`Connection`
+        """
+        self.access_key = access_key
+        self.access_secret = access_secret
+        self.version = version
+        # TODO: Remove cycling dependency between connection and signer
+        self.connection = connection
+
+    def get_request_params(self, params, method='GET', path='/'):
+        return params
+
+    def get_request_headers(self, params, headers, method='GET', path='/'):
+        return params, headers
+
+
+class AWSRequestSignerAlgorithmV2(AWSRequestSigner):
+    def get_request_params(self, params, method='GET', path='/'):
         params['SignatureVersion'] = '2'
         params['SignatureMethod'] = 'HmacSHA256'
-        params['AWSAccessKeyId'] = self.user_id
+        params['AWSAccessKeyId'] = self.access_key
         params['Version'] = self.version
         params['Timestamp'] = time.strftime('%Y-%m-%dT%H:%M:%SZ',
                                             time.gmtime())
-        params['Signature'] = self._get_aws_auth_param(params, self.key,
-                                                       self.action)
+        params['Signature'] = self._get_aws_auth_param(params=params,
+                                                       secret_key=self.access_secret,
+                                                       path=path)
         return params
 
     def _get_aws_auth_param(self, params, secret_key, path='/'):
@@ -155,6 +202,8 @@ class SignedAWSConnection(AWSTokenConnection):
                        HTTPRequestURI + "\n" +
                        CanonicalizedQueryString <from the preceding step>
         """
+        connection = self.connection
+
         keys = list(params.keys())
         keys.sort()
         pairs = []
@@ -165,10 +214,10 @@ class SignedAWSConnection(AWSTokenConnection):
 
         qs = '&'.join(pairs)
 
-        hostname = self.host
-        if (self.secure and self.port != 443) or \
-           (not self.secure and self.port != 80):
-            hostname += ":" + str(self.port)
+        hostname = connection.host
+        if (connection.secure and connection.port != 443) or \
+           (not connection.secure and connection.port != 80):
+            hostname += ':' + str(connection.port)
 
         string_to_sign = '\n'.join(('GET', hostname, path, qs))
 
@@ -180,57 +229,70 @@ class SignedAWSConnection(AWSTokenConnection):
         return b64_hmac.decode('utf-8')
 
 
-class V4SignedAWSConnection(AWSTokenConnection):
-
-    def add_default_params(self, params):
+class AWSRequestSignerAlgorithmV4(AWSRequestSigner):
+    def get_request_params(self, params, method='GET', path='/'):
         params['Version'] = self.version
         return params
 
-    def pre_connect_hook(self, params, headers):
+    def get_request_headers(self, params, headers, method='GET', path='/'):
         now = datetime.utcnow()
         headers['X-AMZ-Date'] = now.strftime('%Y%m%dT%H%M%SZ')
         headers['Authorization'] = \
-            self._get_authorization_v4_header(params, headers, now)
+            self._get_authorization_v4_header(params=params, headers=headers,
+                                              dt=now, method=method, path=path)
 
         return params, headers
 
-    def _get_authorization_v4_header(self, params, headers, dt):
-        assert self.method == 'GET', 'AWS Signature V4 not implemented for ' \
-                                     'other methods than GET'
+    def _get_authorization_v4_header(self, params, headers, dt, method='GET',
+                                     path='/'):
+        assert method == 'GET', 'AWS Signature V4 not implemented for ' \
+                                'other methods than GET'
+
+        credentials_scope = self._get_credential_scope(dt=dt)
+        signed_headers = self._get_signed_headers(headers=headers)
+        signature = self._get_signature(params=params, headers=headers,
+                                        dt=dt, method=method, path=path)
 
         return 'AWS4-HMAC-SHA256 Credential=%(u)s/%(c)s, ' \
                'SignedHeaders=%(sh)s, Signature=%(s)s' % {
-                   'u': self.user_id,
-                   'c': self._get_credential_scope(dt),
-                   'sh': self._get_signed_headers(headers),
-                   's': self._get_signature(params, headers, dt)
+                   'u': self.access_key,
+                   'c': credentials_scope,
+                   'sh': signed_headers,
+                   's': signature
                }
 
-    def _get_signature(self, params, headers, dt):
-        return _sign(
-            self._get_key_to_sign_with(dt),
-            self._get_string_to_sign(params, headers, dt),
-            hex=True)
+    def _get_signature(self, params, headers, dt, method, path):
+        key = self._get_key_to_sign_with(dt)
+        string_to_sign = self._get_string_to_sign(params=params,
+                                                  headers=headers, dt=dt,
+                                                  method=method, path=path)
+        return _sign(key=key, msg=string_to_sign, hex=True)
 
     def _get_key_to_sign_with(self, dt):
         return _sign(
             _sign(
                 _sign(
-                    _sign(('AWS4' + self.key), dt.strftime('%Y%m%d')),
-                    self.driver.region_name),
-                self.service_name),
+                    _sign(('AWS4' + self.access_secret),
+                          dt.strftime('%Y%m%d')),
+                    self.connection.driver.region_name),
+                self.connection.service_name),
             'aws4_request')
 
-    def _get_string_to_sign(self, params, headers, dt):
+    def _get_string_to_sign(self, params, headers, dt, method, path):
+        canonical_request = self._get_canonical_request(params=params,
+                                                        headers=headers,
+                                                        method=method,
+                                                        path=path)
+
         return '\n'.join(['AWS4-HMAC-SHA256',
                           dt.strftime('%Y%m%dT%H%M%SZ'),
                           self._get_credential_scope(dt),
-                          _hash(self._get_canonical_request(params, headers))])
+                          _hash(canonical_request)])
 
     def _get_credential_scope(self, dt):
         return '/'.join([dt.strftime('%Y%m%d'),
-                         self.driver.region_name,
-                         self.service_name,
+                         self.connection.driver.region_name,
+                         self.connection.service_name,
                          'aws4_request'])
 
     def _get_signed_headers(self, headers):
@@ -249,10 +311,10 @@ class V4SignedAWSConnection(AWSTokenConnection):
                          (urlquote(k, safe=''), urlquote(str(v), safe='~'))
                          for k, v in sorted(params.items())])
 
-    def _get_canonical_request(self, params, headers):
+    def _get_canonical_request(self, params, headers, method, path):
         return '\n'.join([
-            self.method,
-            self.action,
+            method,
+            path,
             self._get_request_params(params),
             self._get_canonical_headers(headers),
             self._get_signed_headers(headers),
@@ -260,6 +322,43 @@ class V4SignedAWSConnection(AWSTokenConnection):
         ])
 
 
+class SignedAWSConnection(AWSTokenConnection):
+    def __init__(self, user_id, key, secure=True, host=None, port=None,
+                 url=None, timeout=None, token=None,
+                 signature_version=DEFAULT_SIGNATURE_VERSION):
+        super(SignedAWSConnection, self).__init__(user_id=user_id, key=key,
+                                                  secure=secure, host=host,
+                                                  port=port, url=url,
+                                                  timeout=timeout, token=token)
+        self.signature_version = str(signature_version)
+
+        if self.signature_version == '2':
+            signer_cls = AWSRequestSignerAlgorithmV2
+        elif signature_version == '4':
+            signer_cls = AWSRequestSignerAlgorithmV4
+        else:
+            raise ValueError('Unsupported signature_version: %s' %
+                             (signature_version))
+
+        self.signer = signer_cls(access_key=self.user_id,
+                                 access_secret=self.key,
+                                 version=self.version,
+                                 connection=self)
+
+    def add_default_params(self, params):
+        params = self.signer.get_request_params(params=params,
+                                                method=self.method,
+                                                path=self.action)
+        return params
+
+    def pre_connect_hook(self, params, headers):
+        params, headers = self.signer.get_request_headers(params=params,
+                                                          headers=headers,
+                                                          method=self.method,
+                                                          path=self.action)
+        return params, headers
+
+
 def _sign(key, msg, hex=False):
     if hex:
         return hmac.new(b(key), b(msg), hashlib.sha256).hexdigest()

http://git-wip-us.apache.org/repos/asf/libcloud/blob/a84f5782/libcloud/compute/drivers/ec2.py
----------------------------------------------------------------------
diff --git a/libcloud/compute/drivers/ec2.py b/libcloud/compute/drivers/ec2.py
index 03c817f..8f64705 100644
--- a/libcloud/compute/drivers/ec2.py
+++ b/libcloud/compute/drivers/ec2.py
@@ -36,6 +36,7 @@ from libcloud.utils.publickey import get_pubkey_comment
 from libcloud.utils.iso8601 import parse_date
 from libcloud.common.aws import (AWSBaseResponse, SignedAWSConnection,
                                  V4SignedAWSConnection)
+from libcloud.common.aws import DEFAULT_SIGNATURE_VERSION
 from libcloud.common.types import (InvalidCredsError, MalformedResponseError,
                                    LibcloudError)
 from libcloud.compute.providers import Provider
@@ -377,6 +378,7 @@ REGION_DETAILS = {
         'endpoint': 'ec2.us-east-1.amazonaws.com',
         'api_name': 'ec2_us_east',
         'country': 'USA',
+        'signature_version': '2',
         'instance_types': [
             't1.micro',
             'm1.small',
@@ -421,6 +423,7 @@ REGION_DETAILS = {
         'endpoint': 'ec2.us-west-1.amazonaws.com',
         'api_name': 'ec2_us_west',
         'country': 'USA',
+        'signature_version': '2',
         'instance_types': [
             't1.micro',
             'm1.small',
@@ -461,6 +464,7 @@ REGION_DETAILS = {
         'endpoint': 'ec2.us-west-2.amazonaws.com',
         'api_name': 'ec2_us_west_oregon',
         'country': 'US',
+        'signature_version': '2',
         'instance_types': [
             't1.micro',
             'm1.small',
@@ -503,6 +507,7 @@ REGION_DETAILS = {
         'endpoint': 'ec2.eu-west-1.amazonaws.com',
         'api_name': 'ec2_eu_west',
         'country': 'Ireland',
+        'signature_version': '2',
         'instance_types': [
             't1.micro',
             'm1.small',
@@ -545,6 +550,7 @@ REGION_DETAILS = {
         'endpoint': 'ec2.eu-central-1.amazonaws.com',
         'api_name': 'ec2_eu_central',
         'country': 'Frankfurt',
+        'signature_version': '4',
         'instance_types': [
             'm3.medium',
             'm3.large',
@@ -574,6 +580,7 @@ REGION_DETAILS = {
         'endpoint': 'ec2.ap-southeast-1.amazonaws.com',
         'api_name': 'ec2_ap_southeast',
         'country': 'Singapore',
+        'signature_version': '2',
         'instance_types': [
             't1.micro',
             'm1.small',
@@ -609,6 +616,7 @@ REGION_DETAILS = {
         'endpoint': 'ec2.ap-northeast-1.amazonaws.com',
         'api_name': 'ec2_ap_northeast',
         'country': 'Japan',
+        'signature_version': '2',
         'instance_types': [
             't1.micro',
             'm1.small',
@@ -650,6 +658,7 @@ REGION_DETAILS = {
         'endpoint': 'ec2.sa-east-1.amazonaws.com',
         'api_name': 'ec2_sa_east',
         'country': 'Brazil',
+        'signature_version': '2',
         'instance_types': [
             't1.micro',
             'm1.small',
@@ -675,6 +684,7 @@ REGION_DETAILS = {
         'endpoint': 'ec2.ap-southeast-2.amazonaws.com',
         'api_name': 'ec2_ap_southeast_2',
         'country': 'Australia',
+        'signature_version': '2',
         'instance_types': [
             't1.micro',
             'm1.small',
@@ -714,6 +724,7 @@ REGION_DETAILS = {
         'endpoint': 'ec2.us-gov-west-1.amazonaws.com',
         'api_name': 'ec2_us_govwest',
         'country': 'US',
+        'signature_version': '2',
         'instance_types': [
             't1.micro',
             'm1.small',
@@ -755,6 +766,7 @@ REGION_DETAILS = {
         # Nimbus clouds have 3 EC2-style instance types but their particular
         # RAM allocations are configured by the admin
         'country': 'custom',
+        'signature_version': '2',
         'instance_types': [
             'm1.small',
             'm1.large',
@@ -4636,6 +4648,11 @@ class BaseEC2NodeDriver(NodeDriver):
 
         return self._get_boolean(res)
 
+    def _ex_connection_class_kwargs(self):
+        kwargs = super(BaseEC2NodeDriver, self)._ex_connection_class_kwargs()
+        kwargs['signature_version'] = self.signature_version
+        return kwargs
+
     def _to_nodes(self, object, xpath):
         return [self._to_node(el)
                 for el in object.findall(fixxpath(xpath=xpath,
@@ -5560,6 +5577,8 @@ class EC2NodeDriver(BaseEC2NodeDriver):
         self.region_name = region
         self.api_name = details['api_name']
         self.country = details['country']
+        self.signature_version = details.pop('signature_version',
+                                             DEFAULT_SIGNATURE_VERSION)
 
         host = host or details['endpoint']