diff --git a/etc/heat/heat-api-paste.ini b/etc/heat/heat-api-paste.ini index b562de278a..218afe569d 100644 --- a/etc/heat/heat-api-paste.ini +++ b/etc/heat/heat-api-paste.ini @@ -11,6 +11,14 @@ pipeline = versionnegotiation authtoken context apiv1app [pipeline:heat-api-keystone] pipeline = versionnegotiation authtoken context apiv1app +# Use the following pipeline for custom cloud backends +# i.e. in heat-api.conf: +# [paste_deploy] +# flavor = custombackend +# +[pipeline:heat-api-custombackend] +pipeline = versionnegotiation context custombackendauth apiv1app + # Use the following pipeline to enable transparent caching of image files # i.e. in heat-api.conf: # [paste_deploy] @@ -81,3 +89,6 @@ admin_password = verybadpass [filter:auth-context] paste.filter_factory = heat.common.wsgi:filter_factory heat.filter_factory = keystone.middleware.heat_auth_token:KeystoneContextMiddleware + +[filter:custombackendauth] +paste.filter_factory = heat.common.custom_backend_auth:filter_factory \ No newline at end of file diff --git a/etc/heat/heat-api.conf b/etc/heat/heat-api.conf index d708293fc2..1a48a49e4c 100644 --- a/etc/heat/heat-api.conf +++ b/etc/heat/heat-api.conf @@ -25,3 +25,9 @@ use_syslog = False # syslog_log_facility = LOG_LOCAL0 rpc_backend=heat.openstack.common.rpc.impl_qpid + + + +# Uncomment this if you're using a custom cloud backend: +# [paste_deploy] +# flavor = custombackend \ No newline at end of file diff --git a/heat/common/custom_backend_auth.py b/heat/common/custom_backend_auth.py new file mode 100644 index 0000000000..beb0b217c6 --- /dev/null +++ b/heat/common/custom_backend_auth.py @@ -0,0 +1,71 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (C) 2012, Red Hat, Inc. +# +# 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. + +""" +Middleware for authenticating against custom backends. +""" + +import logging + +from heat.openstack.common import local +from heat.rpc import client as rpc_client +import webob.exc + + +LOG = logging.getLogger(__name__) + + +class AuthProtocol(object): + def __init__(self, app, conf): + self.conf = conf + self.app = app + + def __call__(self, env, start_response): + """ + Handle incoming request. + + Authenticate send downstream on success. Reject request if + we can't authenticate. + """ + LOG.debug('Authenticating user token') + context = local.store.context + engine = rpc_client.EngineClient() + authenticated = engine.authenticated_to_backend(context) + if authenticated: + return self.app(env, start_response) + else: + return self._reject_request(env, start_response) + + def _reject_request(self, env, start_response): + """ + Redirect client to auth server. + + :param env: wsgi request environment + :param start_response: wsgi response callback + :returns HTTPUnauthorized http response + """ + resp = webob.exc.HTTPUnauthorized("Backend authentication failed", []) + return resp(env, start_response) + + +def filter_factory(global_conf, **local_conf): + conf = global_conf.copy() + conf.update(local_conf) + + def auth_filter(app): + return AuthProtocol(app, conf) + return auth_filter diff --git a/heat/engine/service.py b/heat/engine/service.py index 8823af83e1..2d80c602d0 100644 --- a/heat/engine/service.py +++ b/heat/engine/service.py @@ -19,6 +19,7 @@ import webob from heat.common import context from heat.db import api as db_api from heat.engine import api +from heat.engine import clients from heat.engine.event import Event from heat.common import exception from heat.common import identifier @@ -304,6 +305,14 @@ class EngineService(service.Service): } return result + @request_context + def authenticated_to_backend(self, context): + """ + Verify that the credentials in the RPC context are valid for the + current cloud backend. + """ + return clients.Clients(context).authenticated() + @request_context def get_template(self, context, stack_identity): """ diff --git a/heat/rpc/client.py b/heat/rpc/client.py index ca2c4f4845..d625803182 100644 --- a/heat/rpc/client.py +++ b/heat/rpc/client.py @@ -139,6 +139,16 @@ class EngineClient(heat.openstack.common.rpc.proxy.RpcProxy): template=template), topic=_engine_topic(self.topic, ctxt, None)) + def authenticated_to_backend(self, ctxt): + """ + Verify that the credentials in the RPC context are valid for the + current cloud backend. + + :param ctxt: RPC context. + """ + return self.call(ctxt, self.make_msg('authenticated_to_backend'), + topic=_engine_topic(self.topic, ctxt, None)) + def get_template(self, ctxt, stack_identity): """ Get the template.