Merge lp:~itohm/nova/enable-rightaws into lp:~hudson-openstack/nova/trunk

Proposed by Masanori Itoh
Status: Merged
Approved by: Vish Ishaya
Approved revision: 967
Merged at revision: 1061
Proposed branch: lp:~itohm/nova/enable-rightaws
Merge into: lp:~hudson-openstack/nova/trunk
Diff against target: 162 lines (+108/-9)
4 files modified
nova/auth/manager.py (+16/-6)
nova/tests/test_auth.py (+37/-3)
nova/tests/test_utils.py (+25/-0)
nova/utils.py (+30/-0)
To merge this branch: bzr merge lp:~itohm/nova/enable-rightaws
Reviewer Review Type Date Requested Status
Dan Prince (community) Approve
Vish Ishaya (community) Approve
Matt Dietz (community) Needs Fixing
Rick Harris (community) Approve
Review via email: mp+57370@code.launchpad.net

Commit message

Enable RightAWS style signature checking using server_string without port number, add test cases for authenticate() and a new helper routine, and fix lp753660.

Description of the change

Enable RightAWS style signing on server_string without port number and fixes lp753660.
In short, when nova detected signature unmatch failure, try one more signature generated
using server_string without port number. e.g., drop ':8773' of '10.1.2.3:8773'
For more details, I wrote explanation why this patch is needed and pasted some evidences in the bug report.
I hope this fix makes sense for people.

To post a comment you must log in.
Revision history for this message
Rick Harris (rconradharris) wrote :

Looks good, nice work Masanori.

Just some *really* small style points:

> 4 + LOG.debug('host_only_signature: %s', host_only_signature)

Needs i18n treatment.

> 50 + [address, sep, port] = server_str.partition(':')

Might be cleaner to use `split` since the separator is ignored as well as unpacking to a tuple since that's more conventional:

    (address, port) = server_str.split(':')

Side note: I think when/if we move to 2.7 we can eventually replace the host-extraction code with urlparse (which supports IPv6 addresses and differentiating the host and port portion of the netloc).

review: Needs Fixing
Revision history for this message
Masanori Itoh (itohm) wrote :

Hi Rick,

Thanks for the review!

I updated the branch according to your comments.
Actually, I found some more lines which need I18N treatment
in the same method which I modified and fixed them.

Also, I rebased to trunk rev 987, ran run_tests.sh and did some other checking.
I hope this looks goot for folks.

Regards,
Masanori

From: Rick Harris <email address hidden>
Subject: Re: [Merge] lp:~itoumsn/nova/enable-rightaws into lp:nova
Date: Thu, 14 Apr 2011 05:55:46 +0900

> Review: Needs Fixing
> Looks good, nice work Masanori.
>
> Just some *really* small style points:
>
> > 4 + LOG.debug('host_only_signature: %s', host_only_signature)
>
> Needs i18n treatment.
>
> > 50 + [address, sep, port] = server_str.partition(':')
>
> Might be cleaner to use `split` since the separator is ignored as well as unpacking to a tuple since that's more conventional:
>
> (address, port) = server_str.split(':')
>
> Side note: I think when/if we move to 2.7 we can eventually replace the host-extraction code with urlparse (which supports IPv6 addresses and differentiating the host and port portion of the netloc).
>
>
>
> --
> https://code.launchpad.net/~itoumsn/nova/enable-rightaws/+merge/57370
> You are the owner of lp:~itoumsn/nova/enable-rightaws.

Revision history for this message
Rick Harris (rconradharris) wrote :

lgtm, thanks for the fixups!

review: Approve
Revision history for this message
Matt Dietz (cerberus) wrote :

Looks good, but can we get some unit tests for this, as well as the helper method in utils?

review: Needs Fixing
Revision history for this message
Masanori Itoh (itohm) wrote :

Thanks Matt!

OK.
I'll consider some test cases for both auth code and the helper method.
I set the status "Work In Progress", and give me time a bit.

Regards,
Masanori

From: Matt Dietz <email address hidden>
Subject: Re: [Merge] lp:~itoumsn/nova/enable-rightaws into lp:nova
Date: Wed, 20 Apr 2011 07:41:18 +0900

> Review: Needs Fixing
> Looks good, but can we get some unit tests for this, as well as the helper method in utils?
> --
> https://code.launchpad.net/~itoumsn/nova/enable-rightaws/+merge/57370
> You are the owner of lp:~itoumsn/nova/enable-rightaws.

Revision history for this message
Masanori Itoh (itohm) wrote :

Hi,

Following Matt's comment, I added test cases for the new helper routine which I added and the authenticate() method in nova/auth/manager.py. Actually, the latter test case, test_004_signature_is_valid(), did nothing so far. I implemented it this time for two patterns of signature generation, using server_string with/without a port number.

One more thing is that I renamed the new helper function to make it more generic one
from get_host_only_server_string() to parse_server_string(). Now, it returns a list of
host and port strings.

One possible issue is that in the test case for authenticate() I used signatures and query strings captured on my development pc. They work anyway, but I'm not sure if it looks good for nova-core people.

-Masanori

Revision history for this message
Vish Ishaya (vishvananda) wrote :

Wow, nice job on the tests. We really should remove the numbers from those tests. 004 and 005 are left over from old tests and 005 doesn't even do anything, but i'm ok with this patch as is.

review: Approve
Revision history for this message
Masanori Itoh (itohm) wrote :

Hi,

I rebased my branch to trunk rev 1057, and did run_tests.sh.
No code change.

-Masanori

lp:~itohm/nova/enable-rightaws updated
965. By Masanori Itoh

Rebased to trunk rev 1030.

966. By Masanori Itoh

Rebased to trunk rev 1035.

967. By Masanori Itoh

Rebased to trunk rev 1057.

Revision history for this message
Dan Prince (dan-prince) wrote :

This works great. Been waiting for this one. My script:

---
require 'rubygems'
require 'right_aws'

ACCESS_KEY=ENV['EC2_ACCESS_KEY']
SECRET_KEY=ENV['EC2_SECRET_KEY']
EC2_URL=ENV['EC2_URL']

@ec2 = RightAws::Ec2.new(ACCESS_KEY, SECRET_KEY, { :endpoint_url => EC2_URL})
images = @ec2.describe_images
puts images.inspect
---

Approve.

review: Approve
Revision history for this message
Vish Ishaya (vishvananda) wrote :

looks like matts review has been handled. Approving.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'nova/auth/manager.py'
2--- nova/auth/manager.py 2011-04-30 02:31:29 +0000
3+++ nova/auth/manager.py 2011-05-07 15:42:37 +0000
4@@ -305,9 +305,9 @@
5 if check_type == 's3':
6 sign = signer.Signer(user.secret.encode())
7 expected_signature = sign.s3_authorization(headers, verb, path)
8- LOG.debug('user.secret: %s', user.secret)
9- LOG.debug('expected_signature: %s', expected_signature)
10- LOG.debug('signature: %s', signature)
11+ LOG.debug(_('user.secret: %s'), user.secret)
12+ LOG.debug(_('expected_signature: %s'), expected_signature)
13+ LOG.debug(_('signature: %s'), signature)
14 if signature != expected_signature:
15 LOG.audit(_("Invalid signature for user %s"), user.name)
16 raise exception.InvalidSignature(signature=signature,
17@@ -317,10 +317,20 @@
18 # secret isn't unicode
19 expected_signature = signer.Signer(user.secret.encode()).generate(
20 params, verb, server_string, path)
21- LOG.debug('user.secret: %s', user.secret)
22- LOG.debug('expected_signature: %s', expected_signature)
23- LOG.debug('signature: %s', signature)
24+ LOG.debug(_('user.secret: %s'), user.secret)
25+ LOG.debug(_('expected_signature: %s'), expected_signature)
26+ LOG.debug(_('signature: %s'), signature)
27 if signature != expected_signature:
28+ (addr_str, port_str) = utils.parse_server_string(server_string)
29+ # If the given server_string contains port num, try without it.
30+ if port_str != '':
31+ host_only_signature = signer.Signer(
32+ user.secret.encode()).generate(params, verb,
33+ addr_str, path)
34+ LOG.debug(_('host_only_signature: %s'),
35+ host_only_signature)
36+ if signature == host_only_signature:
37+ return (user, project)
38 LOG.audit(_("Invalid signature for user %s"), user.name)
39 raise exception.InvalidSignature(signature=signature,
40 user=user)
41
42=== modified file 'nova/tests/test_auth.py'
43--- nova/tests/test_auth.py 2011-03-28 15:05:28 +0000
44+++ nova/tests/test_auth.py 2011-05-07 15:42:37 +0000
45@@ -101,9 +101,43 @@
46 self.assertEqual('private-party', u.access)
47
48 def test_004_signature_is_valid(self):
49- #self.assertTrue(self.manager.authenticate(**boto.generate_url ...? ))
50- pass
51- #raise NotImplementedError
52+ with user_generator(self.manager, name='admin', secret='admin',
53+ access='admin'):
54+ with project_generator(self.manager, name="admin",
55+ manager_user='admin'):
56+ accesskey = 'admin:admin'
57+ expected_result = (self.manager.get_user('admin'),
58+ self.manager.get_project('admin'))
59+ # captured sig and query string using boto 1.9b/euca2ools 1.2
60+ sig = 'd67Wzd9Bwz8xid9QU+lzWXcF2Y3tRicYABPJgrqfrwM='
61+ auth_params = {'AWSAccessKeyId': 'admin:admin',
62+ 'Action': 'DescribeAvailabilityZones',
63+ 'SignatureMethod': 'HmacSHA256',
64+ 'SignatureVersion': '2',
65+ 'Timestamp': '2011-04-22T11:29:29',
66+ 'Version': '2009-11-30'}
67+ self.assertTrue(expected_result, self.manager.authenticate(
68+ accesskey,
69+ sig,
70+ auth_params,
71+ 'GET',
72+ '127.0.0.1:8773',
73+ '/services/Cloud/'))
74+ # captured sig and query string using RightAWS 1.10.0
75+ sig = 'ECYLU6xdFG0ZqRVhQybPJQNJ5W4B9n8fGs6+/fuGD2c='
76+ auth_params = {'AWSAccessKeyId': 'admin:admin',
77+ 'Action': 'DescribeAvailabilityZones',
78+ 'SignatureMethod': 'HmacSHA256',
79+ 'SignatureVersion': '2',
80+ 'Timestamp': '2011-04-22T11:29:49.000Z',
81+ 'Version': '2008-12-01'}
82+ self.assertTrue(expected_result, self.manager.authenticate(
83+ accesskey,
84+ sig,
85+ auth_params,
86+ 'GET',
87+ '127.0.0.1',
88+ '/services/Cloud'))
89
90 def test_005_can_get_credentials(self):
91 return
92
93=== modified file 'nova/tests/test_utils.py'
94--- nova/tests/test_utils.py 2011-03-17 20:43:22 +0000
95+++ nova/tests/test_utils.py 2011-05-07 15:42:37 +0000
96@@ -250,3 +250,28 @@
97 input = {'a': [1, 2, {'b': 'b_1'}]}
98 self.assertEquals([1, 2, {'b': 'b_1'}], f(input, "a"))
99 self.assertEquals(['b_1'], f(input, "a/b"))
100+
101+
102+class GenericUtilsTestCase(test.TestCase):
103+ def test_parse_server_string(self):
104+ result = utils.parse_server_string('::1')
105+ self.assertEqual(('::1', ''), result)
106+ result = utils.parse_server_string('[::1]:8773')
107+ self.assertEqual(('::1', '8773'), result)
108+ result = utils.parse_server_string('2001:db8::192.168.1.1')
109+ self.assertEqual(('2001:db8::192.168.1.1', ''), result)
110+ result = utils.parse_server_string('[2001:db8::192.168.1.1]:8773')
111+ self.assertEqual(('2001:db8::192.168.1.1', '8773'), result)
112+ result = utils.parse_server_string('192.168.1.1')
113+ self.assertEqual(('192.168.1.1', ''), result)
114+ result = utils.parse_server_string('192.168.1.2:8773')
115+ self.assertEqual(('192.168.1.2', '8773'), result)
116+ result = utils.parse_server_string('192.168.1.3')
117+ self.assertEqual(('192.168.1.3', ''), result)
118+ result = utils.parse_server_string('www.example.com:8443')
119+ self.assertEqual(('www.example.com', '8443'), result)
120+ result = utils.parse_server_string('www.example.com')
121+ self.assertEqual(('www.example.com', ''), result)
122+ # error case
123+ result = utils.parse_server_string('www.exa:mple.com:8443')
124+ self.assertEqual(('', ''), result)
125
126=== modified file 'nova/utils.py'
127--- nova/utils.py 2011-04-22 19:44:24 +0000
128+++ nova/utils.py 2011-05-07 15:42:37 +0000
129@@ -709,3 +709,33 @@
130 raise Exception(_('Expected object of type: %s') % (str(cls)))
131 # TODO(justinsb): Can we make this better??
132 return cls() # Ugly PyLint hack
133+
134+
135+def parse_server_string(server_str):
136+ """
137+ Parses the given server_string and returns a list of host and port.
138+ If it's not a combination of host part and port, the port element
139+ is a null string. If the input is invalid expression, return a null
140+ list.
141+ """
142+ try:
143+ # First of all, exclude pure IPv6 address (w/o port).
144+ if netaddr.valid_ipv6(server_str):
145+ return (server_str, '')
146+
147+ # Next, check if this is IPv6 address with a port number combination.
148+ if server_str.find("]:") != -1:
149+ (address, port) = server_str.replace('[', '', 1).split(']:')
150+ return (address, port)
151+
152+ # Third, check if this is a combination of an address and a port
153+ if server_str.find(':') == -1:
154+ return (server_str, '')
155+
156+ # This must be a combination of an address and a port
157+ (address, port) = server_str.split(':')
158+ return (address, port)
159+
160+ except:
161+ LOG.debug(_('Invalid server_string: %s' % server_str))
162+ return ('', '')