]> review.fuel-infra Code Review - openstack-build/heat-build.git/commitdiff
Add auth middleware for custom cloud backend
authorTomas Sedovic <tomas@sedovic.cz>
Wed, 2 Jan 2013 16:34:23 +0000 (17:34 +0100)
committerTomas Sedovic <tomas@sedovic.cz>
Mon, 14 Jan 2013 09:21:59 +0000 (10:21 +0100)
Custom backends will want to provide their own authentication mechanisms
instead of using the Keystone token or EC2-like systems we have in
place.

This adds a new middleware and paste pipeline for the custom backend
that will skip the normal authentication and queries the backend's
`authenticated(context)` method instead.

Since the backend is connected to the Engine whereas the auth middleware
is run in the API service (which may sit on a separate box and have no
access to the engine config or the custom backend itself), we add a new
RPC call that lets API verify the passed credentials.

Change-Id: I2fc4a19564b1e410adb79bd9266f6b6da07dd6c9
Signed-off-by: Tomas Sedovic <tomas@sedovic.cz>
etc/heat/heat-api-paste.ini
etc/heat/heat-api.conf
heat/common/custom_backend_auth.py [new file with mode: 0644]
heat/engine/service.py
heat/rpc/client.py

index b562de278a9762d2cb4b40b863550459b07072d7..218afe569d72372755467eb1b5e1306d742603f5 100644 (file)
@@ -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
index d708293fc2edb7e6234992f0bb2162c1c5f69c5e..1a48a49e4c65bebeaf5bb88d7289b836b4f7ff8a 100644 (file)
@@ -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 (file)
index 0000000..beb0b21
--- /dev/null
@@ -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
index 8823af83e1862e89a20a9955b165c397c4d3e9ba..2d80c602d0cc83826b713cd37bfa30f2d63500fa 100644 (file)
@@ -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):
         """
index ca2c4f4845e4299581fcfd9e5aac66c10d5e2ca2..d62580318221b63118746c2de2320b91771a2a88 100644 (file)
@@ -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.