#password=<None>
+[auth_password]
+
+#
+# Options defined in heat.common.config
+#
+
+# Allow orchestration of multiple clouds (boolean value)
+#multi_cloud=false
+
+# Allowed targets for auth_uri when multi_cloud is enabled.
+# If empty, all targets will be allowed. (list value)
+#allowed_auth_uris=
+
+
[matchmaker_ring]
#
#ringfile=/etc/oslo/matchmaker_ring.json
-# Total option count: 107
+# Total option count: 109
from keystoneclient.v2_0 import client as keystone_client
from keystoneclient import exceptions as keystone_exceptions
from oslo.config import cfg
+from webob.exc import HTTPBadRequest
from webob.exc import HTTPUnauthorized
from heat.openstack.common import importutils
def __init__(self, app, conf):
self.app = app
self.conf = conf
- if 'auth_uri' in self.conf:
- auth_url = self.conf['auth_uri']
- else:
- # Import auth_token to have keystone_authtoken settings setup.
- importutils.import_module('keystoneclient.middleware.auth_token')
- auth_url = cfg.CONF.keystone_authtoken['auth_uri']
+ auth_url = None
+ if not cfg.CONF.auth_password.multi_cloud:
+ if 'auth_uri' in self.conf:
+ auth_url = self.conf['auth_uri']
+ else:
+ # Import auth_token to have keystone_authtoken settings setup.
+ importutils.import_module(
+ 'keystoneclient.middleware.auth_token')
+ auth_url = cfg.CONF.keystone_authtoken['auth_uri']
self.auth_url = auth_url
def __call__(self, env, start_response):
password = env.get('HTTP_X_AUTH_KEY')
# Determine tenant id from path.
tenant = env.get('PATH_INFO').split('/')[1]
+ auth_url = self.auth_url
+ if cfg.CONF.auth_password.multi_cloud:
+ auth_url = env.get('HTTP_X_AUTH_URL')
+ error = self._validate_auth_url(env, start_response, auth_url)
+ if error:
+ return error
if not tenant:
- return self._reject_request(env, start_response)
+ return self._reject_request(env, start_response, auth_url)
try:
client = keystone_client.Client(
username=username, password=password, tenant_id=tenant,
- auth_url=self.auth_url)
+ auth_url=auth_url)
except (keystone_exceptions.Unauthorized,
keystone_exceptions.Forbidden,
keystone_exceptions.NotFound,
keystone_exceptions.AuthorizationFailure):
- return self._reject_request(env, start_response)
+ return self._reject_request(env, start_response, auth_url)
env['keystone.token_info'] = client.auth_ref
- env.update(self._build_user_headers(client.auth_ref))
+ env.update(self._build_user_headers(client.auth_ref, auth_url))
return self.app(env, start_response)
- def _reject_request(self, env, start_response):
+ def _reject_request(self, env, start_response, auth_url):
"""Redirect client to auth server."""
- headers = [('WWW-Authenticate', 'Keystone uri=\'%s\'' % self.auth_url)]
+ headers = [('WWW-Authenticate', 'Keystone uri=\'%s\'' % auth_url)]
resp = HTTPUnauthorized('Authentication required', headers)
return resp(env, start_response)
- def _build_user_headers(self, token_info):
+ def _build_user_headers(self, token_info, auth_url):
"""Build headers that represent authenticated user from auth token."""
tenant_id = token_info['token']['tenant']['id']
tenant_name = token_info['token']['tenant']['name']
'HTTP_X_ROLES': roles,
'HTTP_X_SERVICE_CATALOG': service_catalog,
'HTTP_X_AUTH_TOKEN': auth_token,
- 'HTTP_X_AUTH_URL': self.auth_url,
+ 'HTTP_X_AUTH_URL': auth_url,
# DEPRECATED
'HTTP_X_USER': user_name,
'HTTP_X_TENANT_ID': tenant_id,
return headers
+ def _validate_auth_url(self, env, start_response, auth_url):
+ """Validate auth_url to ensure it can be used."""
+ if not auth_url:
+ resp = HTTPBadRequest(_('Request missing required header '
+ 'X-Auth-Url'))
+ return resp(env, start_response)
+ allowed = cfg.CONF.auth_password.allowed_auth_uris
+ if allowed and not auth_url in allowed:
+ resp = HTTPUnauthorized(_('Header X-Auth-Url "%s" not allowed')
+ % auth_url)
+ return resp(env, start_response)
+ return None
+
def filter_factory(global_conf, **local_conf):
"""Returns a WSGI filter app for use with paste.deploy."""
'This can be an opaque identifier.'
'It is not necessarily a hostname, FQDN, or IP address.')]
+auth_password_group = cfg.OptGroup('auth_password')
+auth_password_opts = [
+ cfg.BoolOpt('multi_cloud',
+ default=False,
+ help=_('Allow orchestration of multiple clouds')),
+ cfg.ListOpt('allowed_auth_uris',
+ default=[],
+ help=_('Allowed targets for auth_uri when multi_cloud is '
+ 'enabled. If empty, all targets will be allowed.'))]
+
cfg.CONF.register_opts(db_opts)
cfg.CONF.register_opts(engine_opts)
cfg.CONF.register_opts(service_opts)
cfg.CONF.register_opts(rpc_opts)
cfg.CONF.register_group(paste_deploy_group)
cfg.CONF.register_opts(paste_deploy_opts, group=paste_deploy_group)
+cfg.CONF.register_group(auth_password_group)
+cfg.CONF.register_opts(auth_password_opts, group=auth_password_group)
def rpc_set_default():
from keystoneclient.v2_0 import client as keystone_client
from keystoneclient.exceptions import Unauthorized
+from oslo.config import cfg
import webob
from heat.common.auth_password import KeystonePasswordAuthProtocol
expected_env={'HTTP_X_AUTH_URL': self.config['auth_uri']})
self.middleware = KeystonePasswordAuthProtocol(self.app, self.config)
+ def tearDown(self):
+ super(KeystonePasswordAuthProtocolTest, self).tearDown()
+ cfg.CONF.clear_override('multi_cloud', 'auth_password')
+ cfg.CONF.clear_override('allowed_auth_uris', 'auth_password')
+
def _start_fake_response(self, status, headers):
self.response_status = int(status.split(' ', 1)[0])
self.response_headers = dict(headers)
req = webob.Request.blank('/')
self.middleware(req.environ, self._start_fake_response)
self.assertEqual(self.response_status, 401)
+
+ def _test_multi_cloud(self, allowed_auth_uris=[]):
+ cfg.CONF.set_override('multi_cloud', True, group='auth_password')
+ auth_url = 'http://multicloud.test.com:5000/v2.0'
+ cfg.CONF.set_override('allowed_auth_uris',
+ allowed_auth_uris,
+ group='auth_password')
+ self.app = FakeApp(
+ expected_env={'HTTP_X_AUTH_URL': auth_url})
+ self.middleware = KeystonePasswordAuthProtocol(self.app, self.config)
+
+ self.m.StubOutClassWithMocks(keystone_client, 'Client')
+ mock_client = keystone_client.Client(
+ username='user_name1', password='goodpassword',
+ tenant_id='tenant_id1', auth_url=auth_url)
+ mock_client.auth_ref = TOKEN_RESPONSE
+ self.m.ReplayAll()
+ req = webob.Request.blank('/tenant_id1/')
+ req.headers['X_AUTH_USER'] = 'user_name1'
+ req.headers['X_AUTH_KEY'] = 'goodpassword'
+ req.headers['X_AUTH_URL'] = auth_url
+ self.middleware(req.environ, self._start_fake_response)
+ self.m.VerifyAll()
+
+ def test_multi_cloud(self):
+ self._test_multi_cloud(['http://multicloud.test.com:5000/v2.0'])
+
+ def test_multi_cloud_empty_allowed_uris(self):
+ self._test_multi_cloud()
+
+ def test_multi_cloud_target_not_allowed(self):
+ cfg.CONF.set_override('multi_cloud', True, group='auth_password')
+ auth_url = 'http://multicloud.test.com:5000/v2.0'
+ cfg.CONF.set_override('allowed_auth_uris',
+ ['http://some.other.url:5000/v2.0'],
+ group='auth_password')
+ req = webob.Request.blank('/tenant_id1/')
+ req.headers['X_AUTH_USER'] = 'user_name1'
+ req.headers['X_AUTH_KEY'] = 'goodpassword'
+ req.headers['X_AUTH_URL'] = auth_url
+ self.middleware(req.environ, self._start_fake_response)
+ self.assertEqual(self.response_status, 401)
+
+ def test_multi_cloud_no_auth_url(self):
+ cfg.CONF.set_override('multi_cloud', True, group='auth_password')
+ req = webob.Request.blank('/tenant_id1/')
+ req.headers['X_AUTH_USER'] = 'user_name1'
+ req.headers['X_AUTH_KEY'] = 'goodpassword'
+ response = self.middleware(req.environ, self._start_fake_response)
+ self.assertEqual(self.response_status, 400)