Merged from trunk and fixed review comments

This commit is contained in:
Tushar Patil 2011-08-19 15:04:25 -07:00
commit 94b3055ade
12 changed files with 178 additions and 49 deletions

View File

@ -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

75
nova/api/auth.py Normal file
View File

@ -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

View File

@ -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.

View File

@ -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):

View File

@ -45,10 +45,8 @@ class Createserverext(extensions.ExtensionDescriptor):
resources = []
headers_serializer = servers.HeadersSerializer()
metadata = servers._get_metadata()
body_serializers = {
'application/xml': wsgi.XMLDictSerializer(metadata=metadata,
xmlns=wsgi.XMLNS_V11),
'application/xml': servers.ServerXMLSerializer(),
}
body_deserializers = {

View File

@ -936,7 +936,21 @@ def create_resource(version='1.0'):
'1.1': ControllerV11,
}[version]()
metadata = _get_metadata()
metadata = {
"attributes": {
"server": ["id", "imageId", "name", "flavorId", "hostId",
"status", "progress", "adminPass", "flavorRef",
"imageRef"],
"link": ["rel", "type", "href"],
},
"dict_collections": {
"metadata": {"item_name": "meta", "item_key": "key"},
},
"list_collections": {
"public": {"item_name": "ip", "item_key": "addr"},
"private": {"item_name": "ip", "item_key": "addr"},
},
}
xmlns = {
'1.0': wsgi.XMLNS_V10,
@ -969,25 +983,6 @@ def create_resource(version='1.0'):
return wsgi.Resource(controller, deserializer, serializer)
def _get_metadata():
metadata = {
"attributes": {
"server": ["id", "imageId", "name", "flavorId", "hostId",
"status", "progress", "adminPass", "flavorRef",
"imageRef"],
"link": ["rel", "type", "href"],
},
"dict_collections": {
"metadata": {"item_name": "meta", "item_key": "key"},
},
"list_collections": {
"public": {"item_name": "ip", "item_key": "addr"},
"private": {"item_name": "ip", "item_key": "addr"},
},
}
return metadata
def remove_invalid_options(context, search_options, allowed_search_options):
"""Remove search options that are not valid for non-admin API/context"""
if FLAGS.allow_admin_api and context.is_admin:

View File

@ -422,12 +422,11 @@ class API(base.Base):
LOG.debug(_("Casting to scheduler for %(pid)s/%(uid)s's"
" (all-at-once)") % locals())
filter_class = 'nova.scheduler.host_filter.InstanceTypeFilter'
request_spec = {
'image': image,
'instance_properties': base_options,
'instance_type': instance_type,
'filter': filter_class,
'filter': None,
'blob': zone_blob,
'num_instances': num_instances,
}

View File

@ -62,12 +62,13 @@ class AbstractScheduler(driver.Scheduler):
host = build_plan_item['hostname']
base_options = request_spec['instance_properties']
image = request_spec['image']
instance_type = request_spec.get('instance_type')
# TODO(sandy): I guess someone needs to add block_device_mapping
# support at some point? Also, OS API has no concept of security
# groups.
instance = compute_api.API().create_db_entry_for_new_instance(context,
image, base_options, None, [])
instance_type, image, base_options, None, [])
instance_id = instance['id']
kwargs['instance_id'] = instance_id

View File

@ -58,8 +58,6 @@ def choose_host_filter(filter_name=None):
if not filter_name:
filter_name = FLAGS.default_host_filter
for filter_class in _get_filters():
host_match = "%s.%s" % (filter_class.__module__, filter_class.__name__)
if (host_match.startswith("nova.scheduler.filters") and
(host_match.split(".")[-1] == filter_name)):
if filter_class.__name__ == filter_name:
return filter_class()
raise exception.SchedulerHostFilterNotFound(filter_name=filter_name)

View File

@ -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,9 +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:

View File

@ -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'))

View File

@ -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.