Fix listeners with SNI certificates

The single process patch changed the way listeners and load balancers
are deployed inside the amphora. This caused listeners with SNI
enabled to load all of the certificates for all of the TLS enabled
listeners on a load balancer.
This patch corrects that by configuring each listener with a
specific list of certificates.

Change-Id: I2f3c7ab4137dbd84d77a6a6b675975af406249d0
Story: 2006758
Task: 37252
(cherry picked from commit 3c05ce1297)
This commit is contained in:
Michael Johnson 2019-10-22 16:15:45 -07:00
parent ce22677502
commit cf1703beb8
12 changed files with 130 additions and 160 deletions

View File

@ -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+)')

View File

@ -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):

View File

@ -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,

View File

@ -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 %}

View File

@ -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,

View File

@ -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 %}

View File

@ -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(

View File

@ -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.'

View File

@ -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.'

View File

@ -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(

View File

@ -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(

View File

@ -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.