summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorWilliam Wolf <throughnothing@gmail.com>2011-08-19 16:03:24 -0400
committerWilliam Wolf <throughnothing@gmail.com>2011-08-19 16:03:24 -0400
commit4d9cd63c2f269f795e476869557f6bd3d7dcc777 (patch)
tree2db1bc5edd1f162708ebe9efad40eba7d956a294
parent70cfff2a9ee4e34705a342157c8711552f89d764 (diff)
parent2837a9f8e81fd67b01971a4d8c308f79ab7e7d68 (diff)
merge from trunk
-rw-r--r--etc/nova/api-paste.ini29
-rw-r--r--nova/api/auth.py75
-rw-r--r--nova/api/ec2/__init__.py53
-rw-r--r--nova/api/ec2/metadatarequesthandler.py1
-rw-r--r--nova/tests/api/openstack/fakes.py6
-rw-r--r--nova/tests/test_api.py3
-rw-r--r--nova/wsgi.py12
7 files changed, 158 insertions, 21 deletions
diff --git a/etc/nova/api-paste.ini b/etc/nova/api-paste.ini
index abe8c20c4..b540509a2 100644
--- a/etc/nova/api-paste.ini
+++ b/etc/nova/api-paste.ini
@@ -20,7 +20,8 @@ use = egg:Paste#urlmap
[pipeline:ec2cloud]
pipeline = logrequest authenticate cloudrequest authorizer ec2executor
-#pipeline = logrequest ec2lockout authenticate cloudrequest authorizer ec2executor
+# NOTE(vish): use the following pipeline for keystone
+# pipeline = logrequest totoken authtoken keystonecontext cloudrequest authorizer ec2executor
[pipeline:ec2admin]
pipeline = logrequest authenticate adminrequest authorizer ec2executor
@@ -37,6 +38,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:ToToken.factory
+
[filter:authenticate]
paste.filter_factory = nova.api.ec2:Authenticate.factory
@@ -72,9 +76,13 @@ use = egg:Paste#urlmap
[pipeline:openstackapi10]
pipeline = faultwrap auth ratelimit osapiapp10
+# NOTE(vish): use the following pipeline for keystone
+#pipeline = faultwrap authtoken keystonecontext ratelimit osapiapp10
[pipeline:openstackapi11]
pipeline = faultwrap auth ratelimit extensions osapiapp11
+# NOTE(vish): use the following pipeline for keystone
+# pipeline = faultwrap authtoken keystonecontext ratelimit extensions osapiapp11
[filter:faultwrap]
paste.filter_factory = nova.api.openstack:FaultWrapper.factory
@@ -99,3 +107,22 @@ pipeline = faultwrap osversionapp
[app:osversionapp]
paste.app_factory = nova.api.openstack.versions:Versions.factory
+
+##########
+# Shared #
+##########
+
+[filter:keystonecontext]
+paste.filter_factory = nova.api.auth:KeystoneContext.factory
+
+[filter:authtoken]
+paste.filter_factory = keystone.middleware.auth_token:filter_factory
+service_protocol = http
+service_host = 127.0.0.1
+service_port = 808
+auth_host = 127.0.0.1
+auth_port = 5001
+auth_protocol = http
+auth_uri = http://127.0.0.1:5000/
+admin_token = 999888777666
+
diff --git a/nova/api/auth.py b/nova/api/auth.py
new file mode 100644
index 000000000..cd3e3e8a0
--- /dev/null
+++ b/nova/api/auth.py
@@ -0,0 +1,75 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright (c) 2011 OpenStack, LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+"""
+Common Auth Middleware.
+
+"""
+
+import webob.dec
+import webob.exc
+
+from nova import context
+from nova import flags
+from nova import wsgi
+
+
+FLAGS = flags.FLAGS
+flags.DEFINE_boolean('use_forwarded_for', False,
+ 'Treat X-Forwarded-For as the canonical remote address. '
+ 'Only enable this if you have a sanitizing proxy.')
+
+
+class InjectContext(wsgi.Middleware):
+ """Add a 'nova.context' to WSGI environ."""
+
+ def __init__(self, context, *args, **kwargs):
+ self.context = context
+ super(InjectContext, self).__init__(*args, **kwargs)
+
+ @webob.dec.wsgify(RequestClass=wsgi.Request)
+ def __call__(self, req):
+ req.environ['nova.context'] = self.context
+ return self.application
+
+
+class KeystoneContext(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:
+ return webob.exc.HTTPUnauthorized()
+ # get the roles
+ roles = [r.strip() for r in req.headers.get('X_ROLE', '').split(',')]
+ 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 = 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,
+ 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 96df97393..17969099d 100644
--- a/nova/api/ec2/__init__.py
+++ b/nova/api/ec2/__init__.py
@@ -20,6 +20,7 @@ Starting point for routing EC2 requests.
"""
+import httplib2
import webob
import webob.dec
import webob.exc
@@ -37,15 +38,16 @@ from nova.auth import manager
FLAGS = flags.FLAGS
LOG = logging.getLogger("nova.api")
-flags.DEFINE_boolean('use_forwarded_for', False,
- 'Treat X-Forwarded-For as the canonical remote address. '
- 'Only enable this if you have a sanitizing proxy.')
flags.DEFINE_integer('lockout_attempts', 5,
'Number of failed auths before lockout.')
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')
class RequestLogging(wsgi.Middleware):
@@ -138,6 +140,49 @@ class Lockout(wsgi.Middleware):
return res
+class ToToken(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:
+ 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.
+ client = httplib2.Http()
+ creds = {'ec2Credentials': {'access': access,
+ 'signature': signature,
+ 'host': req.host,
+ 'verb': req.method,
+ 'path': req.path,
+ 'params': auth_params,
+ }}
+ headers = {'Content-Type': 'application/json'},
+ resp, content = client.request(FLAGS.keystone_ec2_url,
+ 'POST',
+ headers=headers,
+ body=utils.dumps(creds))
+ # NOTE(vish): We could save a call to keystone by
+ # having keystone return token, tenant,
+ # user, and roles from this call.
+ result = utils.loads(content)
+ # TODO(vish): check for errors
+ token_id = result['auth']['token']['id']
+
+ # Authenticated!
+ req.headers['X-Auth-Token'] = token_id
+ return self.application
+
+
class Authenticate(wsgi.Middleware):
"""Authenticate an EC2 request and add 'nova.context' to WSGI environ."""
@@ -147,7 +192,7 @@ class Authenticate(wsgi.Middleware):
try:
signature = req.params['Signature']
access = req.params['AWSAccessKeyId']
- except KeyError, e:
+ except KeyError:
raise webob.exc.HTTPBadRequest()
# Make a copy of args for authentication and signature verification.
diff --git a/nova/api/ec2/metadatarequesthandler.py b/nova/api/ec2/metadatarequesthandler.py
index 1dc275c90..0198bf490 100644
--- a/nova/api/ec2/metadatarequesthandler.py
+++ b/nova/api/ec2/metadatarequesthandler.py
@@ -30,6 +30,7 @@ from nova.api.ec2 import cloud
LOG = logging.getLogger('nova.api.ec2.metadata')
FLAGS = flags.FLAGS
+flags.DECLARE('use_forwarded_for', 'nova.api.auth')
class MetadataRequestHandler(wsgi.Application):
diff --git a/nova/tests/api/openstack/fakes.py b/nova/tests/api/openstack/fakes.py
index 0611ad962..a095dd90a 100644
--- a/nova/tests/api/openstack/fakes.py
+++ b/nova/tests/api/openstack/fakes.py
@@ -32,6 +32,7 @@ from nova import utils
from nova import wsgi
import nova.api.openstack.auth
from nova.api import openstack
+from nova.api import auth as api_auth
from nova.api.openstack import auth
from nova.api.openstack import extensions
from nova.api.openstack import versions
@@ -83,10 +84,9 @@ def wsgi_app(inner_app10=None, inner_app11=None, fake_auth=True,
ctxt = fake_auth_context
else:
ctxt = context.RequestContext('fake', 'fake')
-
- api10 = openstack.FaultWrapper(wsgi.InjectContext(ctxt,
+ api10 = openstack.FaultWrapper(api_auth.InjectContext(ctxt,
limits.RateLimitingMiddleware(inner_app10)))
- api11 = openstack.FaultWrapper(wsgi.InjectContext(ctxt,
+ api11 = openstack.FaultWrapper(api_auth.InjectContext(ctxt,
limits.RateLimitingMiddleware(
extensions.ExtensionMiddleware(inner_app11))))
else:
diff --git a/nova/tests/test_api.py b/nova/tests/test_api.py
index 2011ae756..526d1c490 100644
--- a/nova/tests/test_api.py
+++ b/nova/tests/test_api.py
@@ -32,6 +32,7 @@ from nova import context
from nova import exception
from nova import test
from nova import wsgi
+from nova.api import auth
from nova.api import ec2
from nova.api.ec2 import apirequest
from nova.api.ec2 import cloud
@@ -199,7 +200,7 @@ class ApiEc2TestCase(test.TestCase):
# NOTE(vish): skipping the Authorizer
roles = ['sysadmin', 'netadmin']
ctxt = context.RequestContext('fake', 'fake', roles=roles)
- self.app = wsgi.InjectContext(ctxt,
+ self.app = auth.InjectContext(ctxt,
ec2.Requestify(ec2.Authorizer(ec2.Executor()),
'nova.api.ec2.cloud.CloudController'))
diff --git a/nova/wsgi.py b/nova/wsgi.py
index f2846aa73..09b45be5a 100644
--- a/nova/wsgi.py
+++ b/nova/wsgi.py
@@ -271,18 +271,6 @@ class Middleware(Application):
return self.process_response(response)
-class InjectContext(Middleware):
- """Add a 'nova.context' to WSGI environ."""
- def __init__(self, context, *args, **kwargs):
- self.context = context
- super(InjectContext, self).__init__(*args, **kwargs)
-
- @webob.dec.wsgify(RequestClass=Request)
- def __call__(self, req):
- req.environ['nova.context'] = self.context
- return self.application
-
-
class Debug(Middleware):
"""Helper class for debugging a WSGI application.