Merged from trunk and fixed review comments
This commit is contained in:
commit
94b3055ade
@ -20,7 +20,8 @@ use = egg:Paste#urlmap
|
|||||||
|
|
||||||
[pipeline:ec2cloud]
|
[pipeline:ec2cloud]
|
||||||
pipeline = logrequest authenticate cloudrequest authorizer ec2executor
|
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:ec2admin]
|
||||||
pipeline = logrequest authenticate adminrequest authorizer ec2executor
|
pipeline = logrequest authenticate adminrequest authorizer ec2executor
|
||||||
@ -37,6 +38,9 @@ paste.filter_factory = nova.api.ec2:RequestLogging.factory
|
|||||||
[filter:ec2lockout]
|
[filter:ec2lockout]
|
||||||
paste.filter_factory = nova.api.ec2:Lockout.factory
|
paste.filter_factory = nova.api.ec2:Lockout.factory
|
||||||
|
|
||||||
|
[filter:totoken]
|
||||||
|
paste.filter_factory = nova.api.ec2:ToToken.factory
|
||||||
|
|
||||||
[filter:authenticate]
|
[filter:authenticate]
|
||||||
paste.filter_factory = nova.api.ec2:Authenticate.factory
|
paste.filter_factory = nova.api.ec2:Authenticate.factory
|
||||||
|
|
||||||
@ -72,9 +76,13 @@ use = egg:Paste#urlmap
|
|||||||
|
|
||||||
[pipeline:openstackapi10]
|
[pipeline:openstackapi10]
|
||||||
pipeline = faultwrap auth ratelimit osapiapp10
|
pipeline = faultwrap auth ratelimit osapiapp10
|
||||||
|
# NOTE(vish): use the following pipeline for keystone
|
||||||
|
#pipeline = faultwrap authtoken keystonecontext ratelimit osapiapp10
|
||||||
|
|
||||||
[pipeline:openstackapi11]
|
[pipeline:openstackapi11]
|
||||||
pipeline = faultwrap auth ratelimit extensions osapiapp11
|
pipeline = faultwrap auth ratelimit extensions osapiapp11
|
||||||
|
# NOTE(vish): use the following pipeline for keystone
|
||||||
|
# pipeline = faultwrap authtoken keystonecontext ratelimit extensions osapiapp11
|
||||||
|
|
||||||
[filter:faultwrap]
|
[filter:faultwrap]
|
||||||
paste.filter_factory = nova.api.openstack:FaultWrapper.factory
|
paste.filter_factory = nova.api.openstack:FaultWrapper.factory
|
||||||
@ -99,3 +107,22 @@ pipeline = faultwrap osversionapp
|
|||||||
|
|
||||||
[app:osversionapp]
|
[app:osversionapp]
|
||||||
paste.app_factory = nova.api.openstack.versions:Versions.factory
|
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
75
nova/api/auth.py
Normal 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
|
@ -20,6 +20,7 @@ Starting point for routing EC2 requests.
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import httplib2
|
||||||
import webob
|
import webob
|
||||||
import webob.dec
|
import webob.dec
|
||||||
import webob.exc
|
import webob.exc
|
||||||
@ -37,15 +38,16 @@ from nova.auth import manager
|
|||||||
|
|
||||||
FLAGS = flags.FLAGS
|
FLAGS = flags.FLAGS
|
||||||
LOG = logging.getLogger("nova.api")
|
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,
|
flags.DEFINE_integer('lockout_attempts', 5,
|
||||||
'Number of failed auths before lockout.')
|
'Number of failed auths before lockout.')
|
||||||
flags.DEFINE_integer('lockout_minutes', 15,
|
flags.DEFINE_integer('lockout_minutes', 15,
|
||||||
'Number of minutes to lockout if triggered.')
|
'Number of minutes to lockout if triggered.')
|
||||||
flags.DEFINE_integer('lockout_window', 15,
|
flags.DEFINE_integer('lockout_window', 15,
|
||||||
'Number of minutes for lockout window.')
|
'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):
|
class RequestLogging(wsgi.Middleware):
|
||||||
@ -138,6 +140,49 @@ class Lockout(wsgi.Middleware):
|
|||||||
return res
|
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):
|
class Authenticate(wsgi.Middleware):
|
||||||
"""Authenticate an EC2 request and add 'nova.context' to WSGI environ."""
|
"""Authenticate an EC2 request and add 'nova.context' to WSGI environ."""
|
||||||
|
|
||||||
@ -147,7 +192,7 @@ class Authenticate(wsgi.Middleware):
|
|||||||
try:
|
try:
|
||||||
signature = req.params['Signature']
|
signature = req.params['Signature']
|
||||||
access = req.params['AWSAccessKeyId']
|
access = req.params['AWSAccessKeyId']
|
||||||
except KeyError, e:
|
except KeyError:
|
||||||
raise webob.exc.HTTPBadRequest()
|
raise webob.exc.HTTPBadRequest()
|
||||||
|
|
||||||
# Make a copy of args for authentication and signature verification.
|
# Make a copy of args for authentication and signature verification.
|
||||||
|
@ -30,6 +30,7 @@ from nova.api.ec2 import cloud
|
|||||||
|
|
||||||
LOG = logging.getLogger('nova.api.ec2.metadata')
|
LOG = logging.getLogger('nova.api.ec2.metadata')
|
||||||
FLAGS = flags.FLAGS
|
FLAGS = flags.FLAGS
|
||||||
|
flags.DECLARE('use_forwarded_for', 'nova.api.auth')
|
||||||
|
|
||||||
|
|
||||||
class MetadataRequestHandler(wsgi.Application):
|
class MetadataRequestHandler(wsgi.Application):
|
||||||
|
@ -45,10 +45,8 @@ class Createserverext(extensions.ExtensionDescriptor):
|
|||||||
resources = []
|
resources = []
|
||||||
|
|
||||||
headers_serializer = servers.HeadersSerializer()
|
headers_serializer = servers.HeadersSerializer()
|
||||||
metadata = servers._get_metadata()
|
|
||||||
body_serializers = {
|
body_serializers = {
|
||||||
'application/xml': wsgi.XMLDictSerializer(metadata=metadata,
|
'application/xml': servers.ServerXMLSerializer(),
|
||||||
xmlns=wsgi.XMLNS_V11),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body_deserializers = {
|
body_deserializers = {
|
||||||
|
@ -936,7 +936,21 @@ def create_resource(version='1.0'):
|
|||||||
'1.1': ControllerV11,
|
'1.1': ControllerV11,
|
||||||
}[version]()
|
}[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 = {
|
xmlns = {
|
||||||
'1.0': wsgi.XMLNS_V10,
|
'1.0': wsgi.XMLNS_V10,
|
||||||
@ -969,25 +983,6 @@ def create_resource(version='1.0'):
|
|||||||
return wsgi.Resource(controller, deserializer, serializer)
|
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):
|
def remove_invalid_options(context, search_options, allowed_search_options):
|
||||||
"""Remove search options that are not valid for non-admin API/context"""
|
"""Remove search options that are not valid for non-admin API/context"""
|
||||||
if FLAGS.allow_admin_api and context.is_admin:
|
if FLAGS.allow_admin_api and context.is_admin:
|
||||||
|
@ -422,12 +422,11 @@ class API(base.Base):
|
|||||||
LOG.debug(_("Casting to scheduler for %(pid)s/%(uid)s's"
|
LOG.debug(_("Casting to scheduler for %(pid)s/%(uid)s's"
|
||||||
" (all-at-once)") % locals())
|
" (all-at-once)") % locals())
|
||||||
|
|
||||||
filter_class = 'nova.scheduler.host_filter.InstanceTypeFilter'
|
|
||||||
request_spec = {
|
request_spec = {
|
||||||
'image': image,
|
'image': image,
|
||||||
'instance_properties': base_options,
|
'instance_properties': base_options,
|
||||||
'instance_type': instance_type,
|
'instance_type': instance_type,
|
||||||
'filter': filter_class,
|
'filter': None,
|
||||||
'blob': zone_blob,
|
'blob': zone_blob,
|
||||||
'num_instances': num_instances,
|
'num_instances': num_instances,
|
||||||
}
|
}
|
||||||
|
@ -62,12 +62,13 @@ class AbstractScheduler(driver.Scheduler):
|
|||||||
host = build_plan_item['hostname']
|
host = build_plan_item['hostname']
|
||||||
base_options = request_spec['instance_properties']
|
base_options = request_spec['instance_properties']
|
||||||
image = request_spec['image']
|
image = request_spec['image']
|
||||||
|
instance_type = request_spec.get('instance_type')
|
||||||
|
|
||||||
# TODO(sandy): I guess someone needs to add block_device_mapping
|
# TODO(sandy): I guess someone needs to add block_device_mapping
|
||||||
# support at some point? Also, OS API has no concept of security
|
# support at some point? Also, OS API has no concept of security
|
||||||
# groups.
|
# groups.
|
||||||
instance = compute_api.API().create_db_entry_for_new_instance(context,
|
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']
|
instance_id = instance['id']
|
||||||
kwargs['instance_id'] = instance_id
|
kwargs['instance_id'] = instance_id
|
||||||
|
@ -58,8 +58,6 @@ def choose_host_filter(filter_name=None):
|
|||||||
if not filter_name:
|
if not filter_name:
|
||||||
filter_name = FLAGS.default_host_filter
|
filter_name = FLAGS.default_host_filter
|
||||||
for filter_class in _get_filters():
|
for filter_class in _get_filters():
|
||||||
host_match = "%s.%s" % (filter_class.__module__, filter_class.__name__)
|
if filter_class.__name__ == filter_name:
|
||||||
if (host_match.startswith("nova.scheduler.filters") and
|
|
||||||
(host_match.split(".")[-1] == filter_name)):
|
|
||||||
return filter_class()
|
return filter_class()
|
||||||
raise exception.SchedulerHostFilterNotFound(filter_name=filter_name)
|
raise exception.SchedulerHostFilterNotFound(filter_name=filter_name)
|
||||||
|
@ -32,6 +32,7 @@ from nova import utils
|
|||||||
from nova import wsgi
|
from nova import wsgi
|
||||||
import nova.api.openstack.auth
|
import nova.api.openstack.auth
|
||||||
from nova.api import openstack
|
from nova.api import openstack
|
||||||
|
from nova.api import auth as api_auth
|
||||||
from nova.api.openstack import auth
|
from nova.api.openstack import auth
|
||||||
from nova.api.openstack import extensions
|
from nova.api.openstack import extensions
|
||||||
from nova.api.openstack import versions
|
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
|
ctxt = fake_auth_context
|
||||||
else:
|
else:
|
||||||
ctxt = context.RequestContext('fake', 'fake')
|
ctxt = context.RequestContext('fake', 'fake')
|
||||||
api10 = openstack.FaultWrapper(wsgi.InjectContext(ctxt,
|
api10 = openstack.FaultWrapper(api_auth.InjectContext(ctxt,
|
||||||
limits.RateLimitingMiddleware(inner_app10)))
|
limits.RateLimitingMiddleware(inner_app10)))
|
||||||
api11 = openstack.FaultWrapper(wsgi.InjectContext(ctxt,
|
api11 = openstack.FaultWrapper(api_auth.InjectContext(ctxt,
|
||||||
limits.RateLimitingMiddleware(
|
limits.RateLimitingMiddleware(
|
||||||
extensions.ExtensionMiddleware(inner_app11))))
|
extensions.ExtensionMiddleware(inner_app11))))
|
||||||
else:
|
else:
|
||||||
|
@ -32,6 +32,7 @@ from nova import context
|
|||||||
from nova import exception
|
from nova import exception
|
||||||
from nova import test
|
from nova import test
|
||||||
from nova import wsgi
|
from nova import wsgi
|
||||||
|
from nova.api import auth
|
||||||
from nova.api import ec2
|
from nova.api import ec2
|
||||||
from nova.api.ec2 import apirequest
|
from nova.api.ec2 import apirequest
|
||||||
from nova.api.ec2 import cloud
|
from nova.api.ec2 import cloud
|
||||||
@ -199,7 +200,7 @@ class ApiEc2TestCase(test.TestCase):
|
|||||||
# NOTE(vish): skipping the Authorizer
|
# NOTE(vish): skipping the Authorizer
|
||||||
roles = ['sysadmin', 'netadmin']
|
roles = ['sysadmin', 'netadmin']
|
||||||
ctxt = context.RequestContext('fake', 'fake', roles=roles)
|
ctxt = context.RequestContext('fake', 'fake', roles=roles)
|
||||||
self.app = wsgi.InjectContext(ctxt,
|
self.app = auth.InjectContext(ctxt,
|
||||||
ec2.Requestify(ec2.Authorizer(ec2.Executor()),
|
ec2.Requestify(ec2.Authorizer(ec2.Executor()),
|
||||||
'nova.api.ec2.cloud.CloudController'))
|
'nova.api.ec2.cloud.CloudController'))
|
||||||
|
|
||||||
|
12
nova/wsgi.py
12
nova/wsgi.py
@ -271,18 +271,6 @@ class Middleware(Application):
|
|||||||
return self.process_response(response)
|
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):
|
class Debug(Middleware):
|
||||||
"""Helper class for debugging a WSGI application.
|
"""Helper class for debugging a WSGI application.
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user