Sophie

Sophie

distrib > Fedora > 19 > i386 > media > updates-src > by-pkgid > ba59a791bd2083edb232ab94febba914 > files > 8

python-keystoneclient-0.2.3-7.fc19.src.rpm

From fafbb97aa1d579a077611889e08f63822fa384df Mon Sep 17 00:00:00 2001
From: Steven Hardy <shardy@redhat.com>
Date: Fri, 26 Jul 2013 11:42:29 +0100
Subject: [PATCH] Ec2Signer : Allow signature verification for older boto
 versions

Since the fix for bug #1197553, verification for older clients (which strip the
port when formatting the request) fails.

This conditionally reverts to the original behavior, by detecting the boto
version via the User-Agent header, the default behavior will be the new
behavior (which doesn't strip the port), but this will allow a less painful
transition for clients/distros to the new boto version.

Fixes bug #1205281

Change-Id: I54ac9c5ba91e697004f1346a8f2d685da488992a
---
 keystoneclient/contrib/ec2/utils.py |  18 ++++--
 tests/test_ec2utils.py              | 113 ++++++++++++++++++++++++++++++++++++
 2 files changed, 127 insertions(+), 4 deletions(-)

diff --git a/keystoneclient/contrib/ec2/utils.py b/keystoneclient/contrib/ec2/utils.py
index ca5afa7..2839654 100644
--- a/keystoneclient/contrib/ec2/utils.py
+++ b/keystoneclient/contrib/ec2/utils.py
@@ -21,6 +21,7 @@
 import base64
 import hashlib
 import hmac
+import re
 import urllib
 
 
@@ -211,17 +212,26 @@ class Ec2Signer(object):
             # - the X-Amz-SignedHeaders query parameter
             headers_lower = dict((k.lower().strip(), v.strip())
                                  for (k, v) in headers.iteritems())
+
+            # Boto versions < 2.9.3 strip the port component of the host:port
+            # header, so detect the user-agent via the header and strip the
+            # port if we detect an old boto version.  FIXME: remove when all
+            # distros package boto >= 2.9.3, this is a transitional workaround
+            user_agent = headers_lower.get('user-agent', '')
+            strip_port = re.match('Boto/2.[0-9].[0-2]', user_agent)
+
             header_list = []
             sh_str = auth_param('SignedHeaders')
             for h in sh_str.split(';'):
                 if h not in headers_lower:
                     continue
-                if h == 'host':
-                    # Note we discard any port suffix
+
+                if h == 'host' and strip_port:
                     header_list.append('%s:%s' %
                                        (h, headers_lower[h].split(':')[0]))
-                else:
-                    header_list.append('%s:%s' % (h, headers_lower[h]))
+                    continue
+
+                header_list.append('%s:%s' % (h, headers_lower[h]))
             return '\n'.join(header_list) + '\n'
 
         # Create canonical request:
diff --git a/tests/test_ec2utils.py b/tests/test_ec2utils.py
index a3c36fa..c5edfcc 100644
--- a/tests/test_ec2utils.py
+++ b/tests/test_ec2utils.py
@@ -143,3 +143,116 @@ class Ec2SignerTest(testtools.TestCase):
         expected = ('ced6826de92d2bdeed8f846f0bf508e8'
                     '559e98e4b0199114b84c54174deb456c')
         self.assertEqual(signature, expected)
+
+    def test_generate_v4_port(self):
+        """
+        Test v4 generator with host:port format
+        """
+        # Create a new signer object with the AWS example key
+        secret = 'wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY'
+        signer = Ec2Signer(secret)
+
+        body_hash = ('b6359072c78d70ebee1e81adcbab4f0'
+                     '1bf2c23245fa365ef83fe8f1f955085e2')
+        auth_str = ('AWS4-HMAC-SHA256 '
+                    'Credential=AKIAIOSFODNN7EXAMPLE/20110909/'
+                    'us-east-1/iam/aws4_request,'
+                    'SignedHeaders=content-type;host;x-amz-date,')
+        headers = {'Content-type':
+                   'application/x-www-form-urlencoded; charset=utf-8',
+                   'X-Amz-Date': '20110909T233600Z',
+                   'Host': 'foo:8000',
+                   'Authorization': auth_str}
+        # Note the example in the AWS docs is inconsistent, previous
+        # examples specify no query string, but the final POST example
+        # does, apparently incorrectly since an empty parameter list
+        # aligns all steps and the final signature with the examples
+        params = {}
+        credentials = {'host': 'foo:8000',
+                       'verb': 'POST',
+                       'path': '/',
+                       'params': params,
+                       'headers': headers,
+                       'body_hash': body_hash}
+        signature = signer.generate(credentials)
+
+        expected = ('26dd92ea79aaa49f533d13b1055acdc'
+                    'd7d7321460d64621f96cc79c4f4d4ab2b')
+        self.assertEqual(signature, expected)
+
+    def test_generate_v4_port_strip(self):
+        """
+        Test v4 generator with host:port format, but for an old
+        (<2.9.3) version of boto, where the port should be stripped
+        to match boto behavior
+        """
+        # Create a new signer object with the AWS example key
+        secret = 'wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY'
+        signer = Ec2Signer(secret)
+
+        body_hash = ('b6359072c78d70ebee1e81adcbab4f0'
+                     '1bf2c23245fa365ef83fe8f1f955085e2')
+        auth_str = ('AWS4-HMAC-SHA256 '
+                    'Credential=AKIAIOSFODNN7EXAMPLE/20110909/'
+                    'us-east-1/iam/aws4_request,'
+                    'SignedHeaders=content-type;host;x-amz-date,')
+        headers = {'Content-type':
+                   'application/x-www-form-urlencoded; charset=utf-8',
+                   'X-Amz-Date': '20110909T233600Z',
+                   'Host': 'foo:8000',
+                   'Authorization': auth_str,
+                   'User-Agent': 'Boto/2.9.2 (linux2)'}
+        # Note the example in the AWS docs is inconsistent, previous
+        # examples specify no query string, but the final POST example
+        # does, apparently incorrectly since an empty parameter list
+        # aligns all steps and the final signature with the examples
+        params = {}
+        credentials = {'host': 'foo:8000',
+                       'verb': 'POST',
+                       'path': '/',
+                       'params': params,
+                       'headers': headers,
+                       'body_hash': body_hash}
+        signature = signer.generate(credentials)
+
+        expected = ('9a4b2276a5039ada3b90f72ea8ec1745'
+                    '14b92b909fb106b22ad910c5d75a54f4')
+        self.assertEqual(expected, signature)
+
+    def test_generate_v4_port_nostrip(self):
+        """
+        Test v4 generator with host:port format, but for an new
+        (>=2.9.3) version of boto, where the port should not be stripped
+        """
+        # Create a new signer object with the AWS example key
+        secret = 'wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY'
+        signer = Ec2Signer(secret)
+
+        body_hash = ('b6359072c78d70ebee1e81adcbab4f0'
+                     '1bf2c23245fa365ef83fe8f1f955085e2')
+        auth_str = ('AWS4-HMAC-SHA256 '
+                    'Credential=AKIAIOSFODNN7EXAMPLE/20110909/'
+                    'us-east-1/iam/aws4_request,'
+                    'SignedHeaders=content-type;host;x-amz-date,')
+        headers = {'Content-type':
+                   'application/x-www-form-urlencoded; charset=utf-8',
+                   'X-Amz-Date': '20110909T233600Z',
+                   'Host': 'foo:8000',
+                   'Authorization': auth_str,
+                   'User-Agent': 'Boto/2.9.3 (linux2)'}
+        # Note the example in the AWS docs is inconsistent, previous
+        # examples specify no query string, but the final POST example
+        # does, apparently incorrectly since an empty parameter list
+        # aligns all steps and the final signature with the examples
+        params = {}
+        credentials = {'host': 'foo:8000',
+                       'verb': 'POST',
+                       'path': '/',
+                       'params': params,
+                       'headers': headers,
+                       'body_hash': body_hash}
+        signature = signer.generate(credentials)
+
+        expected = ('26dd92ea79aaa49f533d13b1055acdc'
+                    'd7d7321460d64621f96cc79c4f4d4ab2b')
+        self.assertEqual(expected, signature)