summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGeorgina Shippey <georgina.shippey@bbc.co.uk>2019-04-08 19:00:32 +0100
committerJonathan Rosser <jonathan.rosser@rd.bbc.co.uk>2019-08-23 06:46:34 +0000
commit35ef93d037baf4e704f23ee20f58d11b1d4ccbe7 (patch)
tree3f71bd67267fae58e5aebac569444377ab619aef
parent3476f1dcfce383e6c6031a118cfeb0c9871f0474 (diff)
downloadheat-35ef93d037baf4e704f23ee20f58d11b1d4ccbe7.tar.gz
Add dedicated auth endpoint config for servers
Added a new config option to specify the keystone authentication endpoint to pass into cloud-init data. Heat code currently has several different methods of retrieving the keystone endpoint to embed into cloud-init data for created servers. This data is currently read from several different parts of the heat config file rather than the service catalog which results in URLs being passed which are appropriate for the heat service rather than the server. In particular there can be misconfiguration of servers due to deployments which separate the internal and external API endpoints. This patch introduces a new config variable server_keystone_endpoint_type which if set reads the keystone endpoint directly from the service catalog, if it is unset the original behavior is unchanged. story: 2004808 task: 28967 story: 2004524 Change-Id: I5d8fc5977014b196c34f4a59a30a7525bc778359 (cherry picked from commit 5ba3b608741a0f00744c87b016185a6d845a34b9)
-rw-r--r--heat/common/config.py9
-rw-r--r--heat/engine/clients/os/keystone/fake_keystoneclient.py3
-rw-r--r--heat/engine/clients/os/keystone/heat_keystoneclient.py23
-rw-r--r--heat/engine/resources/server_base.py6
-rw-r--r--heat/engine/resources/signal_responder.py3
-rw-r--r--heat/tests/clients/test_heat_client.py46
-rw-r--r--releasenotes/notes/add-dedicated-auth-endpoint-config-for-servers-b20f7eb351f619d0.yaml16
7 files changed, 102 insertions, 4 deletions
diff --git a/heat/common/config.py b/heat/common/config.py
index c4c9e1d2e..8e39719c8 100644
--- a/heat/common/config.py
+++ b/heat/common/config.py
@@ -88,7 +88,14 @@ service_opts = [
cfg.IntOpt('num_engine_workers',
help=_('Number of heat-engine processes to fork and run. '
'Will default to either to 4 or number of CPUs on '
- 'the host, whichever is greater.'))]
+ 'the host, whichever is greater.')),
+ cfg.StrOpt('server_keystone_endpoint_type',
+ choices=['', 'public', 'internal', 'admin'],
+ default='',
+ help=_('If set, is used to control which authentication '
+ 'endpoint is used by user-controlled servers to make '
+ 'calls back to Heat. '
+ 'If unset www_authenticate_uri is used.'))]
engine_opts = [
cfg.ListOpt('plugin_dirs',
diff --git a/heat/engine/clients/os/keystone/fake_keystoneclient.py b/heat/engine/clients/os/keystone/fake_keystoneclient.py
index 6e594ecfb..524d09a8e 100644
--- a/heat/engine/clients/os/keystone/fake_keystoneclient.py
+++ b/heat/engine/clients/os/keystone/fake_keystoneclient.py
@@ -121,3 +121,6 @@ class FakeKeystoneClient(object):
def stack_domain_user_token(self, user_id, project_id, password):
return 'adomainusertoken'
+
+ def server_keystone_endpoint_url(self, fallback_endpoint):
+ return fallback_endpoint
diff --git a/heat/engine/clients/os/keystone/heat_keystoneclient.py b/heat/engine/clients/os/keystone/heat_keystoneclient.py
index f91c2c134..b317d55e7 100644
--- a/heat/engine/clients/os/keystone/heat_keystoneclient.py
+++ b/heat/engine/clients/os/keystone/heat_keystoneclient.py
@@ -550,6 +550,29 @@ class KsClientWrapper(object):
self._check_stack_domain_user(user_id, project_id, 'enable')
self.domain_admin_client.users.update(user=user_id, enabled=True)
+ def server_keystone_endpoint_url(self, fallback_endpoint):
+ ks_endpoint_type = cfg.CONF.server_keystone_endpoint_type
+ if ((ks_endpoint_type == 'public') or (
+ ks_endpoint_type == 'internal') or
+ (ks_endpoint_type == 'admin')):
+ if (hasattr(self.context, 'auth_plugin') and
+ hasattr(self.context.auth_plugin, 'get_access')):
+ try:
+ auth_ref = self.context.auth_plugin.get_access(
+ self.session)
+ if hasattr(auth_ref, "service_catalog"):
+ unversioned_sc_auth_uri = (
+ auth_ref.service_catalog.get_urls(
+ service_type='identity',
+ interface=ks_endpoint_type))
+ if len(unversioned_sc_auth_uri) > 0:
+ sc_auth_uri = (
+ unversioned_sc_auth_uri[0] + "/v3")
+ return sc_auth_uri
+ except ks_exception.Unauthorized:
+ LOG.error("Keystone client authentication failed")
+ return fallback_endpoint
+
class KeystoneClient(object):
"""Keystone Auth Client.
diff --git a/heat/engine/resources/server_base.py b/heat/engine/resources/server_base.py
index 983efe298..5f0b5d00c 100644
--- a/heat/engine/resources/server_base.py
+++ b/heat/engine/resources/server_base.py
@@ -84,7 +84,8 @@ class BaseServer(stack_user.StackUser):
occ.update({'heat': {
'user_id': self._get_user_id(),
'password': self.password,
- 'auth_url': self.context.auth_url,
+ 'auth_url': self.keystone().server_keystone_endpoint_url(
+ fallback_endpoint=self.context.auth_url),
'project_id': self.stack.stack_user_project_id,
'stack_id': self.stack.identifier().stack_path(),
'resource_name': self.name,
@@ -96,7 +97,8 @@ class BaseServer(stack_user.StackUser):
occ.update({'zaqar': {
'user_id': self._get_user_id(),
'password': self.password,
- 'auth_url': self.context.auth_url,
+ 'auth_url': self.keystone().server_keystone_endpoint_url(
+ fallback_endpoint=self.context.auth_url),
'project_id': self.stack.stack_user_project_id,
'queue_id': queue_id,
'region_name': region_name}})
diff --git a/heat/engine/resources/signal_responder.py b/heat/engine/resources/signal_responder.py
index 23c1f9c76..b9127d9f1 100644
--- a/heat/engine/resources/signal_responder.py
+++ b/heat/engine/resources/signal_responder.py
@@ -103,7 +103,8 @@ class SignalResponder(stack_user.StackUser):
if self.password is None:
self.password = password_gen.generate_openstack_password()
self._create_user()
- return {'auth_url': self.keystone().v3_endpoint,
+ return {'auth_url': self.keystone().server_keystone_endpoint_url(
+ fallback_endpoint=self.keystone().v3_endpoint),
'username': self.physical_resource_name(),
'user_id': self._get_user_id(),
'password': self.password,
diff --git a/heat/tests/clients/test_heat_client.py b/heat/tests/clients/test_heat_client.py
index 8397ca740..c2971df7d 100644
--- a/heat/tests/clients/test_heat_client.py
+++ b/heat/tests/clients/test_heat_client.py
@@ -1433,6 +1433,52 @@ class KeystoneClientTest(common.HeatTestCase):
self.assertIsNone(heat_ks_client.delete_stack_domain_project(
project_id='aprojectid'))
+ def test_server_keystone_endpoint_url_config(self):
+ """Return non fallback url path."""
+ cfg.CONF.set_override('server_keystone_endpoint_type', 'public')
+ ctx = utils.dummy_context()
+ ctx.trust_id = None
+ heat_ks_client = heat_keystoneclient.KeystoneClient(ctx)
+ fallback_url = 'http://server.fallback.test:5000/v3'
+ auth_ref = heat_ks_client.context.auth_plugin.get_access(
+ heat_ks_client.session)
+ auth_ref.service_catalog.get_urls = mock.MagicMock()
+ auth_ref.service_catalog.get_urls.return_value = [
+ 'http://server.public.test:5000']
+ self.assertEqual(
+ heat_ks_client.server_keystone_endpoint_url(fallback_url),
+ 'http://server.public.test:5000/v3')
+ cfg.CONF.clear_override('server_keystone_endpoint_type')
+
+ def test_server_keystone_endpoint_url_no_config(self):
+ """Return fallback as no config option specified."""
+ ctx = utils.dummy_context()
+ ctx.trust_id = None
+ heat_ks_client = heat_keystoneclient.KeystoneClient(ctx)
+ cfg.CONF.clear_override('server_keystone_endpoint_type')
+ fallback_url = 'http://server.fallback.test:5000/v3'
+ self.assertEqual(heat_ks_client.server_keystone_endpoint_url(
+ fallback_url), fallback_url)
+
+ def test_server_keystone_endpoint_url_auth_exception(self):
+ """Authorization call fails, return fallback."""
+ cfg.CONF.set_override('server_keystone_endpoint_type', 'public')
+ ctx = utils.dummy_context()
+ ctx.trust_id = None
+ heat_ks_client = heat_keystoneclient.KeystoneClient(ctx)
+ auth_ref = heat_ks_client.context.auth_plugin.get_access(
+ heat_ks_client.session)
+ auth_ref.service_catalog.get_urls = mock.MagicMock()
+ auth_ref.service_catalog.get_urls.return_value = [
+ 'http://server.public.test:5000']
+ heat_ks_client.context.auth_plugin.get_access = mock.MagicMock()
+ heat_ks_client.context.auth_plugin.get_access.side_effect = (
+ kc_exception.Unauthorized)
+ fallback_url = 'http://server.fallback.test:5000/v3'
+ self.assertEqual(heat_ks_client.server_keystone_endpoint_url(
+ fallback_url), fallback_url)
+ cfg.CONF.clear_override('server_keystone_endpoint_type')
+
class KeystoneClientTestDomainName(KeystoneClientTest):
def setUp(self):
diff --git a/releasenotes/notes/add-dedicated-auth-endpoint-config-for-servers-b20f7eb351f619d0.yaml b/releasenotes/notes/add-dedicated-auth-endpoint-config-for-servers-b20f7eb351f619d0.yaml
new file mode 100644
index 000000000..52d93f8da
--- /dev/null
+++ b/releasenotes/notes/add-dedicated-auth-endpoint-config-for-servers-b20f7eb351f619d0.yaml
@@ -0,0 +1,16 @@
+---
+features:
+ - |
+ Added a new config option server_keystone_endpoint_type to specify
+ the keystone authentication endpoint (public/internal/admin)
+ to pass into cloud-init data.
+ If left unset the original behavior should remain unchanged.
+
+ This feature allows the deployer to unambiguously specify the
+ keystone endpoint passed to user provisioned servers, and is particularly
+ useful where the deployment network architecture requires the heat
+ service to interact with the internal endpoint,
+ but user provisioned servers only have access to the external network.
+
+ For more information see
+ http://lists.openstack.org/pipermail/openstack-discuss/2019-February/002925.html