diff options
author | Georgina Shippey <georgina.shippey@bbc.co.uk> | 2019-04-08 19:00:32 +0100 |
---|---|---|
committer | Jonathan Rosser <jonathan.rosser@rd.bbc.co.uk> | 2019-08-23 06:46:34 +0000 |
commit | 35ef93d037baf4e704f23ee20f58d11b1d4ccbe7 (patch) | |
tree | 3f71bd67267fae58e5aebac569444377ab619aef | |
parent | 3476f1dcfce383e6c6031a118cfeb0c9871f0474 (diff) | |
download | heat-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.py | 9 | ||||
-rw-r--r-- | heat/engine/clients/os/keystone/fake_keystoneclient.py | 3 | ||||
-rw-r--r-- | heat/engine/clients/os/keystone/heat_keystoneclient.py | 23 | ||||
-rw-r--r-- | heat/engine/resources/server_base.py | 6 | ||||
-rw-r--r-- | heat/engine/resources/signal_responder.py | 3 | ||||
-rw-r--r-- | heat/tests/clients/test_heat_client.py | 46 | ||||
-rw-r--r-- | releasenotes/notes/add-dedicated-auth-endpoint-config-for-servers-b20f7eb351f619d0.yaml | 16 |
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 |