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
This commit is contained in:
Steven Hardy
2013-07-26 11:42:29 +01:00
parent 262c17922a
commit fb6792ada7
2 changed files with 92 additions and 0 deletions

View File

@@ -21,6 +21,7 @@
import base64 import base64
import hashlib import hashlib
import hmac import hmac
import re
import urllib import urllib
@@ -211,11 +212,25 @@ class Ec2Signer(object):
# - the X-Amz-SignedHeaders query parameter # - the X-Amz-SignedHeaders query parameter
headers_lower = dict((k.lower().strip(), v.strip()) headers_lower = dict((k.lower().strip(), v.strip())
for (k, v) in headers.iteritems()) 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 = [] header_list = []
sh_str = auth_param('SignedHeaders') sh_str = auth_param('SignedHeaders')
for h in sh_str.split(';'): for h in sh_str.split(';'):
if h not in headers_lower: if h not in headers_lower:
continue continue
if h == 'host' and strip_port:
header_list.append('%s:%s' %
(h, headers_lower[h].split(':')[0]))
continue
header_list.append('%s:%s' % (h, headers_lower[h])) header_list.append('%s:%s' % (h, headers_lower[h]))
return '\n'.join(header_list) + '\n' return '\n'.join(header_list) + '\n'

View File

@@ -179,3 +179,80 @@ class Ec2SignerTest(testtools.TestCase):
expected = ('26dd92ea79aaa49f533d13b1055acdc' expected = ('26dd92ea79aaa49f533d13b1055acdc'
'd7d7321460d64621f96cc79c4f4d4ab2b') 'd7d7321460d64621f96cc79c4f4d4ab2b')
self.assertEqual(signature, expected) 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)