Merge "Fix listener update with SNI certificates" into stable/stein
This commit is contained in:
commit
9b69f16f6f
|
@ -201,12 +201,6 @@ def listener_dict_to_provider_dict(listener_dict, for_delete=False):
|
||||||
if 'tls_certificate_id' in new_listener_dict:
|
if 'tls_certificate_id' in new_listener_dict:
|
||||||
new_listener_dict['default_tls_container_ref'] = new_listener_dict.pop(
|
new_listener_dict['default_tls_container_ref'] = new_listener_dict.pop(
|
||||||
'tls_certificate_id')
|
'tls_certificate_id')
|
||||||
if 'sni_containers' in new_listener_dict:
|
|
||||||
new_listener_dict['sni_container_refs'] = new_listener_dict.pop(
|
|
||||||
'sni_containers')
|
|
||||||
if 'sni_container_refs' in listener_dict:
|
|
||||||
listener_dict['sni_containers'] = listener_dict.pop(
|
|
||||||
'sni_container_refs')
|
|
||||||
if 'client_ca_tls_certificate_id' in new_listener_dict:
|
if 'client_ca_tls_certificate_id' in new_listener_dict:
|
||||||
new_listener_dict['client_ca_tls_container_ref'] = (
|
new_listener_dict['client_ca_tls_container_ref'] = (
|
||||||
new_listener_dict.pop('client_ca_tls_certificate_id'))
|
new_listener_dict.pop('client_ca_tls_certificate_id'))
|
||||||
|
@ -261,6 +255,20 @@ def listener_dict_to_provider_dict(listener_dict, for_delete=False):
|
||||||
listener_obj.client_crl_container_id)
|
listener_obj.client_crl_container_id)
|
||||||
new_listener_dict['client_crl_container_data'] = crl_file
|
new_listener_dict['client_crl_container_data'] = crl_file
|
||||||
|
|
||||||
|
# Format the sni_containers -> sni_container_refs
|
||||||
|
sni_containers = new_listener_dict.pop('sni_containers', None)
|
||||||
|
if sni_containers:
|
||||||
|
new_listener_dict['sni_container_refs'] = []
|
||||||
|
for sni in sni_containers:
|
||||||
|
if isinstance(sni, dict):
|
||||||
|
new_listener_dict['sni_container_refs'].append(
|
||||||
|
sni['tls_container_id'])
|
||||||
|
elif isinstance(sni, str):
|
||||||
|
new_listener_dict['sni_container_refs'].append(sni)
|
||||||
|
else:
|
||||||
|
raise exceptions.ValidationException(
|
||||||
|
detail=_('Invalid SNI container on listener'))
|
||||||
|
|
||||||
# Remove the DB back references
|
# Remove the DB back references
|
||||||
if 'load_balancer' in new_listener_dict:
|
if 'load_balancer' in new_listener_dict:
|
||||||
del new_listener_dict['load_balancer']
|
del new_listener_dict['load_balancer']
|
||||||
|
|
|
@ -28,6 +28,7 @@ class BaseListenerType(types.BaseType):
|
||||||
_type_to_model_map = {
|
_type_to_model_map = {
|
||||||
'admin_state_up': 'enabled',
|
'admin_state_up': 'enabled',
|
||||||
'default_tls_container_ref': 'tls_certificate_id',
|
'default_tls_container_ref': 'tls_certificate_id',
|
||||||
|
'sni_container_refs': 'sni_containers',
|
||||||
'client_ca_tls_container_ref': 'client_ca_tls_certificate_id',
|
'client_ca_tls_container_ref': 'client_ca_tls_certificate_id',
|
||||||
'client_crl_container_ref': 'client_crl_container_id'}
|
'client_crl_container_ref': 'client_crl_container_id'}
|
||||||
_child_map = {}
|
_child_map = {}
|
||||||
|
|
|
@ -530,6 +530,14 @@ class SNI(BaseDataModel):
|
||||||
self.listener = listener
|
self.listener = listener
|
||||||
self.tls_container_id = tls_container_id
|
self.tls_container_id = tls_container_id
|
||||||
|
|
||||||
|
# SQLAlchemy kindly attaches the whole listener object so
|
||||||
|
# let's keep this simple by overriding the to_dict for this
|
||||||
|
# object. Otherwise we recurse down the "ghost" listener object.
|
||||||
|
def to_dict(self, **kwargs):
|
||||||
|
return {'tls_container_id': self.tls_container_id,
|
||||||
|
'listener_id': self.listener_id,
|
||||||
|
'position': self.position}
|
||||||
|
|
||||||
|
|
||||||
class TLSContainer(BaseDataModel):
|
class TLSContainer(BaseDataModel):
|
||||||
|
|
||||||
|
|
|
@ -491,8 +491,8 @@ class Listener(base_models.BASE, base_models.IdMixin,
|
||||||
default_pool = orm.relationship("Pool", uselist=False,
|
default_pool = orm.relationship("Pool", uselist=False,
|
||||||
back_populates="_default_listeners")
|
back_populates="_default_listeners")
|
||||||
sni_containers = orm.relationship(
|
sni_containers = orm.relationship(
|
||||||
'SNI', cascade='delete', uselist=True,
|
'SNI', cascade='all,delete-orphan',
|
||||||
backref=orm.backref('listener', uselist=False))
|
uselist=True, backref=orm.backref('listener', uselist=False))
|
||||||
|
|
||||||
l7policies = orm.relationship(
|
l7policies = orm.relationship(
|
||||||
'L7Policy', uselist=True, order_by='L7Policy.position',
|
'L7Policy', uselist=True, order_by='L7Policy.position',
|
||||||
|
|
|
@ -1050,11 +1050,16 @@ class ListenerRepository(BaseRepository):
|
||||||
default_pool_id = model_kwargs.get('default_pool_id')
|
default_pool_id = model_kwargs.get('default_pool_id')
|
||||||
if default_pool_id:
|
if default_pool_id:
|
||||||
self._pool_check(session, default_pool_id, listener_id=id)
|
self._pool_check(session, default_pool_id, listener_id=id)
|
||||||
sni_containers = model_kwargs.pop('sni_containers', [])
|
if 'sni_containers' in model_kwargs:
|
||||||
for container_ref in sni_containers:
|
# sni_container_refs is being updated. It is either being set
|
||||||
sni = models.SNI(listener_id=id,
|
# or unset/cleared. We need to update in DB side.
|
||||||
tls_certificate_id=container_ref)
|
containers = model_kwargs.pop('sni_containers', []) or []
|
||||||
listener_db.sni_containers.append(sni)
|
listener_db.sni_containers = []
|
||||||
|
if containers:
|
||||||
|
listener_db.sni_containers = [
|
||||||
|
models.SNI(listener_id=id,
|
||||||
|
tls_container_id=container_ref)
|
||||||
|
for container_ref in containers]
|
||||||
listener_db.update(model_kwargs)
|
listener_db.update(model_kwargs)
|
||||||
|
|
||||||
def create(self, session, **model_kwargs):
|
def create(self, session, **model_kwargs):
|
||||||
|
|
|
@ -2196,7 +2196,29 @@ class TestListener(base.BaseAPITest):
|
||||||
|
|
||||||
# TODO(johnsom) Fix this when there is a noop certificate manager
|
# TODO(johnsom) Fix this when there is a noop certificate manager
|
||||||
@mock.patch('octavia.common.tls_utils.cert_parser.load_certificates_data')
|
@mock.patch('octavia.common.tls_utils.cert_parser.load_certificates_data')
|
||||||
def test_update_with_sni_data(self, mock_cert_data):
|
def test_update_tls_terminated_with_sni_data(self, mock_cert_data):
|
||||||
|
cert2 = data_models.TLSContainer(certificate='cert 2')
|
||||||
|
cert3 = data_models.TLSContainer(certificate='cert 3')
|
||||||
|
mock_cert_data.return_value = {'sni_certs': [cert2, cert3]}
|
||||||
|
sni_id1 = uuidutils.generate_uuid()
|
||||||
|
sni_id2 = uuidutils.generate_uuid()
|
||||||
|
listener = self.create_listener(
|
||||||
|
constants.PROTOCOL_TERMINATED_HTTPS, 80, self.lb_id,
|
||||||
|
default_tls_container_ref=uuidutils.generate_uuid())
|
||||||
|
self.set_lb_status(self.lb_id)
|
||||||
|
listener_path = self.LISTENER_PATH.format(
|
||||||
|
listener_id=listener['listener']['id'])
|
||||||
|
get_listener = self.get(listener_path).json['listener']
|
||||||
|
self.assertEqual([], get_listener.get('sni_container_refs'))
|
||||||
|
self.put(listener_path,
|
||||||
|
self._build_body({'sni_container_refs': [sni_id1, sni_id2]}))
|
||||||
|
get_listener = self.get(listener_path).json['listener']
|
||||||
|
self.assertItemsEqual([sni_id1, sni_id2],
|
||||||
|
get_listener.get('sni_container_refs'))
|
||||||
|
|
||||||
|
# TODO(johnsom) Fix this when there is a noop certificate manager
|
||||||
|
@mock.patch('octavia.common.tls_utils.cert_parser.load_certificates_data')
|
||||||
|
def test_update_non_tls_terminated_with_sni_data(self, mock_cert_data):
|
||||||
cert2 = data_models.TLSContainer(certificate='cert 2')
|
cert2 = data_models.TLSContainer(certificate='cert 2')
|
||||||
cert3 = data_models.TLSContainer(certificate='cert 3')
|
cert3 = data_models.TLSContainer(certificate='cert 3')
|
||||||
mock_cert_data.return_value = {'sni_certs': [cert2, cert3]}
|
mock_cert_data.return_value = {'sni_certs': [cert2, cert3]}
|
||||||
|
@ -2209,8 +2231,12 @@ class TestListener(base.BaseAPITest):
|
||||||
listener_id=listener['listener']['id'])
|
listener_id=listener['listener']['id'])
|
||||||
get_listener = self.get(listener_path).json['listener']
|
get_listener = self.get(listener_path).json['listener']
|
||||||
self.assertEqual([], get_listener.get('sni_container_refs'))
|
self.assertEqual([], get_listener.get('sni_container_refs'))
|
||||||
self.put(listener_path,
|
body = self._build_body({'sni_container_refs': [sni_id1, sni_id2]})
|
||||||
self._build_body({'sni_container_refs': [sni_id1, sni_id2]}))
|
response = self.put(listener_path, body, status=400).json
|
||||||
|
self.assertEqual(
|
||||||
|
"Validation failure: Certificate container references are only "
|
||||||
|
"allowed on TERMINATED_HTTPS protocol listeners.",
|
||||||
|
response['faultstring'])
|
||||||
get_listener = self.get(listener_path).json['listener']
|
get_listener = self.get(listener_path).json['listener']
|
||||||
self.assertEqual([], get_listener.get('sni_container_refs'))
|
self.assertEqual([], get_listener.get('sni_container_refs'))
|
||||||
|
|
||||||
|
|
|
@ -2363,6 +2363,16 @@ class TestListenerRepositoryTest(BaseRepositoryTest):
|
||||||
new_listener = self.listener_repo.get(self.session, id=listener.id)
|
new_listener = self.listener_repo.get(self.session, id=listener.id)
|
||||||
self.assertEqual(name_change, new_listener.name)
|
self.assertEqual(name_change, new_listener.name)
|
||||||
|
|
||||||
|
def test_update_with_sni(self):
|
||||||
|
listener = self.create_listener(self.FAKE_UUID_1, 80)
|
||||||
|
container1 = {'listener_id': listener.id,
|
||||||
|
'tls_container_id': self.FAKE_UUID_2}
|
||||||
|
container1_dm = models.SNI(**container1)
|
||||||
|
self.listener_repo.update(self.session, listener.id,
|
||||||
|
sni_containers=[self.FAKE_UUID_2])
|
||||||
|
new_listener = self.listener_repo.get(self.session, id=listener.id)
|
||||||
|
self.assertIn(container1_dm, new_listener.sni_containers)
|
||||||
|
|
||||||
def test_delete(self):
|
def test_delete(self):
|
||||||
listener = self.create_listener(self.FAKE_UUID_1, 80)
|
listener = self.create_listener(self.FAKE_UUID_1, 80)
|
||||||
self.listener_repo.delete(self.session, id=listener.id)
|
self.listener_repo.delete(self.session, id=listener.id)
|
||||||
|
|
|
@ -291,7 +291,7 @@ class TestUtils(base.TestCase):
|
||||||
'sni_certs': [cert2, cert3]}
|
'sni_certs': [cert2, cert3]}
|
||||||
# Test with bad SNI content
|
# Test with bad SNI content
|
||||||
test_listener = copy.deepcopy(self.sample_data.test_listener1_dict)
|
test_listener = copy.deepcopy(self.sample_data.test_listener1_dict)
|
||||||
test_listener['sni_containers'] = [[]]
|
test_listener['sni_containers'] = [()]
|
||||||
self.assertRaises(exceptions.ValidationException,
|
self.assertRaises(exceptions.ValidationException,
|
||||||
utils.listener_dict_to_provider_dict,
|
utils.listener_dict_to_provider_dict,
|
||||||
test_listener)
|
test_listener)
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
---
|
||||||
|
fixes:
|
||||||
|
- |
|
||||||
|
Fixed an issue where setting of SNI containers were not being applied on
|
||||||
|
listener update API calls.
|
||||||
|
- |
|
||||||
|
Fixed an Octavia API validation on listener update where SNI containers
|
||||||
|
could be set on non-TERMINATED_HTTPS listeners.
|
Loading…
Reference in New Issue