diff options
| author | Vishvananda Ishaya <vishvananda@gmail.com> | 2012-01-11 10:42:00 -0800 |
|---|---|---|
| committer | Vishvananda Ishaya <vishvananda@gmail.com> | 2012-01-23 16:40:15 -0800 |
| commit | a47b5276244dba9c0a9058e6868522cf3fb3cada (patch) | |
| tree | ec56c0328d88407a66e1665390cb22060ec75b1d | |
| parent | aaa7f7ed456cb9103b07ae629492f3a343ace9d8 (diff) | |
Brings back keystone middleware
* Adds ec2 auth middleware
* Adds keystonecontext middleware
* Notes alternative configurations in paste config
* Fixes nova portion of bug 917408
Change-Id: I9909d6d235445b1413d1cc0fb61e99e1ab819438
| -rw-r--r-- | etc/nova/api-paste.ini | 36 | ||||
| -rw-r--r-- | nova/api/auth.py | 41 | ||||
| -rw-r--r-- | nova/api/ec2/__init__.py | 62 |
3 files changed, 137 insertions, 2 deletions
diff --git a/etc/nova/api-paste.ini b/etc/nova/api-paste.ini index e03697f8e..2dc37439b 100644 --- a/etc/nova/api-paste.ini +++ b/etc/nova/api-paste.ini @@ -38,12 +38,16 @@ use = egg:Paste#urlmap [pipeline:ec2cloud] pipeline = ec2faultwrap logrequest ec2noauth cloudrequest authorizer validator ec2executor # NOTE(vish): use the following pipeline for deprecated auth -#pipeline = ec2faultwrap logrequest authenticate cloudrequest authorizer validator ec2executor +# pipeline = ec2faultwrap logrequest authenticate cloudrequest authorizer validator ec2executor +# NOTE(vish): use the following pipeline for keystone auth +# pipeline = ec2faultwrap logrequest totoken authtoken keystonecontext cloudrequest authorizer validator ec2executor [pipeline:ec2admin] pipeline = ec2faultwrap logrequest ec2noauth adminrequest authorizer ec2executor # NOTE(vish): use the following pipeline for deprecated auth -#pipeline = ec2faultwrap logrequest authenticate adminrequest authorizer ec2executor +# pipeline = ec2faultwrap logrequest authenticate adminrequest authorizer ec2executor +# NOTE(vish): use the following pipeline for keystone auth +# pipeline = ec2faultwrap logrequest totoken authtoken keystonecontext adminrequest authorizer ec2executor [filter:ec2faultwrap] paste.filter_factory = nova.api.ec2:FaultWrapper.factory @@ -54,6 +58,9 @@ paste.filter_factory = nova.api.ec2:RequestLogging.factory [filter:ec2lockout] paste.filter_factory = nova.api.ec2:Lockout.factory +[filter:totoken] +paste.filter_factory = nova.api.ec2:EC2Token.factory + [filter:ec2noauth] paste.filter_factory = nova.api.ec2:NoAuth.factory @@ -96,9 +103,15 @@ use = call:nova.api.openstack.urlmap:urlmap_factory pipeline = faultwrap noauth ratelimit osapi_compute_app_v2 # NOTE(vish): use the following pipeline for deprecated auth # pipeline = faultwrap auth ratelimit osapi_compute_app_v2 +# NOTE(vish): use the following pipeline for keystone auth +# pipeline = faultwrap authtoken keystonecontext ratelimit osapi_compute_app_v2 [pipeline:openstack_volume_api_v1] pipeline = faultwrap noauth ratelimit osapi_volume_app_v1 +# NOTE(vish): use the following pipeline for deprecated auth +# pipeline = faultwrap auth ratelimit osapi_volume_app_v1 +# NOTE(vish): use the following pipeline for keystone auth +# pipeline = faultwrap authtoken keystonecontext ratelimit osapi_volume_app_v1 [filter:faultwrap] paste.filter_factory = nova.api.openstack:FaultWrapper.factory @@ -129,3 +142,22 @@ pipeline = faultwrap osvolumeversionapp [app:osvolumeversionapp] paste.app_factory = nova.api.openstack.volume.versions:Versions.factory + +########## +# Shared # +########## + +[filter:keystonecontext] +paste.filter_factory = nova.api.auth.nova_keystone_context:NovaKeystoneContext.factory + +[filter:authtoken] +paste.filter_factory = keystone.middleware.auth_token:filter_factory +service_protocol = http +service_host = 127.0.0.1 +service_port = 5000 +auth_host = 127.0.0.1 +auth_port = 35357 +auth_protocol = http +auth_uri = http://127.0.0.1:5000/ +# NOTE(vish): you will have to replace the value below with an actual admin token +admin_token = %SERVICE_TOKEN% diff --git a/nova/api/auth.py b/nova/api/auth.py index 6be22620c..54fff94df 100644 --- a/nova/api/auth.py +++ b/nova/api/auth.py @@ -21,7 +21,9 @@ Common Auth Middleware. import webob.dec import webob.exc +from nova import context from nova import flags +from nova import log as logging from nova import wsgi @@ -42,3 +44,42 @@ class InjectContext(wsgi.Middleware): def __call__(self, req): req.environ['nova.context'] = self.context return self.application + + +class NovaKeystoneContext(wsgi.Middleware): + """Make a request context from keystone headers""" + + @webob.dec.wsgify(RequestClass=wsgi.Request) + def __call__(self, req): + try: + user_id = req.headers['X_USER'] + except KeyError: + logging.debug("X_USER not found in request") + return webob.exc.HTTPUnauthorized() + # get the roles + roles = [r.strip() for r in req.headers.get('X_ROLE', '').split(',')] + if 'X_TENANT_ID' in req.headers: + # This is the new header since Keystone went to ID/Name + project_id = req.headers['X_TENANT_ID'] + else: + # This is for legacy compatibility + project_id = req.headers['X_TENANT'] + + # Get the auth token + auth_token = req.headers.get('X_AUTH_TOKEN', + req.headers.get('X_STORAGE_TOKEN')) + + # Build a context, including the auth_token... + remote_address = getattr(req, 'remote_address', '127.0.0.1') + remote_address = req.remote_addr + if FLAGS.use_forwarded_for: + remote_address = req.headers.get('X-Forwarded-For', remote_address) + ctx = context.RequestContext(user_id, + project_id, + roles=roles, + auth_token=auth_token, + strategy='keystone', + remote_address=remote_address) + + req.environ['nova.context'] = ctx + return self.application diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index 0dd97f8de..b052f7010 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -20,7 +20,9 @@ Starting point for routing EC2 requests. """ +import urlparse +from eventlet.green import httplib import webob import webob.dec import webob.exc @@ -45,6 +47,9 @@ flags.DEFINE_integer('lockout_minutes', 15, 'Number of minutes to lockout if triggered.') flags.DEFINE_integer('lockout_window', 15, 'Number of minutes for lockout window.') +flags.DEFINE_string('keystone_ec2_url', + 'http://localhost:5000/v2.0/ec2tokens', + 'URL to get token from ec2 request.') flags.DECLARE('use_forwarded_for', 'nova.api.auth') @@ -151,6 +156,63 @@ class Lockout(wsgi.Middleware): return res +class EC2Token(wsgi.Middleware): + """Authenticate an EC2 request with keystone and convert to token.""" + + @webob.dec.wsgify(RequestClass=wsgi.Request) + def __call__(self, req): + # Read request signature and access id. + try: + signature = req.params['Signature'] + access = req.params['AWSAccessKeyId'] + except KeyError, e: + LOG.exception(e) + raise webob.exc.HTTPBadRequest() + + # Make a copy of args for authentication and signature verification. + auth_params = dict(req.params) + # Not part of authentication args + auth_params.pop('Signature') + + # Authenticate the request. + creds = {'ec2Credentials': {'access': access, + 'signature': signature, + 'host': req.host, + 'verb': req.method, + 'path': req.path, + 'params': auth_params, + }} + creds_json = utils.dumps(creds) + headers = {'Content-Type': 'application/json'} + + # Disable "has no x member" pylint error + # for httplib and urlparse + # pylint: disable-msg=E1101 + o = urlparse.urlparse(FLAGS.keystone_ec2_url) + if o.scheme == "http": + conn = httplib.HTTPConnection(o.netloc) + else: + conn = httplib.HTTPSConnection(o.netloc) + conn.request('POST', o.path, body=creds_json, headers=headers) + response = conn.getresponse().read() + conn.close() + + # NOTE(vish): We could save a call to keystone by + # having keystone return token, tenant, + # user, and roles from this call. + + result = utils.loads(response) + try: + token_id = result['access']['token']['id'] + except (AttributeError, KeyError), e: + LOG.exception(e) + raise webob.exc.HTTPBadRequest() + + # Authenticated! + req.headers['X-Auth-Token'] = token_id + return self.application + + class NoAuth(wsgi.Middleware): """Add user:project as 'nova.context' to WSGI environ.""" |
