--- /dev/null
+From 467adeb3dc9a89aa6b39780b83196501d5c31ea7 Mon Sep 17 00:00:00 2001
+From: Serg Melikyan <smelikyan@mirantis.com>
+Date: Tue, 12 Nov 2013 14:33:17 +0400
+Subject: [PATCH] Adds ability to configure SSL params for clients used by the Heat
+
+---
+ etc/heat/heat.conf.sample | 169 ++++++++++++++++++++++++++++++++++++
+ heat/common/config.py | 32 ++++++-
+ heat/common/heat_keystoneclient.py | 18 ++++
+ heat/engine/clients.py | 50 +++++++++--
+ heat/tests/test_heatclient.py | 36 ++++++--
+ 5 files changed, 291 insertions(+), 14 deletions(-)
+
+diff --git a/etc/heat/heat.conf.sample b/etc/heat/heat.conf.sample
+index 1444f9b..376c98e 100644
+--- a/etc/heat/heat.conf.sample
++++ b/etc/heat/heat.conf.sample
+@@ -724,3 +724,172 @@
+ #password=<None>
+
+
++[clients_swift]
++
++#
++# Options defined in heat.common.config
++#
++
++# Optional CA cert file to use in SSL connections (string
++# value)
++#ca_file=<None>
++
++# Optional PEM-formatted certificate chain file (string value)
++#cert_file=<None>
++
++# Optional PEM-formatted file that contains the private key
++# (string value)
++#key_file=<None>
++
++# If set then the server's certificate will not be verified
++# (boolean value)
++#insecure=false
++
++# Endpoint type
++#endpoint_type=publicURL
++
++
++[clients_cinder]
++
++#
++# Options defined in heat.common.config
++#
++
++# Optional CA cert file to use in SSL connections (string
++# value)
++#ca_file=<None>
++
++# Optional PEM-formatted certificate chain file (string value)
++#cert_file=<None>
++
++# Optional PEM-formatted file that contains the private key
++# (string value)
++#key_file=<None>
++
++# If set then the server's certificate will not be verified
++# (boolean value)
++#insecure=false
++
++# Endpoint type
++#endpoint_type=publicURL
++
++[clients]
++
++#
++# Options defined in heat.common.config
++#
++
++# Optional CA cert file to use in SSL connections (string
++# value)
++#ca_file=<None>
++
++# Optional PEM-formatted certificate chain file (string value)
++#cert_file=<None>
++
++# Optional PEM-formatted file that contains the private key
++# (string value)
++#key_file=<None>
++
++# If set then the server's certificate will not be verified
++# (boolean value)
++#insecure=false
++
++# Endpoint type
++#endpoint_type=publicURL
++
++[clients_nova]
++
++#
++# Options defined in heat.common.config
++#
++
++# Optional CA cert file to use in SSL connections (string
++# value)
++#ca_file=<None>
++
++# Optional PEM-formatted certificate chain file (string value)
++#cert_file=<None>
++
++# Optional PEM-formatted file that contains the private key
++# (string value)
++#key_file=<None>
++
++# If set then the server's certificate will not be verified
++# (boolean value)
++#insecure=false
++
++# Endpoint type
++#endpoint_type=publicURL
++
++[clients_ceilometer]
++
++#
++# Options defined in heat.common.config
++#
++
++# Optional CA cert file to use in SSL connections (string
++# value)
++#ca_file=<None>
++
++# Optional PEM-formatted certificate chain file (string value)
++#cert_file=<None>
++
++# Optional PEM-formatted file that contains the private key
++# (string value)
++#key_file=<None>
++
++# If set then the server's certificate will not be verified
++# (boolean value)
++#insecure=false
++
++# Endpoint type
++#endpoint_type=publicURL
++
++[clients_neutron]
++
++#
++# Options defined in heat.common.config
++#
++
++# Optional CA cert file to use in SSL connections (string
++# value)
++#ca_file=<None>
++
++# Optional PEM-formatted certificate chain file (string value)
++#cert_file=<None>
++
++# Optional PEM-formatted file that contains the private key
++# (string value)
++#key_file=<None>
++
++# If set then the server's certificate will not be verified
++# (boolean value)
++#insecure=false
++
++# Endpoint type
++#endpoint_type=publicURL
++
++
++[clients_keystone]
++
++#
++# Options defined in heat.common.config
++#
++
++# Optional CA cert file to use in SSL connections (string
++# value)
++#ca_file=<None>
++
++# Optional PEM-formatted certificate chain file (string value)
++#cert_file=<None>
++
++# Optional PEM-formatted file that contains the private key
++# (string value)
++#key_file=<None>
++
++# If set then the server's certificate will not be verified
++# (boolean value)
++#insecure=false
++
++# Endpoint type
++#endpoint_type=publicURL
+diff --git a/heat/common/config.py b/heat/common/config.py
+index 82b4ca5..155d4f4 100644
+--- a/heat/common/config.py
++++ b/heat/common/config.py
+@@ -1,4 +1,3 @@
+-
+ # vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+ #
+@@ -18,6 +17,7 @@
+ Routines for configuring Heat
+ """
+
++import copy
+ import logging as sys_logging
+ import os
+
+@@ -134,6 +134,35 @@ auth_password_opts = [
+ 'multi_cloud is enabled. At least one endpoint needs '
+ 'to be specified.'))]
+
++clients_opts = [
++ cfg.StrOpt('ca_file',
++ help=_('Optional CA cert file to use in SSL connections')),
++ cfg.StrOpt('cert_file',
++ help=_('Optional PEM-formatted certificate chain file')),
++ cfg.StrOpt('key_file',
++ help=_('Optional PEM-formatted file that contains the '
++ 'private key')),
++ cfg.BoolOpt('insecure',
++ default=False,
++ help=_("If set then the server's certificate will not "
++ "be verified")),
++ cfg.StrOpt('endpoint_type',
++ default='publicURL',
++ help=_('Endpoint type'))]
++
++
++def register_clients_opts():
++ cfg.CONF.register_opts(clients_opts, group='clients')
++ for client in ('nova', 'swift', 'neutron', 'cinder',
++ 'ceilometer', 'keystone'):
++ client_specific_group = 'clients_' + client
++ # register opts copy and put it to globals in order to
++ # generate_sample.sh to work
++ opts_copy = copy.deepcopy(clients_opts)
++ globals()[client_specific_group + '_opts'] = opts_copy
++ cfg.CONF.register_opts(opts_copy, group=client_specific_group)
++
++
+ cfg.CONF.register_opts(db_opts)
+ cfg.CONF.register_opts(engine_opts)
+ cfg.CONF.register_opts(service_opts)
+@@ -142,6 +171,7 @@ 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)
++register_clients_opts()
+
+
+ def rpc_set_default():
+diff --git a/heat/common/heat_keystoneclient.py b/heat/common/heat_keystoneclient.py
+index 8fb13f7..8099ef2 100644
+--- a/heat/common/heat_keystoneclient.py
++++ b/heat/common/heat_keystoneclient.py
+@@ -100,6 +100,10 @@ class KeystoneClient(object):
+ logger.error("Keystone v2 API connection failed, no password or "
+ "auth_token!")
+ raise exception.AuthorizationFailure()
++ kwargs['cacert'] = self._get_client_option('ca_file')
++ kwargs['insecure'] = self._get_client_option('insecure')
++ kwargs['cert'] = self._get_client_option('cert_file')
++ kwargs['key'] = self._get_client_option('key_file')
+ client_v2 = kc.Client(**kwargs)
+
+ client_v2.authenticate(**auth_kwargs)
+@@ -161,12 +165,26 @@ class KeystoneClient(object):
+ "auth_token!")
+ raise exception.AuthorizationFailure()
+
++ kwargs['cacert'] = self._get_client_option('ca_file')
++ kwargs['insecure'] = self._get_client_option('insecure')
++ kwargs['cert'] = self._get_client_option('cert_file')
++ kwargs['key'] = self._get_client_option('key_file')
++
+ client = kc_v3.Client(**kwargs)
+ # Have to explicitly authenticate() or client.auth_ref is None
+ client.authenticate()
+
+ return client
+
++ def _get_client_option(self, option):
++ try:
++ cfg.CONF.import_opt(option, 'heat.common.config',
++ group='clients_keystone')
++ return getattr(cfg.CONF.clients_keystone, option)
++ except (cfg.NoSuchGroupError, cfg.NoSuchOptError):
++ cfg.CONF.import_opt(option, 'heat.common.config', group='clients')
++ return getattr(cfg.CONF.clients, option)
++
+ def create_trust_context(self):
+ """
+ If cfg.CONF.deferred_auth_method is trusts, we create a
+diff --git a/heat/engine/clients.py b/heat/engine/clients.py
+index 6deae5b..a9475f7 100644
+--- a/heat/engine/clients.py
++++ b/heat/engine/clients.py
+@@ -103,12 +103,16 @@ class OpenStackClients(object):
+ 'service_type': service_type,
+ 'username': None,
+ 'api_key': None,
+- 'extensions': extensions
++ 'extensions': extensions,
++ 'cacert': self._get_client_option('nova', 'ca_file'),
++ 'insecure': self._get_client_option('nova', 'insecure')
+ }
+
+ client = novaclient.Client(1.1, **args)
+
+- management_url = self.url_for(service_type=service_type)
++ management_url = self.url_for(
++ service_type=service_type,
++ endpoint_type=self._get_client_option('nova', 'endpoint_type'))
+ client.client.auth_token = self.auth_token
+ client.client.management_url = management_url
+
+@@ -133,7 +137,12 @@ class OpenStackClients(object):
+ 'key': None,
+ 'authurl': None,
+ 'preauthtoken': self.auth_token,
+- 'preauthurl': self.url_for(service_type='object-store')
++ 'preauthurl': self.url_for(
++ service_type='object-store',
++ endpoint_type=self._get_client_option(
++ 'swift', 'endpoint_type')),
++ 'cacert': self._get_client_option('swift', 'ca_file'),
++ 'insecure': self._get_client_option('swift', 'insecure')
+ }
+ self._swift = swiftclient.Connection(**args)
+ return self._swift
+@@ -153,7 +162,12 @@ class OpenStackClients(object):
+ 'auth_url': con.auth_url,
+ 'service_type': 'network',
+ 'token': self.auth_token,
+- 'endpoint_url': self.url_for(service_type='network')
++ 'endpoint_url': self.url_for(
++ service_type='network',
++ endpoint_type=self._get_client_option(
++ 'neutron', 'endpoint_type')),
++ 'ca_cert': self._get_client_option('neutron', 'ca_file'),
++ 'insecure': self._get_client_option('neutron', 'insecure')
+ }
+
+ self._neutron = neutronclient.Client(**args)
+@@ -176,11 +190,16 @@ class OpenStackClients(object):
+ 'auth_url': con.auth_url,
+ 'project_id': con.tenant,
+ 'username': None,
+- 'api_key': None
++ 'api_key': None,
++ 'cacert': self._get_client_option('cinder', 'ca_file'),
++ 'insecure': self._get_client_option('cinder', 'insecure')
+ }
+
+ self._cinder = cinderclient.Client('1', **args)
+- management_url = self.url_for(service_type='volume')
++ management_url = self.url_for(
++ service_type='volume',
++ endpoint_type=self._get_client_option(
++ 'cinder', 'endpoint_type'))
+ self._cinder.client.auth_token = self.auth_token
+ self._cinder.client.management_url = management_url
+
+@@ -201,7 +220,14 @@ class OpenStackClients(object):
+ 'service_type': 'metering',
+ 'project_id': con.tenant,
+ 'token': lambda: self.auth_token,
+- 'endpoint': self.url_for(service_type='metering'),
++ 'endpoint': self.url_for(
++ service_type='metering',
++ endpoint_type=self._get_client_option(
++ 'ceilometer', 'endpoint_type')),
++ 'ca_file': self._get_client_option('ceilometer', 'ca_file'),
++ 'cert_file': self._get_client_option('ceilometer', 'cert_file'),
++ 'key_file': self._get_client_option('ceilometer', 'key_file'),
++ 'insecure': self._get_client_option('ceilometer', 'insecure')
+ }
+
+ client = ceilometerclient.Client(**args)
+@@ -209,6 +235,15 @@ class OpenStackClients(object):
+ self._ceilometer = client
+ return self._ceilometer
+
++ def _get_client_option(self, client, option):
++ try:
++ group_name = 'clients_' + client
++ cfg.CONF.import_opt(option, 'heat.common.config',
++ group=group_name)
++ return getattr(getattr(cfg.CONF, group_name), option)
++ except (cfg.NoSuchGroupError, cfg.NoSuchOptError):
++ cfg.CONF.import_opt(option, 'heat.common.config', group='clients')
++ return getattr(cfg.CONF.clients, option)
+
+ if cfg.CONF.cloud_backend:
+ cloud_backend_module = importutils.import_module(cfg.CONF.cloud_backend)
+@@ -217,3 +252,4 @@ else:
+ Clients = OpenStackClients
+
+ logger.debug('Using backend %s' % Clients)
++
+diff --git a/heat/tests/test_heatclient.py b/heat/tests/test_heatclient.py
+index 7e195dc..712ffa5 100644
+--- a/heat/tests/test_heatclient.py
++++ b/heat/tests/test_heatclient.py
+@@ -51,7 +51,11 @@ class KeystoneClientTest(HeatTestCase):
+ self.mock_ks_client = heat_keystoneclient.kc.Client(
+ auth_url=mox.IgnoreArg(),
+ tenant_name='test_tenant',
+- token='abcd1234')
++ token='abcd1234',
++ cacert=None,
++ cert=None,
++ insecure=False,
++ key=None)
+ self.mock_ks_client.authenticate().AndReturn(auth_ok)
+ elif method == 'password':
+ self.mock_ks_client = heat_keystoneclient.kc.Client(
+@@ -59,14 +63,22 @@ class KeystoneClientTest(HeatTestCase):
+ tenant_name='test_tenant',
+ tenant_id='test_tenant_id',
+ username='test_username',
+- password='password')
++ password='password',
++ cacert=None,
++ cert=None,
++ insecure=False,
++ key=None)
+ self.mock_ks_client.authenticate().AndReturn(auth_ok)
+ if method == 'trust':
+ self.mock_ks_client = heat_keystoneclient.kc.Client(
+ auth_url='http://server.test:5000/v2.0',
+ password='verybadpass',
+ tenant_name='service',
+- username='heat')
++ username='heat',
++ cacert=None,
++ cert=None,
++ insecure=False,
++ key=None)
+ self.mock_ks_client.authenticate(trust_id='atrust123',
+ tenant_id='test_tenant_id'
+ ).AndReturn(auth_ok)
+@@ -81,7 +93,11 @@ class KeystoneClientTest(HeatTestCase):
+ self.mock_ks_v3_client = heat_keystoneclient.kc_v3.Client(
+ token='abcd1234', project_name='test_tenant',
+ auth_url='http://server.test:5000/v3',
+- endpoint='http://server.test:5000/v3')
++ endpoint='http://server.test:5000/v3',
++ cacert=None,
++ cert=None,
++ insecure=False,
++ key=None)
+ elif method == 'password':
+ self.mock_ks_v3_client = heat_keystoneclient.kc_v3.Client(
+ username='test_username',
+@@ -89,13 +105,21 @@ class KeystoneClientTest(HeatTestCase):
+ project_name='test_tenant',
+ project_id='test_tenant_id',
+ auth_url='http://server.test:5000/v3',
+- endpoint='http://server.test:5000/v3')
++ endpoint='http://server.test:5000/v3',
++ cacert=None,
++ cert=None,
++ insecure=False,
++ key=None)
+ elif method == 'trust':
+ self.mock_ks_v3_client = heat_keystoneclient.kc_v3.Client(
+ username='heat',
+ password='verybadpass',
+ project_name='service',
+- auth_url='http://server.test:5000/v3')
++ auth_url='http://server.test:5000/v3',
++ cacert=None,
++ cert=None,
++ insecure=False,
++ key=None)
+ self.mock_ks_v3_client.authenticate().AndReturn(auth_ok)
+
+ def test_username_length(self):
+--
+1.7.9.5
+