Merge lp:~vishvananda/nova/keystone-migration-ec2 into lp:~hudson-openstack/nova/trunk

Proposed by Vish Ishaya
Status: Merged
Approved by: Brian Lamar
Approved revision: 1423
Merged at revision: 1467
Proposed branch: lp:~vishvananda/nova/keystone-migration-ec2
Merge into: lp:~hudson-openstack/nova/trunk
Diff against target: 310 lines (+158/-20)
7 files modified
etc/nova/api-paste.ini (+28/-1)
nova/api/auth.py (+75/-0)
nova/api/ec2/__init__.py (+49/-4)
nova/api/ec2/metadatarequesthandler.py (+1/-0)
nova/tests/api/openstack/fakes.py (+3/-2)
nova/tests/test_api.py (+2/-1)
nova/wsgi.py (+0/-12)
To merge this branch: bzr merge lp:~vishvananda/nova/keystone-migration-ec2
Reviewer Review Type Date Requested Status
Brian Lamar (community) Approve
Dan Prince (community) Approve
Devin Carlen (community) Needs Information
Review via email: mp+71311@code.launchpad.net

Description of the change

Next round of prep for keystone integration.

 * adds middleware for authenticating ec2 signature with keystone
 * adds middleware for converting keystone response into request context
 * gives examples of alternative pipelines for keystone integration

Next steps:
 * provide default config with no keystone integration (perhaps setting every context to admin?)
 * write authmanager to keystone conversion code
 * add api extension to create and destroy access/secret keys
 * deprecate authmanager
 * rename project to tenant

To post a comment you must log in.
1420. By Vish Ishaya

remove extra log statements

Revision history for this message
Brian Lamar (blamar) wrote :

A few questions first:

Why include AdminContext middleware if it's not being used? Does it provide any benefit currently?

The KeystoneContext middleware is strikingly similar to the KeystoneAuthShim in keystone/middleware/nova_auth_token.py (in the Keystone project)... is this just another candidate for openstack-common?

Then some nits:

84 +"""
85 +Common Auth Middleware.
86 +
87 +"""
88 +
89 +from nova import context
90 +from nova import flags
91 +from nova import wsgi
92 +import webob.dec
93 +import webob.exc

"""Common Auth Middleware."""

import webob.dec
import webob.exc

from nova import context
from nova import flags
from nova import wsgi

104 + def __init__(self, context, *args, **kwargs):

Can you put a spacer before this?

139 + except:

except KeyError:

204 + except KeyError, e:

No need to specify e.

review: Needs Fixing
Revision history for this message
Devin Carlen (devcamcar) wrote :

Vish, this is looking good. I do have a couple of questions:

Is the intent here to return a dummy admin context, with no relationship to an auth system or real credentials? I ask because we have made efforts not to hardcode 'admin'/'admin' in other places.

123 + ctx = context.RequestContext('admin',
124 + 'admin',
125 + is_admin=True,
126 + remote_address=remote_address)

Also, you mentioned renaming project to tenant, but that work doesn't seem to have been done in this branch.

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

> Also, you mentioned renaming project to tenant, but that work doesn't seem to
> have been done in this branch.

The renaming was in next steps. Addressing the admin context point in my comment to Brian

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

> A few questions first:
>
> Why include AdminContext middleware if it's not being used? Does it provide
> any benefit currently?

I can remove this for now. This was more just an example of how we could offer a version with no authentication if we want to be able to run without keystone. The plan is to deprecate AuthManager, so if we want a way to run without keystone, we need to create a context without authentication. This could be just allowing a user to authenticate for any user and tenant or giving everyone an adimn context. I'm going to address this in a separate patch so I will remove it for now.
>
> The KeystoneContext middleware is strikingly similar to the KeystoneAuthShim
> in keystone/middleware/nova_auth_token.py (in the Keystone project)... is this
> just another candidate for openstack-common?

The auth-shim was a temporary shim until we could manage users and projects in keystone. The idea is that this middleware will replace the keystone shim. I think it belongs here instead of keystone.

>
>
> Then some nits:
>
> 84 +"""
> 85 +Common Auth Middleware.
> 86 +
> 87 +"""
> 88 +
> 89 +from nova import context
> 90 +from nova import flags
> 91 +from nova import wsgi
> 92 +import webob.dec
> 93 +import webob.exc
>
> """Common Auth Middleware."""
>
> import webob.dec
> import webob.exc
>
> from nova import context
> from nova import flags
> from nova import wsgi

done

>
>
> 104 + def __init__(self, context, *args, **kwargs):
>
> Can you put a spacer before this?

done

>
>
> 139 + except:
>
> except KeyError:

done
>
>
> 204 + except KeyError, e:
>
> No need to specify e.

done

1421. By Vish Ishaya

updates from review

1422. By Vish Ishaya

removed admincontext middleware

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

Removing the use_forwarded_for flag from nova/api/ec2/__init__.py will cause an AttributeError (because it is still being used in __init__.py).

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

> Removing the use_forwarded_for flag from nova/api/ec2/__init__.py will cause
> an AttributeError (because it is still being used in __init__.py).

Thanks Dan. Fixed the other files to declare the flag.

1423. By Vish Ishaya

declare the use_forwarded_for flag

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

Looks good.

review: Approve
Revision history for this message
Brian Lamar (blamar) wrote :

Good here.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'etc/nova/api-paste.ini'
2--- etc/nova/api-paste.ini 2011-08-15 22:09:42 +0000
3+++ etc/nova/api-paste.ini 2011-08-18 22:43:01 +0000
4@@ -20,7 +20,8 @@
5
6 [pipeline:ec2cloud]
7 pipeline = logrequest authenticate cloudrequest authorizer ec2executor
8-#pipeline = logrequest ec2lockout authenticate cloudrequest authorizer ec2executor
9+# NOTE(vish): use the following pipeline for keystone
10+# pipeline = logrequest totoken authtoken keystonecontext cloudrequest authorizer ec2executor
11
12 [pipeline:ec2admin]
13 pipeline = logrequest authenticate adminrequest authorizer ec2executor
14@@ -37,6 +38,9 @@
15 [filter:ec2lockout]
16 paste.filter_factory = nova.api.ec2:Lockout.factory
17
18+[filter:totoken]
19+paste.filter_factory = nova.api.ec2:ToToken.factory
20+
21 [filter:authenticate]
22 paste.filter_factory = nova.api.ec2:Authenticate.factory
23
24@@ -72,9 +76,13 @@
25
26 [pipeline:openstackapi10]
27 pipeline = faultwrap auth ratelimit osapiapp10
28+# NOTE(vish): use the following pipeline for keystone
29+#pipeline = faultwrap authtoken keystonecontext ratelimit osapiapp10
30
31 [pipeline:openstackapi11]
32 pipeline = faultwrap auth ratelimit extensions osapiapp11
33+# NOTE(vish): use the following pipeline for keystone
34+# pipeline = faultwrap authtoken keystonecontext ratelimit extensions osapiapp11
35
36 [filter:faultwrap]
37 paste.filter_factory = nova.api.openstack:FaultWrapper.factory
38@@ -99,3 +107,22 @@
39
40 [app:osversionapp]
41 paste.app_factory = nova.api.openstack.versions:Versions.factory
42+
43+##########
44+# Shared #
45+##########
46+
47+[filter:keystonecontext]
48+paste.filter_factory = nova.api.auth:KeystoneContext.factory
49+
50+[filter:authtoken]
51+paste.filter_factory = keystone.middleware.auth_token:filter_factory
52+service_protocol = http
53+service_host = 127.0.0.1
54+service_port = 808
55+auth_host = 127.0.0.1
56+auth_port = 5001
57+auth_protocol = http
58+auth_uri = http://127.0.0.1:5000/
59+admin_token = 999888777666
60+
61
62=== added file 'nova/api/auth.py'
63--- nova/api/auth.py 1970-01-01 00:00:00 +0000
64+++ nova/api/auth.py 2011-08-18 22:43:01 +0000
65@@ -0,0 +1,75 @@
66+# vim: tabstop=4 shiftwidth=4 softtabstop=4
67+
68+# Copyright (c) 2011 OpenStack, LLC
69+#
70+# Licensed under the Apache License, Version 2.0 (the "License"); you may
71+# not use this file except in compliance with the License. You may obtain
72+# a copy of the License at
73+#
74+# http://www.apache.org/licenses/LICENSE-2.0
75+#
76+# Unless required by applicable law or agreed to in writing, software
77+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
78+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
79+# License for the specific language governing permissions and limitations
80+# under the License.
81+"""
82+Common Auth Middleware.
83+
84+"""
85+
86+import webob.dec
87+import webob.exc
88+
89+from nova import context
90+from nova import flags
91+from nova import wsgi
92+
93+
94+FLAGS = flags.FLAGS
95+flags.DEFINE_boolean('use_forwarded_for', False,
96+ 'Treat X-Forwarded-For as the canonical remote address. '
97+ 'Only enable this if you have a sanitizing proxy.')
98+
99+
100+class InjectContext(wsgi.Middleware):
101+ """Add a 'nova.context' to WSGI environ."""
102+
103+ def __init__(self, context, *args, **kwargs):
104+ self.context = context
105+ super(InjectContext, self).__init__(*args, **kwargs)
106+
107+ @webob.dec.wsgify(RequestClass=wsgi.Request)
108+ def __call__(self, req):
109+ req.environ['nova.context'] = self.context
110+ return self.application
111+
112+
113+class KeystoneContext(wsgi.Middleware):
114+ """Make a request context from keystone headers"""
115+
116+ @webob.dec.wsgify(RequestClass=wsgi.Request)
117+ def __call__(self, req):
118+ try:
119+ user_id = req.headers['X_USER']
120+ except KeyError:
121+ return webob.exc.HTTPUnauthorized()
122+ # get the roles
123+ roles = [r.strip() for r in req.headers.get('X_ROLE', '').split(',')]
124+ project_id = req.headers['X_TENANT']
125+ # Get the auth token
126+ auth_token = req.headers.get('X_AUTH_TOKEN',
127+ req.headers.get('X_STORAGE_TOKEN'))
128+
129+ # Build a context, including the auth_token...
130+ remote_address = req.remote_addr
131+ if FLAGS.use_forwarded_for:
132+ remote_address = req.headers.get('X-Forwarded-For', remote_address)
133+ ctx = context.RequestContext(user_id,
134+ project_id,
135+ roles=roles,
136+ auth_token=auth_token,
137+ remote_address=remote_address)
138+
139+ req.environ['nova.context'] = ctx
140+ return self.application
141
142=== modified file 'nova/api/ec2/__init__.py'
143--- nova/api/ec2/__init__.py 2011-08-15 22:09:42 +0000
144+++ nova/api/ec2/__init__.py 2011-08-18 22:43:01 +0000
145@@ -20,6 +20,7 @@
146
147 """
148
149+import httplib2
150 import webob
151 import webob.dec
152 import webob.exc
153@@ -37,15 +38,16 @@
154
155 FLAGS = flags.FLAGS
156 LOG = logging.getLogger("nova.api")
157-flags.DEFINE_boolean('use_forwarded_for', False,
158- 'Treat X-Forwarded-For as the canonical remote address. '
159- 'Only enable this if you have a sanitizing proxy.')
160 flags.DEFINE_integer('lockout_attempts', 5,
161 'Number of failed auths before lockout.')
162 flags.DEFINE_integer('lockout_minutes', 15,
163 'Number of minutes to lockout if triggered.')
164 flags.DEFINE_integer('lockout_window', 15,
165 'Number of minutes for lockout window.')
166+flags.DEFINE_string('keystone_ec2_url',
167+ 'http://localhost:5000/v2.0/ec2tokens',
168+ 'URL to get token from ec2 request.')
169+flags.DECLARE('use_forwarded_for', 'nova.api.auth')
170
171
172 class RequestLogging(wsgi.Middleware):
173@@ -138,6 +140,49 @@
174 return res
175
176
177+class ToToken(wsgi.Middleware):
178+ """Authenticate an EC2 request with keystone and convert to token."""
179+
180+ @webob.dec.wsgify(RequestClass=wsgi.Request)
181+ def __call__(self, req):
182+ # Read request signature and access id.
183+ try:
184+ signature = req.params['Signature']
185+ access = req.params['AWSAccessKeyId']
186+ except KeyError:
187+ raise webob.exc.HTTPBadRequest()
188+
189+ # Make a copy of args for authentication and signature verification.
190+ auth_params = dict(req.params)
191+ # Not part of authentication args
192+ auth_params.pop('Signature')
193+
194+ # Authenticate the request.
195+ client = httplib2.Http()
196+ creds = {'ec2Credentials': {'access': access,
197+ 'signature': signature,
198+ 'host': req.host,
199+ 'verb': req.method,
200+ 'path': req.path,
201+ 'params': auth_params,
202+ }}
203+ headers = {'Content-Type': 'application/json'},
204+ resp, content = client.request(FLAGS.keystone_ec2_url,
205+ 'POST',
206+ headers=headers,
207+ body=utils.dumps(creds))
208+ # NOTE(vish): We could save a call to keystone by
209+ # having keystone return token, tenant,
210+ # user, and roles from this call.
211+ result = utils.loads(content)
212+ # TODO(vish): check for errors
213+ token_id = result['auth']['token']['id']
214+
215+ # Authenticated!
216+ req.headers['X-Auth-Token'] = token_id
217+ return self.application
218+
219+
220 class Authenticate(wsgi.Middleware):
221 """Authenticate an EC2 request and add 'nova.context' to WSGI environ."""
222
223@@ -147,7 +192,7 @@
224 try:
225 signature = req.params['Signature']
226 access = req.params['AWSAccessKeyId']
227- except KeyError, e:
228+ except KeyError:
229 raise webob.exc.HTTPBadRequest()
230
231 # Make a copy of args for authentication and signature verification.
232
233=== modified file 'nova/api/ec2/metadatarequesthandler.py'
234--- nova/api/ec2/metadatarequesthandler.py 2011-06-26 00:29:14 +0000
235+++ nova/api/ec2/metadatarequesthandler.py 2011-08-18 22:43:01 +0000
236@@ -30,6 +30,7 @@
237
238 LOG = logging.getLogger('nova.api.ec2.metadata')
239 FLAGS = flags.FLAGS
240+flags.DECLARE('use_forwarded_for', 'nova.api.auth')
241
242
243 class MetadataRequestHandler(wsgi.Application):
244
245=== modified file 'nova/tests/api/openstack/fakes.py'
246--- nova/tests/api/openstack/fakes.py 2011-08-15 22:09:42 +0000
247+++ nova/tests/api/openstack/fakes.py 2011-08-18 22:43:01 +0000
248@@ -32,6 +32,7 @@
249 from nova import wsgi
250 import nova.api.openstack.auth
251 from nova.api import openstack
252+from nova.api import auth as api_auth
253 from nova.api.openstack import auth
254 from nova.api.openstack import extensions
255 from nova.api.openstack import versions
256@@ -83,9 +84,9 @@
257 ctxt = fake_auth_context
258 else:
259 ctxt = context.RequestContext('fake', 'fake')
260- api10 = openstack.FaultWrapper(wsgi.InjectContext(ctxt,
261+ api10 = openstack.FaultWrapper(api_auth.InjectContext(ctxt,
262 limits.RateLimitingMiddleware(inner_app10)))
263- api11 = openstack.FaultWrapper(wsgi.InjectContext(ctxt,
264+ api11 = openstack.FaultWrapper(api_auth.InjectContext(ctxt,
265 limits.RateLimitingMiddleware(
266 extensions.ExtensionMiddleware(inner_app11))))
267 else:
268
269=== modified file 'nova/tests/test_api.py'
270--- nova/tests/test_api.py 2011-08-09 12:47:47 +0000
271+++ nova/tests/test_api.py 2011-08-18 22:43:01 +0000
272@@ -32,6 +32,7 @@
273 from nova import exception
274 from nova import test
275 from nova import wsgi
276+from nova.api import auth
277 from nova.api import ec2
278 from nova.api.ec2 import apirequest
279 from nova.api.ec2 import cloud
280@@ -199,7 +200,7 @@
281 # NOTE(vish): skipping the Authorizer
282 roles = ['sysadmin', 'netadmin']
283 ctxt = context.RequestContext('fake', 'fake', roles=roles)
284- self.app = wsgi.InjectContext(ctxt,
285+ self.app = auth.InjectContext(ctxt,
286 ec2.Requestify(ec2.Authorizer(ec2.Executor()),
287 'nova.api.ec2.cloud.CloudController'))
288
289
290=== modified file 'nova/wsgi.py'
291--- nova/wsgi.py 2011-07-22 19:47:41 +0000
292+++ nova/wsgi.py 2011-08-18 22:43:01 +0000
293@@ -274,18 +274,6 @@
294 return self.process_response(response)
295
296
297-class InjectContext(Middleware):
298- """Add a 'nova.context' to WSGI environ."""
299- def __init__(self, context, *args, **kwargs):
300- self.context = context
301- super(InjectContext, self).__init__(*args, **kwargs)
302-
303- @webob.dec.wsgify(RequestClass=Request)
304- def __call__(self, req):
305- req.environ['nova.context'] = self.context
306- return self.application
307-
308-
309 class Debug(Middleware):
310 """Helper class for debugging a WSGI application.
311