diff --git a/octavia/amphorae/backends/agent/api_server/util.py b/octavia/amphorae/backends/agent/api_server/util.py index fef833d3fc..22ca99f9c5 100644 --- a/octavia/amphorae/backends/agent/api_server/util.py +++ b/octavia/amphorae/backends/agent/api_server/util.py @@ -30,7 +30,7 @@ LOG = logging.getLogger(__name__) FRONTEND_BACKEND_PATTERN = re.compile(r'\n(frontend|backend)\s+(\S+)\n') LISTENER_MODE_PATTERN = re.compile(r'^\s+mode\s+(.*)$', re.MULTILINE) -TLS_CERT_PATTERN = re.compile(r'^\s+bind\s+\S+\s+ssl crt\s+(.*)$', +TLS_CERT_PATTERN = re.compile(r'^\s+bind\s+\S+\s+ssl crt-list\s+(.*)$', re.MULTILINE) STATS_SOCKET_PATTERN = re.compile(r'stats socket\s+(\S+)') diff --git a/octavia/amphorae/drivers/haproxy/rest_api_driver.py b/octavia/amphorae/drivers/haproxy/rest_api_driver.py index 63eb6d4eb0..bee2b2145c 100644 --- a/octavia/amphorae/drivers/haproxy/rest_api_driver.py +++ b/octavia/amphorae/drivers/haproxy/rest_api_driver.py @@ -145,7 +145,6 @@ class HaproxyAmphoraLoadBalancerDriver( 'process mode.', amphora.id, loadbalancer.id) has_tcp = False - certs = {} for listener in loadbalancer.listeners: LOG.debug("%s updating listener %s on amphora %s", self.__class__.__name__, listener.id, amphora.id) @@ -163,10 +162,8 @@ class HaproxyAmphoraLoadBalancerDriver( else: obj_id = loadbalancer.id - certs.update({ - listener.tls_certificate_id: - self._process_tls_certificates( - listener, amphora, obj_id)['tls_cert']}) + self._process_tls_certificates(listener, amphora, obj_id) + client_ca_filename = self._process_secret( listener, listener.client_ca_tls_certificate_id, amphora, obj_id) @@ -179,7 +176,6 @@ class HaproxyAmphoraLoadBalancerDriver( if split_config: config = self.jinja_split.build_config( host_amphora=amphora, listener=listener, - tls_cert=certs[listener.tls_certificate_id], haproxy_versions=haproxy_versions, client_ca_filename=client_ca_filename, client_crl=crl_filename, @@ -194,7 +190,6 @@ class HaproxyAmphoraLoadBalancerDriver( # Generate HaProxy configuration from listener object config = self.jinja_combo.build_config( host_amphora=amphora, listeners=loadbalancer.listeners, - tls_certs=certs, haproxy_versions=haproxy_versions, client_ca_filename=client_ca_filename, client_crl=crl_filename, @@ -414,11 +409,13 @@ class HaproxyAmphoraLoadBalancerDriver( tls_cert = None sni_certs = [] certs = [] + cert_filename_list = [] data = cert_parser.load_certificates_data( self.cert_manager, listener) if data['tls_cert'] is not None: tls_cert = data['tls_cert'] + # Note, the first cert is the TLS default cert certs.append(tls_cert) if data['sni_certs']: sni_certs = data['sni_certs'] @@ -429,7 +426,17 @@ class HaproxyAmphoraLoadBalancerDriver( pem = cert_parser.build_pem(cert) md5 = hashlib.md5(pem).hexdigest() # nosec name = '{id}.pem'.format(id=cert.id) + cert_filename_list.append( + os.path.join( + CONF.haproxy_amphora.base_cert_dir, obj_id, name)) self._upload_cert(amphora, obj_id, pem, md5, name) + + if certs: + # Build and upload the crt-list file for haproxy + crt_list = "\n".join(cert_filename_list).encode('utf-8') + md5 = hashlib.md5(crt_list).hexdigest() # nosec + name = '{id}.pem'.format(id=listener.id) + self._upload_cert(amphora, obj_id, crt_list, md5, name) return {'tls_cert': tls_cert, 'sni_certs': sni_certs} def _process_secret(self, listener, secret_ref, amphora=None, obj_id=None): diff --git a/octavia/common/jinja/haproxy/combined_listeners/jinja_cfg.py b/octavia/common/jinja/haproxy/combined_listeners/jinja_cfg.py index 99aaf19946..c9c628af6b 100644 --- a/octavia/common/jinja/haproxy/combined_listeners/jinja_cfg.py +++ b/octavia/common/jinja/haproxy/combined_listeners/jinja_cfg.py @@ -81,15 +81,13 @@ class JinjaTemplater(object): self.log_server = log_server self.connection_logging = connection_logging - def build_config(self, host_amphora, listeners, tls_certs, - haproxy_versions, socket_path=None, - client_ca_filename=None, client_crl=None, - pool_tls_certs=None): + def build_config(self, host_amphora, listeners, haproxy_versions, + socket_path=None, client_ca_filename=None, + client_crl=None, pool_tls_certs=None): """Convert a logical configuration to the HAProxy version :param host_amphora: The Amphora this configuration is hosted on :param listener: The listener configuration - :param tls_certs: Dict of the TLS certificates for the listeners :param socket_path: The socket path for Haproxy process :return: Rendered configuration """ @@ -104,8 +102,7 @@ class JinjaTemplater(object): feature_compatibility[constants.HTTP_REUSE] = True return self.render_loadbalancer_obj( - host_amphora, listeners, tls_certs=tls_certs, - socket_path=socket_path, + host_amphora, listeners, socket_path=socket_path, feature_compatibility=feature_compatibility, client_ca_filename=client_ca_filename, client_crl=client_crl, pool_tls_certs=pool_tls_certs) @@ -144,15 +141,13 @@ class JinjaTemplater(object): return log_format def render_loadbalancer_obj(self, host_amphora, listeners, - tls_certs=None, socket_path=None, - feature_compatibility=None, + socket_path=None, feature_compatibility=None, client_ca_filename=None, client_crl=None, pool_tls_certs=None): """Renders a templated configuration from a load balancer object :param host_amphora: The Amphora this configuration is hosted on :param listener: The listener configuration - :param tls_certs: Dict of the TLS certificates for the listener :param client_ca_filename: The CA certificate for client authorization :param socket_path: The socket path for Haproxy process :return: Rendered configuration @@ -162,7 +157,6 @@ class JinjaTemplater(object): host_amphora, listeners[0].load_balancer, listeners, - tls_certs, feature_compatibility, client_ca_filename=client_ca_filename, client_crl=client_crl, @@ -182,9 +176,8 @@ class JinjaTemplater(object): constants=constants) def _transform_loadbalancer(self, host_amphora, loadbalancer, listeners, - tls_certs, feature_compatibility, - client_ca_filename=None, client_crl=None, - pool_tls_certs=None): + feature_compatibility, client_ca_filename=None, + client_crl=None, pool_tls_certs=None): """Transforms a load balancer into an object that will be processed by the templating system @@ -194,7 +187,7 @@ class JinjaTemplater(object): if listener.protocol == constants.PROTOCOL_UDP: continue listener_transforms.append(self._transform_listener( - listener, tls_certs, feature_compatibility, loadbalancer, + listener, feature_compatibility, loadbalancer, client_ca_filename=client_ca_filename, client_crl=client_crl, pool_tls_certs=pool_tls_certs)) @@ -246,7 +239,7 @@ class JinjaTemplater(object): 'vrrp_priority': amphora.vrrp_priority } - def _transform_listener(self, listener, tls_certs, feature_compatibility, + def _transform_listener(self, listener, feature_compatibility, loadbalancer, client_ca_filename=None, client_crl=None, pool_tls_certs=None): """Transforms a listener into an object that will @@ -279,14 +272,12 @@ class JinjaTemplater(object): ret_value['connection_limit'] = listener.connection_limit else: ret_value['connection_limit'] = constants.HAPROXY_MAX_MAXCONN - if listener.tls_certificate_id and tls_certs is not None: - ret_value['default_tls_path'] = '%s.pem' % ( - os.path.join(self.base_crt_dir, - loadbalancer.id, - tls_certs[listener.tls_certificate_id].id)) - if listener.sni_containers: - ret_value['crt_dir'] = os.path.join( - self.base_crt_dir, loadbalancer.id) + + if listener.tls_certificate_id: + ret_value['crt_list_filename'] = os.path.join( + CONF.haproxy_amphora.base_cert_dir, + loadbalancer.id, '{}.pem'.format(listener.id)) + if listener.client_ca_tls_certificate_id: ret_value['client_ca_tls_path'] = '%s' % ( os.path.join(self.base_crt_dir, loadbalancer.id, diff --git a/octavia/common/jinja/haproxy/combined_listeners/templates/macros.j2 b/octavia/common/jinja/haproxy/combined_listeners/templates/macros.j2 index a02d1f980d..96a8f957ff 100644 --- a/octavia/common/jinja/haproxy/combined_listeners/templates/macros.j2 +++ b/octavia/common/jinja/haproxy/combined_listeners/templates/macros.j2 @@ -27,17 +27,12 @@ peers {{ "%s_peers"|format(loadbalancer.id.replace("-", ""))|trim() }} {% macro bind_macro(constants, listener, lb_vip_address) %} - {% if listener.default_tls_path %} - {% set def_crt_opt = ("ssl crt %s"|format( - listener.default_tls_path)|trim()) %} + {% if listener.crt_list_filename is defined %} + {% set def_crt_opt = ("ssl crt-list %s"|format( + listener.crt_list_filename)|trim()) %} {% else %} {% set def_crt_opt = "" %} {% endif %} - {% if listener.crt_dir %} - {% set crt_dir_opt = "crt %s"|format(listener.crt_dir)|trim() %} - {% else %} - {% set crt_dir_opt = "" %} - {% endif %} {% if listener.client_ca_tls_path and listener.client_auth %} {% set client_ca_opt = "ca-file %s verify %s"|format(listener.client_ca_tls_path, listener.client_auth)|trim() %} {% else %} @@ -49,7 +44,7 @@ peers {{ "%s_peers"|format(loadbalancer.id.replace("-", ""))|trim() }} {% set ca_crl_opt = "" %} {% endif %} bind {{ lb_vip_address }}:{{ listener.protocol_port }} {{ -"%s %s %s %s"|format(def_crt_opt, crt_dir_opt, client_ca_opt, ca_crl_opt)|trim() }} +"%s %s %s"|format(def_crt_opt, client_ca_opt, ca_crl_opt)|trim() }} {% endmacro %} diff --git a/octavia/common/jinja/haproxy/split_listeners/jinja_cfg.py b/octavia/common/jinja/haproxy/split_listeners/jinja_cfg.py index 6b8159ff7e..c0cfb966d9 100644 --- a/octavia/common/jinja/haproxy/split_listeners/jinja_cfg.py +++ b/octavia/common/jinja/haproxy/split_listeners/jinja_cfg.py @@ -81,15 +81,13 @@ class JinjaTemplater(object): self.log_server = log_server self.connection_logging = connection_logging - def build_config(self, host_amphora, listener, tls_cert, - haproxy_versions, socket_path=None, - client_ca_filename=None, client_crl=None, - pool_tls_certs=None): + def build_config(self, host_amphora, listener, haproxy_versions, + socket_path=None, client_ca_filename=None, + client_crl=None, pool_tls_certs=None): """Convert a logical configuration to the HAProxy version :param host_amphora: The Amphora this configuration is hosted on :param listener: The listener configuration - :param tls_cert: The TLS certificates for the listener :param socket_path: The socket path for Haproxy process :return: Rendered configuration """ @@ -104,7 +102,7 @@ class JinjaTemplater(object): feature_compatibility[constants.HTTP_REUSE] = True return self.render_loadbalancer_obj( - host_amphora, listener, tls_cert=tls_cert, socket_path=socket_path, + host_amphora, listener, socket_path=socket_path, feature_compatibility=feature_compatibility, client_ca_filename=client_ca_filename, client_crl=client_crl, pool_tls_certs=pool_tls_certs) @@ -142,8 +140,7 @@ class JinjaTemplater(object): log_format = log_format.replace(' ', '\\ ') return log_format - def render_loadbalancer_obj(self, host_amphora, listener, - tls_cert=None, socket_path=None, + def render_loadbalancer_obj(self, host_amphora, listener, socket_path=None, feature_compatibility=None, client_ca_filename=None, client_crl=None, pool_tls_certs=None): @@ -151,21 +148,15 @@ class JinjaTemplater(object): :param host_amphora: The Amphora this configuration is hosted on :param listener: The listener configuration - :param tls_cert: The TLS certificates for the listener :param client_ca_filename: The CA certificate for client authorization :param socket_path: The socket path for Haproxy process :return: Rendered configuration """ feature_compatibility = feature_compatibility or {} loadbalancer = self._transform_loadbalancer( - host_amphora, - listener.load_balancer, - listener, - tls_cert, - feature_compatibility, - client_ca_filename=client_ca_filename, - client_crl=client_crl, - pool_tls_certs=pool_tls_certs) + host_amphora, listener.load_balancer, listener, + feature_compatibility, client_ca_filename=client_ca_filename, + client_crl=client_crl, pool_tls_certs=pool_tls_certs) if not socket_path: socket_path = '%s/%s.sock' % (self.base_amp_path, listener.id) return self._get_template().render( @@ -180,15 +171,14 @@ class JinjaTemplater(object): constants=constants) def _transform_loadbalancer(self, host_amphora, loadbalancer, listener, - tls_cert, feature_compatibility, - client_ca_filename=None, client_crl=None, - pool_tls_certs=None): + feature_compatibility, client_ca_filename=None, + client_crl=None, pool_tls_certs=None): """Transforms a load balancer into an object that will be processed by the templating system """ t_listener = self._transform_listener( - listener, tls_cert, feature_compatibility, loadbalancer, + listener, feature_compatibility, loadbalancer, client_ca_filename=client_ca_filename, client_crl=client_crl, pool_tls_certs=pool_tls_certs) ret_value = { @@ -229,7 +219,7 @@ class JinjaTemplater(object): 'vrrp_priority': amphora.vrrp_priority } - def _transform_listener(self, listener, tls_cert, feature_compatibility, + def _transform_listener(self, listener, feature_compatibility, loadbalancer, client_ca_filename=None, client_crl=None, pool_tls_certs=None): """Transforms a listener into an object that will @@ -265,13 +255,12 @@ class JinjaTemplater(object): ret_value['connection_limit'] = listener.connection_limit else: ret_value['connection_limit'] = constants.HAPROXY_MAX_MAXCONN + if listener.tls_certificate_id: - ret_value['default_tls_path'] = '%s.pem' % ( - os.path.join(self.base_crt_dir, - listener.id, - tls_cert.id)) - if listener.sni_containers: - ret_value['crt_dir'] = os.path.join(self.base_crt_dir, listener.id) + ret_value['crt_list_filename'] = os.path.join( + CONF.haproxy_amphora.base_cert_dir, + listener.id, '{}.pem'.format(listener.id)) + if listener.client_ca_tls_certificate_id: ret_value['client_ca_tls_path'] = '%s' % ( os.path.join(self.base_crt_dir, listener.id, diff --git a/octavia/common/jinja/haproxy/split_listeners/templates/macros.j2 b/octavia/common/jinja/haproxy/split_listeners/templates/macros.j2 index dbd8c0ac0e..ce5b090676 100644 --- a/octavia/common/jinja/haproxy/split_listeners/templates/macros.j2 +++ b/octavia/common/jinja/haproxy/split_listeners/templates/macros.j2 @@ -27,17 +27,12 @@ peers {{ "%s_peers"|format(listener.id.replace("-", ""))|trim() }} {% macro bind_macro(constants, listener, lb_vip_address) %} - {% if listener.default_tls_path %} - {% set def_crt_opt = ("ssl crt %s"|format( - listener.default_tls_path)|trim()) %} + {% if listener.crt_list_filename is defined %} + {% set def_crt_opt = ("ssl crt-list %s"|format( + listener.crt_list_filename)|trim()) %} {% else %} {% set def_crt_opt = "" %} {% endif %} - {% if listener.crt_dir %} - {% set crt_dir_opt = "crt %s"|format(listener.crt_dir)|trim() %} - {% else %} - {% set crt_dir_opt = "" %} - {% endif %} {% if listener.client_ca_tls_path and listener.client_auth %} {% set client_ca_opt = "ca-file %s verify %s"|format(listener.client_ca_tls_path, listener.client_auth)|trim() %} {% else %} @@ -49,7 +44,7 @@ peers {{ "%s_peers"|format(listener.id.replace("-", ""))|trim() }} {% set ca_crl_opt = "" %} {% endif %} bind {{ lb_vip_address }}:{{ listener.protocol_port }} {{ -"%s %s %s %s"|format(def_crt_opt, crt_dir_opt, client_ca_opt, ca_crl_opt)|trim() }} +"%s %s %s"|format(def_crt_opt, client_ca_opt, ca_crl_opt)|trim() }} {% endmacro %} diff --git a/octavia/tests/unit/amphorae/backends/agent/api_server/test_util.py b/octavia/tests/unit/amphorae/backends/agent/api_server/test_util.py index 990c4fd429..0ffbf6b79a 100644 --- a/octavia/tests/unit/amphorae/backends/agent/api_server/test_util.py +++ b/octavia/tests/unit/amphorae/backends/agent/api_server/test_util.py @@ -203,44 +203,33 @@ class TestUtil(base.TestCase): self.assertIsNone(result) def test_parse_haproxy_config(self): - # template_tls - tls_tupe = {'cont_id_1': - sample_configs_combined.sample_tls_container_tuple( - id='tls_container_id', - certificate='imaCert1', private_key='imaPrivateKey1', - primary_cn='FakeCN')} + self.CONF.config(group="haproxy_amphora", + base_cert_dir='/fake_cert_dir') + FAKE_CRT_LIST_FILENAME = os.path.join( + CONF.haproxy_amphora.base_cert_dir, + 'sample_loadbalancer_id_1/sample_listener_id_1.pem') rendered_obj = self.jinja_cfg.render_loadbalancer_obj( sample_configs_combined.sample_amphora_tuple(), [sample_configs_combined.sample_listener_tuple( - proto='TERMINATED_HTTPS', tls=True, sni=True)], - tls_tupe) + proto='TERMINATED_HTTPS', tls=True, sni=True)]) path = util.config_path(LISTENER_ID1) self.useFixture(test_utils.OpenFixture(path, rendered_obj)) res = util.parse_haproxy_file(LISTENER_ID1) listener_dict = res[1]['sample_listener_id_1'] + # NOTE: parse_haproxy_file makes mode TERMINATED_HTTPS even though + # the haproxy.cfg needs mode HTTP self.assertEqual('TERMINATED_HTTPS', listener_dict['mode']) self.assertEqual('/var/lib/octavia/sample_loadbalancer_id_1.sock', res[0]) - self.assertEqual( - '/var/lib/octavia/certs/sample_loadbalancer_id_1/' - 'tls_container_id.pem crt /var/lib/octavia/certs/' - 'sample_loadbalancer_id_1', - listener_dict['ssl_crt']) + self.assertEqual(FAKE_CRT_LIST_FILENAME, listener_dict['ssl_crt']) # render_template_tls_no_sni rendered_obj = self.jinja_cfg.render_loadbalancer_obj( sample_configs_combined.sample_amphora_tuple(), [sample_configs_combined.sample_listener_tuple( - proto='TERMINATED_HTTPS', tls=True)], - tls_certs={'cont_id_1': - sample_configs_combined.sample_tls_container_tuple( - id='tls_container_id', - certificate='ImAalsdkfjCert', - private_key='ImAsdlfksdjPrivateKey', - primary_cn="FakeCN")}) - + proto='TERMINATED_HTTPS', tls=True)]) self.useFixture(test_utils.OpenFixture(path, rendered_obj)) res = util.parse_haproxy_file(LISTENER_ID1) @@ -248,9 +237,7 @@ class TestUtil(base.TestCase): self.assertEqual('TERMINATED_HTTPS', listener_dict['mode']) self.assertEqual(BASE_AMP_PATH + '/sample_loadbalancer_id_1.sock', res[0]) - self.assertEqual( - BASE_CRT_PATH + '/sample_loadbalancer_id_1/tls_container_id.pem', - listener_dict['ssl_crt']) + self.assertEqual(FAKE_CRT_LIST_FILENAME, listener_dict['ssl_crt']) # render_template_http rendered_obj = self.jinja_cfg.render_loadbalancer_obj( diff --git a/octavia/tests/unit/amphorae/drivers/haproxy/test_rest_api_driver_0_5.py b/octavia/tests/unit/amphorae/drivers/haproxy/test_rest_api_driver_0_5.py index 8ccca76cce..f0e5e7e818 100644 --- a/octavia/tests/unit/amphorae/drivers/haproxy/test_rest_api_driver_0_5.py +++ b/octavia/tests/unit/amphorae/drivers/haproxy/test_rest_api_driver_0_5.py @@ -275,7 +275,7 @@ class TestHaproxyAmphoraLoadBalancerDriverTest(base.TestCase): 'sni_certs': sconts } self.driver.clients[API_VERSION].get_cert_md5sum.side_effect = [ - exc.NotFound, 'Fake_MD5', 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'] + exc.NotFound, 'Fake_MD5', 'aaaaa', 'aaaaaaaa'] self.driver._process_tls_certificates( sample_listener, self.amp, sample_listener.load_balancer.id) gcm_calls = [ @@ -309,7 +309,7 @@ class TestHaproxyAmphoraLoadBalancerDriverTest(base.TestCase): self.driver.clients[API_VERSION].upload_cert_pem.assert_has_calls( ucp_calls, any_order=True) self.assertEqual( - 3, self.driver.clients[API_VERSION].upload_cert_pem.call_count) + 4, self.driver.clients[API_VERSION].upload_cert_pem.call_count) @mock.patch('oslo_context.context.RequestContext') @mock.patch('octavia.amphorae.drivers.haproxy.rest_api_driver.' diff --git a/octavia/tests/unit/amphorae/drivers/haproxy/test_rest_api_driver_1_0.py b/octavia/tests/unit/amphorae/drivers/haproxy/test_rest_api_driver_1_0.py index 688440e4e7..22ccd22233 100644 --- a/octavia/tests/unit/amphorae/drivers/haproxy/test_rest_api_driver_1_0.py +++ b/octavia/tests/unit/amphorae/drivers/haproxy/test_rest_api_driver_1_0.py @@ -275,7 +275,7 @@ class TestHaproxyAmphoraLoadBalancerDriverTest(base.TestCase): 'sni_certs': sconts } self.driver.clients[API_VERSION].get_cert_md5sum.side_effect = [ - exc.NotFound, 'Fake_MD5', 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'] + exc.NotFound, 'Fake_MD5', 'aaaaa', 'aaaaa'] self.driver._process_tls_certificates( sample_listener, self.amp, sample_listener.load_balancer.id) gcm_calls = [ @@ -309,7 +309,7 @@ class TestHaproxyAmphoraLoadBalancerDriverTest(base.TestCase): self.driver.clients[API_VERSION].upload_cert_pem.assert_has_calls( ucp_calls, any_order=True) self.assertEqual( - 3, self.driver.clients[API_VERSION].upload_cert_pem.call_count) + 4, self.driver.clients[API_VERSION].upload_cert_pem.call_count) @mock.patch('oslo_context.context.RequestContext') @mock.patch('octavia.amphorae.drivers.haproxy.rest_api_driver.' diff --git a/octavia/tests/unit/common/jinja/haproxy/combined_listeners/test_jinja_cfg.py b/octavia/tests/unit/common/jinja/haproxy/combined_listeners/test_jinja_cfg.py index 70e11404b2..8906fec50b 100644 --- a/octavia/tests/unit/common/jinja/haproxy/combined_listeners/test_jinja_cfg.py +++ b/octavia/tests/unit/common/jinja/haproxy/combined_listeners/test_jinja_cfg.py @@ -16,11 +16,16 @@ import copy import os +from oslo_config import cfg +from oslo_config import fixture as oslo_fixture + from octavia.common import constants from octavia.common.jinja.haproxy.combined_listeners import jinja_cfg from octavia.tests.unit import base from octavia.tests.unit.common.sample_configs import sample_configs_combined +CONF = cfg.CONF + class TestHaproxyCfg(base.TestCase): def setUp(self): @@ -34,20 +39,24 @@ class TestHaproxyCfg(base.TestCase): self.assertEqual('haproxy.cfg.j2', template.name) def test_render_template_tls(self): + conf = oslo_fixture.Config(cfg.CONF) + conf.config(group="haproxy_amphora", base_cert_dir='/fake_cert_dir') + FAKE_CRT_LIST_FILENAME = os.path.join( + CONF.haproxy_amphora.base_cert_dir, + 'sample_loadbalancer_id_1/sample_listener_id_1.pem') fe = ("frontend sample_listener_id_1\n" " maxconn {maxconn}\n" " redirect scheme https if !{{ ssl_fc }}\n" " bind 10.0.0.2:443 " - "ssl crt /var/lib/octavia/certs/" - "sample_loadbalancer_id_1/tls_container_id.pem " - "crt /var/lib/octavia/certs/sample_loadbalancer_id_1 " + "ssl crt-list {crt_list} " "ca-file /var/lib/octavia/certs/sample_loadbalancer_id_1/" "client_ca.pem verify required crl-file /var/lib/octavia/" "certs/sample_loadbalancer_id_1/SHA_ID.pem\n" " mode http\n" " default_backend sample_pool_id_1:sample_listener_id_1\n" " timeout client 50000\n").format( - maxconn=constants.HAPROXY_MAX_MAXCONN) + maxconn=constants.HAPROXY_MAX_MAXCONN, + crt_list=FAKE_CRT_LIST_FILENAME) be = ("backend sample_pool_id_1:sample_listener_id_1\n" " mode http\n" " balance roundrobin\n" @@ -66,34 +75,32 @@ class TestHaproxyCfg(base.TestCase): "weight 13 check inter 30s fall 3 rise 2 cookie " "sample_member_id_2\n\n").format( maxconn=constants.HAPROXY_MAX_MAXCONN) - tls_tupe = {'cont_id_1': - sample_configs_combined.sample_tls_container_tuple( - id='tls_container_id', - certificate='imaCert1', private_key='imaPrivateKey1', - primary_cn='FakeCN')} rendered_obj = self.jinja_cfg.render_loadbalancer_obj( sample_configs_combined.sample_amphora_tuple(), [sample_configs_combined.sample_listener_tuple( proto='TERMINATED_HTTPS', tls=True, sni=True, client_ca_cert=True, client_crl_cert=True)], - tls_tupe, client_ca_filename='client_ca.pem', - client_crl='SHA_ID.pem') + client_ca_filename='client_ca.pem', client_crl='SHA_ID.pem') self.assertEqual( sample_configs_combined.sample_base_expected_config( frontend=fe, backend=be), rendered_obj) def test_render_template_tls_no_sni(self): + conf = oslo_fixture.Config(cfg.CONF) + conf.config(group="haproxy_amphora", base_cert_dir='/fake_cert_dir') + FAKE_CRT_LIST_FILENAME = os.path.join( + CONF.haproxy_amphora.base_cert_dir, + 'sample_loadbalancer_id_1/sample_listener_id_1.pem') fe = ("frontend sample_listener_id_1\n" " maxconn {maxconn}\n" " redirect scheme https if !{{ ssl_fc }}\n" - " bind 10.0.0.2:443 " - "ssl crt /var/lib/octavia/certs/" - "sample_loadbalancer_id_1/tls_container_id.pem\n" + " bind 10.0.0.2:443 ssl crt-list {crt_list}\n" " mode http\n" " default_backend sample_pool_id_1:sample_listener_id_1\n" " timeout client 50000\n").format( - maxconn=constants.HAPROXY_MAX_MAXCONN) + maxconn=constants.HAPROXY_MAX_MAXCONN, + crt_list=FAKE_CRT_LIST_FILENAME) be = ("backend sample_pool_id_1:sample_listener_id_1\n" " mode http\n" " balance roundrobin\n" @@ -115,13 +122,7 @@ class TestHaproxyCfg(base.TestCase): rendered_obj = self.jinja_cfg.render_loadbalancer_obj( sample_configs_combined.sample_amphora_tuple(), [sample_configs_combined.sample_listener_tuple( - proto='TERMINATED_HTTPS', tls=True)], - tls_certs={'cont_id_1': - sample_configs_combined.sample_tls_container_tuple( - id='tls_container_id', - certificate='ImAalsdkfjCert', - private_key='ImAsdlfksdjPrivateKey', - primary_cn="FakeCN")}) + proto='TERMINATED_HTTPS', tls=True)]) self.assertEqual( sample_configs_combined.sample_base_expected_config( frontend=fe, backend=be), @@ -922,13 +923,13 @@ class TestHaproxyCfg(base.TestCase): def test_transform_listener(self): in_listener = sample_configs_combined.sample_listener_tuple() - ret = self.jinja_cfg._transform_listener(in_listener, None, {}, + ret = self.jinja_cfg._transform_listener(in_listener, {}, in_listener.load_balancer) self.assertEqual(sample_configs_combined.RET_LISTENER, ret) def test_transform_listener_with_l7(self): in_listener = sample_configs_combined.sample_listener_tuple(l7=True) - ret = self.jinja_cfg._transform_listener(in_listener, None, {}, + ret = self.jinja_cfg._transform_listener(in_listener, {}, in_listener.load_balancer) self.assertEqual(sample_configs_combined.RET_LISTENER_L7, ret) @@ -936,7 +937,7 @@ class TestHaproxyCfg(base.TestCase): in_amphora = sample_configs_combined.sample_amphora_tuple() in_listener = sample_configs_combined.sample_listener_tuple() ret = self.jinja_cfg._transform_loadbalancer( - in_amphora, in_listener.load_balancer, [in_listener], None, {}) + in_amphora, in_listener.load_balancer, [in_listener], {}) self.assertEqual(sample_configs_combined.RET_LB, ret) def test_transform_amphora(self): @@ -948,7 +949,7 @@ class TestHaproxyCfg(base.TestCase): in_amphora = sample_configs_combined.sample_amphora_tuple() in_listener = sample_configs_combined.sample_listener_tuple(l7=True) ret = self.jinja_cfg._transform_loadbalancer( - in_amphora, in_listener.load_balancer, [in_listener], None, {}) + in_amphora, in_listener.load_balancer, [in_listener], {}) self.assertEqual(sample_configs_combined.RET_LB_L7, ret) def test_transform_l7policy(self): @@ -1066,7 +1067,6 @@ class TestHaproxyCfg(base.TestCase): rendered_obj = j_cfg.build_config( sample_amphora, [sample_proxy_listener], - tls_certs=None, haproxy_versions=("1", "8", "1")) self.assertEqual( sample_configs_combined.sample_base_expected_config(backend=be), @@ -1094,7 +1094,6 @@ class TestHaproxyCfg(base.TestCase): rendered_obj = j_cfg.build_config( sample_amphora, [sample_proxy_listener], - tls_certs=None, haproxy_versions=("1", "5", "18")) self.assertEqual( sample_configs_combined.sample_base_expected_config(backend=be), @@ -1176,7 +1175,6 @@ class TestHaproxyCfg(base.TestCase): rendered_obj = j_cfg.build_config( sample_configs_combined.sample_amphora_tuple(), [sample_listener], - tls_certs=None, haproxy_versions=("1", "5", "18")) self.assertEqual( sample_configs_combined.sample_base_expected_config( diff --git a/octavia/tests/unit/common/jinja/haproxy/split_listeners/test_jinja_cfg.py b/octavia/tests/unit/common/jinja/haproxy/split_listeners/test_jinja_cfg.py index 18a6246adc..8c198d88e9 100644 --- a/octavia/tests/unit/common/jinja/haproxy/split_listeners/test_jinja_cfg.py +++ b/octavia/tests/unit/common/jinja/haproxy/split_listeners/test_jinja_cfg.py @@ -16,11 +16,16 @@ import copy import os +from oslo_config import cfg +from oslo_config import fixture as oslo_fixture + from octavia.common import constants from octavia.common.jinja.haproxy.split_listeners import jinja_cfg from octavia.tests.unit import base from octavia.tests.unit.common.sample_configs import sample_configs_split +CONF = cfg.CONF + class TestHaproxyCfg(base.TestCase): def setUp(self): @@ -34,20 +39,24 @@ class TestHaproxyCfg(base.TestCase): self.assertEqual('haproxy.cfg.j2', template.name) def test_render_template_tls(self): + conf = oslo_fixture.Config(cfg.CONF) + conf.config(group="haproxy_amphora", base_cert_dir='/fake_cert_dir') + FAKE_CRT_LIST_FILENAME = os.path.join( + CONF.haproxy_amphora.base_cert_dir, + 'sample_listener_id_1/sample_listener_id_1.pem') fe = ("frontend sample_listener_id_1\n" " maxconn {maxconn}\n" " redirect scheme https if !{{ ssl_fc }}\n" " bind 10.0.0.2:443 " - "ssl crt /var/lib/octavia/certs/" - "sample_listener_id_1/tls_container_id.pem " - "crt /var/lib/octavia/certs/sample_listener_id_1 " + "ssl crt-list {crt_list} " "ca-file /var/lib/octavia/certs/sample_listener_id_1/" "client_ca.pem verify required crl-file /var/lib/octavia/" "certs/sample_listener_id_1/SHA_ID.pem\n" " mode http\n" " default_backend sample_pool_id_1\n" " timeout client 50000\n").format( - maxconn=constants.HAPROXY_MAX_MAXCONN) + maxconn=constants.HAPROXY_MAX_MAXCONN, + crt_list=FAKE_CRT_LIST_FILENAME) be = ("backend sample_pool_id_1\n" " mode http\n" " balance roundrobin\n" @@ -66,16 +75,12 @@ class TestHaproxyCfg(base.TestCase): "weight 13 check inter 30s fall 3 rise 2 cookie " "sample_member_id_2\n\n").format( maxconn=constants.HAPROXY_MAX_MAXCONN) - tls_tupe = sample_configs_split.sample_tls_container_tuple( - id='tls_container_id', - certificate='imaCert1', private_key='imaPrivateKey1', - primary_cn='FakeCN') rendered_obj = self.jinja_cfg.render_loadbalancer_obj( sample_configs_split.sample_amphora_tuple(), sample_configs_split.sample_listener_tuple( proto='TERMINATED_HTTPS', tls=True, sni=True, client_ca_cert=True, client_crl_cert=True), - tls_tupe, client_ca_filename='client_ca.pem', + client_ca_filename='client_ca.pem', client_crl='SHA_ID.pem') self.assertEqual( sample_configs_split.sample_base_expected_config( @@ -83,16 +88,21 @@ class TestHaproxyCfg(base.TestCase): rendered_obj) def test_render_template_tls_no_sni(self): + conf = oslo_fixture.Config(cfg.CONF) + conf.config(group="haproxy_amphora", base_cert_dir='/fake_cert_dir') + FAKE_CRT_LIST_FILENAME = os.path.join( + CONF.haproxy_amphora.base_cert_dir, + 'sample_listener_id_1/sample_listener_id_1.pem') fe = ("frontend sample_listener_id_1\n" " maxconn {maxconn}\n" " redirect scheme https if !{{ ssl_fc }}\n" " bind 10.0.0.2:443 " - "ssl crt /var/lib/octavia/certs/" - "sample_listener_id_1/tls_container_id.pem\n" + "ssl crt-list {crt_list}\n" " mode http\n" " default_backend sample_pool_id_1\n" " timeout client 50000\n").format( - maxconn=constants.HAPROXY_MAX_MAXCONN) + maxconn=constants.HAPROXY_MAX_MAXCONN, + crt_list=FAKE_CRT_LIST_FILENAME) be = ("backend sample_pool_id_1\n" " mode http\n" " balance roundrobin\n" @@ -114,12 +124,7 @@ class TestHaproxyCfg(base.TestCase): rendered_obj = self.jinja_cfg.render_loadbalancer_obj( sample_configs_split.sample_amphora_tuple(), sample_configs_split.sample_listener_tuple( - proto='TERMINATED_HTTPS', tls=True), - tls_cert=sample_configs_split.sample_tls_container_tuple( - id='tls_container_id', - certificate='ImAalsdkfjCert', - private_key='ImAsdlfksdjPrivateKey', - primary_cn="FakeCN")) + proto='TERMINATED_HTTPS', tls=True)) self.assertEqual( sample_configs_split.sample_base_expected_config( frontend=fe, backend=be), @@ -913,13 +918,13 @@ class TestHaproxyCfg(base.TestCase): def test_transform_listener(self): in_listener = sample_configs_split.sample_listener_tuple() - ret = self.jinja_cfg._transform_listener(in_listener, None, {}, + ret = self.jinja_cfg._transform_listener(in_listener, {}, in_listener.load_balancer) self.assertEqual(sample_configs_split.RET_LISTENER, ret) def test_transform_listener_with_l7(self): in_listener = sample_configs_split.sample_listener_tuple(l7=True) - ret = self.jinja_cfg._transform_listener(in_listener, None, {}, + ret = self.jinja_cfg._transform_listener(in_listener, {}, in_listener.load_balancer) self.assertEqual(sample_configs_split.RET_LISTENER_L7, ret) @@ -927,7 +932,7 @@ class TestHaproxyCfg(base.TestCase): in_amphora = sample_configs_split.sample_amphora_tuple() in_listener = sample_configs_split.sample_listener_tuple() ret = self.jinja_cfg._transform_loadbalancer( - in_amphora, in_listener.load_balancer, in_listener, None, {}) + in_amphora, in_listener.load_balancer, in_listener, {}) self.assertEqual(sample_configs_split.RET_LB, ret) def test_transform_amphora(self): @@ -939,7 +944,7 @@ class TestHaproxyCfg(base.TestCase): in_amphora = sample_configs_split.sample_amphora_tuple() in_listener = sample_configs_split.sample_listener_tuple(l7=True) ret = self.jinja_cfg._transform_loadbalancer( - in_amphora, in_listener.load_balancer, in_listener, None, {}) + in_amphora, in_listener.load_balancer, in_listener, {}) self.assertEqual(sample_configs_split.RET_LB_L7, ret) def test_transform_l7policy(self): @@ -1052,7 +1057,6 @@ class TestHaproxyCfg(base.TestCase): rendered_obj = j_cfg.build_config( sample_configs_split.sample_amphora_tuple(), sample_configs_split.sample_listener_tuple(be_proto='PROXY'), - tls_cert=None, haproxy_versions=("1", "8", "1")) self.assertEqual( sample_configs_split.sample_base_expected_config(backend=be), @@ -1078,7 +1082,6 @@ class TestHaproxyCfg(base.TestCase): rendered_obj = j_cfg.build_config( sample_configs_split.sample_amphora_tuple(), sample_configs_split.sample_listener_tuple(be_proto='PROXY'), - tls_cert=None, haproxy_versions=("1", "5", "18")) self.assertEqual( sample_configs_split.sample_base_expected_config(backend=be), @@ -1159,7 +1162,6 @@ class TestHaproxyCfg(base.TestCase): rendered_obj = j_cfg.build_config( sample_configs_split.sample_amphora_tuple(), sample_listener, - tls_cert=None, haproxy_versions=("1", "5", "18")) self.assertEqual( sample_configs_split.sample_base_expected_config( diff --git a/releasenotes/notes/fix-SNI-single-process-879ffce5eaa6c1c3.yaml b/releasenotes/notes/fix-SNI-single-process-879ffce5eaa6c1c3.yaml new file mode 100644 index 0000000000..c41d4f968c --- /dev/null +++ b/releasenotes/notes/fix-SNI-single-process-879ffce5eaa6c1c3.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Fixes an issue where load balancers with more than one TLS enabled + listener, one or more SNI enabled, may load certificates from + other TLS enabled listeners for SNI use.