Merge "Add crl-file option for certification"

This commit is contained in:
Zuul 2019-02-26 11:07:29 +00:00 committed by Gerrit Code Review
commit f9bb294206
29 changed files with 543 additions and 74 deletions

View File

@ -282,6 +282,24 @@ client_ca_tls_container_ref-optional:
min_version: 2.8
required: false
type: string
client_crl_container_ref:
description: |
The URI of the `key manager service
<https://docs.openstack.org/castellan/latest/>`__ secret containing a
PEM format CA revocation list file for ``TERMINATED_TLS`` listeners.
in: body
min_version: 2.8
required: true
type: string
client_crl_container_ref-optional:
description: |
The URI of the `key manager service
<https://docs.openstack.org/castellan/latest/>`__ secret containing a
PEM format CA revocation list file for ``TERMINATED_TLS`` listeners.
in: body
min_version: 2.8
required: false
type: string
compute-flavor:
description: |
The ID of the compute flavor used for the amphora.

View File

@ -1 +1 @@
curl -X POST -H "Content-Type: application/json" -H "X-Auth-Token: <token>" -d '{"listener": {"protocol": "TERMINATED_HTTPS", "description": "A great TLS listener", "admin_state_up": true, "connection_limit": 200, "protocol_port": "443", "loadbalancer_id": "607226db-27ef-4d41-ae89-f2a800e9c2db", "name": "great_tls_listener", "insert_headers": {"X-Forwarded-For": "true", "X-Forwarded-Port": "true"}, "default_tls_container_ref": "http://198.51.100.10:9311/v1/containers/a570068c-d295-4780-91d4-3046a325db51", "sni_container_refs": ["http://198.51.100.10:9311/v1/containers/a570068c-d295-4780-91d4-3046a325db51", "http://198.51.100.10:9311/v1/containers/aaebb31e-7761-4826-8cb4-2b829caca3ee"], "timeout_client_data": 50000, "timeout_member_connect": 5000, "timeout_member_data": 50000, "timeout_tcp_inspect": 0, "tags": ["test_tag"], "client_ca_tls_container_ref": "http://198.51.100.10:9311/v1/containers/35649991-49f3-4625-81ce-2465fe8932e5", "client_authentication": "MANDATORY"}}' http://198.51.100.10:9876/v2/lbaas/listeners
curl -X POST -H "Content-Type: application/json" -H "X-Auth-Token: <token>" -d '{"listener": {"protocol": "TERMINATED_HTTPS", "description": "A great TLS listener", "admin_state_up": true, "connection_limit": 200, "protocol_port": "443", "loadbalancer_id": "607226db-27ef-4d41-ae89-f2a800e9c2db", "name": "great_tls_listener", "insert_headers": {"X-Forwarded-For": "true", "X-Forwarded-Port": "true"}, "default_tls_container_ref": "http://198.51.100.10:9311/v1/containers/a570068c-d295-4780-91d4-3046a325db51", "sni_container_refs": ["http://198.51.100.10:9311/v1/containers/a570068c-d295-4780-91d4-3046a325db51", "http://198.51.100.10:9311/v1/containers/aaebb31e-7761-4826-8cb4-2b829caca3ee"], "timeout_client_data": 50000, "timeout_member_connect": 5000, "timeout_member_data": 50000, "timeout_tcp_inspect": 0, "tags": ["test_tag"], "client_ca_tls_container_ref": "http://198.51.100.10:9311/v1/containers/35649991-49f3-4625-81ce-2465fe8932e5", "client_authentication": "MANDATORY", "client_crl_container_ref": "http://198.51.100.10:9311/v1/containers/e222b065-b93b-4e2a-9a02-804b7a118c3c"}}' http://198.51.100.10:9876/v2/lbaas/listeners

View File

@ -22,6 +22,7 @@
"timeout_tcp_inspect": 0,
"tags": ["test_tag"],
"client_ca_tls_container_ref": "http://198.51.100.10:9311/v1/containers/35649991-49f3-4625-81ce-2465fe8932e5",
"client_authentication": "MANDATORY"
"client_authentication": "MANDATORY",
"client_crl_container_ref": "http://198.51.100.10:9311/v1/containers/e222b065-b93b-4e2a-9a02-804b7a118c3c"
}
}

View File

@ -37,6 +37,7 @@
"timeout_tcp_inspect": 0,
"tags": ["test_tag"],
"client_ca_tls_container_ref": "http://198.51.100.10:9311/v1/containers/35649991-49f3-4625-81ce-2465fe8932e5",
"client_authentication": "MANDATORY"
"client_authentication": "MANDATORY",
"client_crl_container_ref": "http://198.51.100.10:9311/v1/containers/e222b065-b93b-4e2a-9a02-804b7a118c3c"
}
}

View File

@ -37,6 +37,7 @@
"timeout_tcp_inspect": 0,
"tags": ["test_tag"],
"client_ca_tls_container_ref": "http://198.51.100.10:9311/v1/containers/35649991-49f3-4625-81ce-2465fe8932e5",
"client_authentication": "MANDATORY"
"client_authentication": "MANDATORY",
"client_crl_container_ref": "http://198.51.100.10:9311/v1/containers/e222b065-b93b-4e2a-9a02-804b7a118c3c"
}
}

View File

@ -37,6 +37,7 @@
"timeout_tcp_inspect": 5,
"tags": ["updated_tag"],
"client_ca_tls_container_ref": null,
"client_authentication": "NONE"
"client_authentication": "NONE",
"client_crl_container_ref": null
}
}

View File

@ -39,7 +39,8 @@
"timeout_tcp_inspect": 0,
"tags": ["test_tag"],
"client_ca_tls_container_ref": "http://198.51.100.10:9311/v1/containers/35649991-49f3-4625-81ce-2465fe8932e5",
"client_authentication": "NONE"
"client_authentication": "NONE",
"client_crl_container_ref": "http://198.51.100.10:9311/v1/containers/e222b065-b93b-4e2a-9a02-804b7a118c3c"
}
]
}

View File

@ -48,6 +48,7 @@ Response Parameters
- admin_state_up: admin_state_up
- client_authentication: client_authentication
- client_ca_tls_container_ref: client_ca_tls_container_ref
- client_crl_container_ref: client_crl_container_ref
- connection_limit: connection_limit
- created_at: created_at
- default_pool_id: default_pool_id
@ -140,6 +141,7 @@ Request
- admin_state_up: admin_state_up-default-optional
- client_authentication: client_authentication-optional
- client_ca_tls_container_ref: client_ca_tls_container_ref-optional
- client_crl_container_ref: client_crl_container_ref-optional
- connection_limit: connection_limit-optional
- default_pool: pool-optional
- default_pool_id: default_pool_id-optional
@ -210,6 +212,7 @@ Response Parameters
- admin_state_up: admin_state_up
- client_authentication: client_authentication
- client_ca_tls_container_ref: client_ca_tls_container_ref
- client_crl_container_ref: client_crl_container_ref
- connection_limit: connection_limit
- created_at: created_at
- default_pool_id: default_pool_id
@ -286,6 +289,7 @@ Response Parameters
- admin_state_up: admin_state_up
- client_authentication: client_authentication
- client_ca_tls_container_ref: client_ca_tls_container_ref
- client_crl_container_ref: client_crl_container_ref
- connection_limit: connection_limit
- created_at: created_at
- default_pool_id: default_pool_id
@ -352,6 +356,7 @@ Request
- admin_state_up: admin_state_up-default-optional
- client_authentication: client_authentication-optional
- client_ca_tls_container_ref: client_ca_tls_container_ref-optional
- client_crl_container_ref: client_crl_container_ref-optional
- connection_limit: connection_limit-optional
- default_pool_id: default_pool_id-optional
- default_tls_container_ref: default_tls_container_ref-optional
@ -386,6 +391,7 @@ Response Parameters
- admin_state_up: admin_state_up
- client_authentication: client_authentication
- client_ca_tls_container_ref: client_ca_tls_container_ref
- client_crl_container_ref: client_crl_container_ref
- connection_limit: connection_limit
- created_at: created_at
- default_pool_id: default_pool_id

View File

@ -373,6 +373,11 @@ contain the following:
| client_ca_tls_container_ref | string | The reference to the secrets |
| | | container. |
+------------------------------+--------+-------------------------------------+
| client_crl_container_data | string | A PEM encoded CRL file. |
+------------------------------+--------+-------------------------------------+
| client_crl_container_ref | string | The reference to the secrets |
| | | container. |
+------------------------------+--------+-------------------------------------+
| connection_limit | int | The max number of connections |
| | | permitted for this listener. Default|
| | | is -1, which is infinite |

View File

@ -118,13 +118,16 @@ class HaproxyAmphoraLoadBalancerDriver(
certs = self._process_tls_certificates(listener)
client_ca_filename = self._process_secret(
listener, listener.client_ca_tls_certificate_id)
crl_filename = self._process_secret(
listener, listener.client_crl_container_id)
# Generate HaProxy configuration from listener object
config = self.jinja.build_config(
host_amphora=amp, listener=listener,
tls_cert=certs['tls_cert'],
haproxy_versions=haproxy_versions,
client_ca_filename=client_ca_filename)
client_ca_filename=client_ca_filename,
client_crl=crl_filename)
self.client.upload_config(amp, listener.id, config,
timeout_dict=timeout_dict)
self.client.reload_listener(amp, listener.id,
@ -156,6 +159,8 @@ class HaproxyAmphoraLoadBalancerDriver(
certs = self._process_tls_certificates(listener)
client_ca_filename = self._process_secret(
listener, listener.client_ca_tls_certificate_id)
crl_filename = self._process_secret(
listener, listener.client_crl_container_id)
for amp in listener.load_balancer.amphorae:
if amp.status != consts.DELETED:
@ -167,7 +172,8 @@ class HaproxyAmphoraLoadBalancerDriver(
host_amphora=amp, listener=listener,
tls_cert=certs['tls_cert'],
haproxy_versions=haproxy_versions,
client_ca_filename=client_ca_filename)
client_ca_filename=client_ca_filename,
client_crl=crl_filename)
self.client.upload_config(amp, listener.id, config)
self.client.reload_listener(amp, listener.id)

View File

@ -135,7 +135,8 @@ class Listener(BaseDataModel):
timeout_member_connect=Unset, timeout_member_data=Unset,
timeout_tcp_inspect=Unset, client_ca_tls_container_ref=Unset,
client_ca_tls_container_data=Unset,
client_authentication=Unset):
client_authentication=Unset, client_crl_container_ref=Unset,
client_crl_container_data=Unset):
self.admin_state_up = admin_state_up
self.connection_limit = connection_limit
@ -160,6 +161,8 @@ class Listener(BaseDataModel):
self.client_ca_tls_container_ref = client_ca_tls_container_ref
self.client_ca_tls_container_data = client_ca_tls_container_data
self.client_authentication = client_authentication
self.client_crl_container_ref = client_crl_container_ref
self.client_crl_container_data = client_crl_container_data
class Pool(BaseDataModel):

View File

@ -184,7 +184,9 @@ def listener_dict_to_provider_dict(listener_dict):
if 'client_ca_tls_certificate_id' in new_listener_dict:
new_listener_dict['client_ca_tls_container_ref'] = (
new_listener_dict.pop('client_ca_tls_certificate_id'))
if 'client_crl_container_id' in new_listener_dict:
new_listener_dict['client_crl_container_ref'] = (
new_listener_dict.pop('client_crl_container_id'))
listener_obj = data_models.Listener(**listener_dict)
if (listener_obj.tls_certificate_id or listener_obj.sni_containers or
listener_obj.client_ca_tls_certificate_id):
@ -220,6 +222,10 @@ def listener_dict_to_provider_dict(listener_dict):
cert = _get_secret_data(cert_manager, listener_obj,
listener_obj.client_ca_tls_certificate_id)
new_listener_dict['client_ca_tls_container_data'] = cert
if listener_obj.client_crl_container_id:
crl_file = _get_secret_data(cert_manager, listener_obj,
listener_obj.client_crl_container_id)
new_listener_dict['client_crl_container_data'] = crl_file
# Remove the DB back references
if 'load_balancer' in new_listener_dict:

View File

@ -142,7 +142,7 @@ class ListenersController(base.BaseController):
if bad_refs:
raise exceptions.CertificateRetrievalException(ref=bad_refs)
def _validate_client_ca_ref(self, client_ca_ref):
def _validate_client_ca_and_crl_refs(self, client_ca_ref, crl_ref):
context = pecan.request.context.get('octavia_context')
bad_refs = []
try:
@ -151,23 +151,51 @@ class ListenersController(base.BaseController):
except Exception:
bad_refs.append(client_ca_ref)
# This will be used in a later patch
pem_crl = None
if crl_ref:
try:
self.cert_manager.set_acls(context, crl_ref)
pem_crl = self.cert_manager.get_secret(context, crl_ref)
except Exception:
bad_refs.append(crl_ref)
if bad_refs:
raise exceptions.CertificateRetrievalException(ref=bad_refs)
ca_cert = None
try:
# Test if it needs to be UTF-8 encoded
try:
ca_pem = ca_pem.encode('utf-8')
except AttributeError:
pass
x509.load_pem_x509_certificate(ca_pem, default_backend())
ca_cert = x509.load_pem_x509_certificate(ca_pem, default_backend())
except Exception as e:
raise exceptions.ValidationException(detail=_(
"The client authentication CA certificate is invalid. "
"It must be a valid x509 PEM format certificate. "
"Error: %s") % str(e))
# Validate the CRL is for the client CA
if pem_crl:
ca_pub_key = ca_cert.public_key()
crl = None
# Test if it needs to be UTF-8 encoded
try:
pem_crl = pem_crl.encode('utf-8')
except AttributeError:
pass
try:
crl = x509.load_pem_x509_crl(pem_crl, default_backend())
except Exception as e:
raise exceptions.ValidationException(detail=_(
"The client authentication certificate revocation list "
"is invalid. It must be a valid x509 PEM format "
"certificate revocation list. Error: %s") % str(e))
if not crl.is_signature_valid(ca_pub_key):
raise exceptions.ValidationException(detail=_(
"The CRL specified is not valid for client certificate "
"authority reference supplied."))
def _has_tls_container_refs(self, listener_dict):
return (listener_dict.get('tls_certificate_id') or
listener_dict.get('client_ca_tls_container_id') or
@ -239,15 +267,27 @@ class ListenersController(base.BaseController):
"container reference.") %
listener_dict.get('client_authentication'))
# Make sure we have a client CA if they specify a CRL
if (listener_dict.get('client_crl_container_id') and
not listener_dict.get('client_ca_tls_certificate_id')):
raise exceptions.ValidationException(detail=_(
"A client authentication CA reference is required to "
"specify a client authentication revocation list."))
# Validate the TLS containers
sni_containers = listener_dict.pop('sni_containers', [])
tls_refs = [sni['tls_container_id'] for sni in sni_containers]
if listener_dict.get('tls_certificate_id'):
tls_refs.append(listener_dict.get('tls_certificate_id'))
self._validate_tls_refs(tls_refs)
# Validate the client CA cert and optional client CRL
if listener_dict.get('client_ca_tls_certificate_id'):
self._validate_client_ca_and_crl_refs(
listener_dict.get('client_ca_tls_certificate_id'),
listener_dict.get('client_crl_container_id', None))
try:
sni_containers = listener_dict.pop('sni_containers', [])
tls_refs = [sni['tls_container_id'] for sni in sni_containers]
if listener_dict.get('tls_certificate_id'):
tls_refs.append(listener_dict.get('tls_certificate_id'))
self._validate_tls_refs(tls_refs)
if listener_dict.get('client_ca_tls_certificate_id'):
self._validate_client_ca_ref(
listener_dict.get('client_ca_tls_certificate_id'))
db_listener = self.repositories.listener.create(
lock_session, **listener_dict)
if sni_containers:
@ -406,8 +446,28 @@ class ListenersController(base.BaseController):
if listener.default_tls_container_ref:
tls_refs.append(listener.default_tls_container_ref)
self._validate_tls_refs(tls_refs)
if listener.client_ca_tls_container_ref:
self._validate_client_ca_ref(listener.client_ca_tls_container_ref)
ca_ref = None
if (listener.client_ca_tls_container_ref and
listener.client_ca_tls_container_ref != wtypes.Unset):
ca_ref = listener.client_ca_tls_container_ref
elif db_listener.client_ca_tls_certificate_id:
ca_ref = db_listener.client_ca_tls_certificate_id
crl_ref = None
if (listener.client_crl_container_ref and
listener.client_crl_container_ref != wtypes.Unset):
crl_ref = listener.client_crl_container_ref
elif db_listener.client_crl_container_id:
crl_ref = db_listener.client_crl_container_id
if crl_ref and not ca_ref:
raise exceptions.ValidationException(detail=_(
"A client authentication CA reference is required to "
"specify a client authentication revocation list."))
if ca_ref or crl_ref:
self._validate_client_ca_and_crl_refs(ca_ref, crl_ref)
@wsme_pecan.wsexpose(listener_types.ListenerRootResponse, wtypes.text,
body=listener_types.ListenerRootPUT, status_code=200)

View File

@ -28,7 +28,8 @@ class BaseListenerType(types.BaseType):
_type_to_model_map = {
'admin_state_up': 'enabled',
'default_tls_container_ref': 'tls_certificate_id',
'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'}
_child_map = {}
@ -59,6 +60,7 @@ class ListenerResponse(BaseListenerType):
tags = wtypes.wsattr(wtypes.ArrayType(wtypes.StringType()))
client_ca_tls_container_ref = wtypes.StringType()
client_authentication = wtypes.wsattr(wtypes.StringType())
client_crl_container_ref = wtypes.wsattr(wtypes.StringType())
@classmethod
def from_data_model(cls, data_model, children=False):
@ -142,6 +144,7 @@ class ListenerPOST(BaseListenerType):
client_authentication = wtypes.wsattr(
wtypes.Enum(str, *constants.SUPPORTED_CLIENT_AUTH_MODES),
default=constants.CLIENT_AUTH_NONE)
client_crl_container_ref = wtypes.StringType(max_length=255)
class ListenerRootPOST(types.BaseType):
@ -177,6 +180,7 @@ class ListenerPUT(BaseListenerType):
client_ca_tls_container_ref = wtypes.StringType(max_length=255)
client_authentication = wtypes.wsattr(
wtypes.Enum(str, *constants.SUPPORTED_CLIENT_AUTH_MODES))
client_crl_container_ref = wtypes.StringType(max_length=255)
class ListenerRootPUT(types.BaseType):
@ -224,6 +228,7 @@ class ListenerSingleCreate(BaseListenerType):
client_authentication = wtypes.wsattr(
wtypes.Enum(str, *constants.SUPPORTED_CLIENT_AUTH_MODES),
default=constants.CLIENT_AUTH_NONE)
client_crl_container_ref = wtypes.StringType(max_length=255)
class ListenerStatusResponse(BaseListenerType):

View File

@ -369,7 +369,7 @@ class Listener(BaseDataModel):
timeout_client_data=None, timeout_member_connect=None,
timeout_member_data=None, timeout_tcp_inspect=None,
tags=None, client_ca_tls_certificate_id=None,
client_authentication=None):
client_authentication=None, client_crl_container_id=None):
self.id = id
self.project_id = project_id
self.name = name
@ -400,6 +400,7 @@ class Listener(BaseDataModel):
self.tags = tags
self.client_ca_tls_certificate_id = client_ca_tls_certificate_id
self.client_authentication = client_authentication
self.client_crl_container_id = client_crl_container_id
def update(self, update_dict):
for key, value in update_dict.items():

View File

@ -83,7 +83,7 @@ class JinjaTemplater(object):
def build_config(self, host_amphora, listener, tls_cert,
haproxy_versions, socket_path=None,
client_ca_filename=None):
client_ca_filename=None, client_crl=None):
"""Convert a logical configuration to the HAProxy version
:param host_amphora: The Amphora this configuration is hosted on
@ -105,7 +105,7 @@ class JinjaTemplater(object):
return self.render_loadbalancer_obj(
host_amphora, listener, tls_cert=tls_cert, socket_path=socket_path,
feature_compatibility=feature_compatibility,
client_ca_filename=client_ca_filename)
client_ca_filename=client_ca_filename, client_crl=client_crl)
def _get_template(self):
"""Returns the specified Jinja configuration template."""
@ -124,7 +124,7 @@ class JinjaTemplater(object):
def render_loadbalancer_obj(self, host_amphora, listener,
tls_cert=None, socket_path=None,
feature_compatibility=None,
client_ca_filename=None):
client_ca_filename=None, client_crl=None):
"""Renders a templated configuration from a load balancer object
:param host_amphora: The Amphora this configuration is hosted on
@ -141,7 +141,8 @@ class JinjaTemplater(object):
listener,
tls_cert,
feature_compatibility,
client_ca_filename=client_ca_filename)
client_ca_filename=client_ca_filename,
client_crl=client_crl)
if not socket_path:
socket_path = '%s/%s.sock' % (self.base_amp_path, listener.id)
return self._get_template().render(
@ -154,14 +155,14 @@ class JinjaTemplater(object):
def _transform_loadbalancer(self, host_amphora, loadbalancer, listener,
tls_cert, feature_compatibility,
client_ca_filename=None):
client_ca_filename=None, client_crl=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,
client_ca_filename=client_ca_filename)
client_ca_filename=client_ca_filename, client_crl=client_crl)
ret_value = {
'id': loadbalancer.id,
'vip_address': loadbalancer.vip.ip_address,
@ -201,7 +202,7 @@ class JinjaTemplater(object):
}
def _transform_listener(self, listener, tls_cert, feature_compatibility,
client_ca_filename=None):
client_ca_filename=None, client_crl=None):
"""Transforms a listener into an object that will
be processed by the templating system
@ -245,6 +246,9 @@ class JinjaTemplater(object):
client_ca_filename))
ret_value['client_auth'] = CLIENT_AUTH_MAP.get(
listener.client_authentication)
if listener.client_crl_container_id:
ret_value['client_crl_path'] = '%s' % (
os.path.join(self.base_crt_dir, listener.id, client_crl))
if listener.default_pool:
ret_value['default_pool'] = self._transform_pool(

View File

@ -43,8 +43,13 @@ peers {{ "%s_peers"|format(listener.id.replace("-", ""))|trim() }}
{% else %}
{% set client_ca_opt = "" %}
{% endif %}
{% if listener.client_crl_path and listener.client_ca_tls_path %}
{% set ca_crl_opt = "crl-file %s"|format(listener.client_crl_path)|trim() %}
{% else %}
{% set ca_crl_opt = "" %}
{% endif %}
bind {{ lb_vip_address }}:{{ listener.protocol_port }} {{
"%s %s %s"|format(def_crt_opt, crt_dir_opt, client_ca_opt)|trim() }}
"%s %s %s %s"|format(def_crt_opt, crt_dir_opt, client_ca_opt, ca_crl_opt)|trim() }}
{% endmacro %}

View File

@ -0,0 +1,36 @@
# Copyright 2018 Huawei
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
"""Add certificate revoke revocation list field
Revision ID: ffad172e98c1
Revises: f21ae3f21adc
Create Date: 2018-10-01 20:47:52.405865
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'ffad172e98c1'
down_revision = 'f21ae3f21adc'
def upgrade():
op.add_column(u'listener',
sa.Column(u'client_crl_container_id', sa.String(255),
nullable=True))

View File

@ -505,6 +505,7 @@ class Listener(base_models.BASE, base_models.IdMixin,
sa.ForeignKey("client_authentication_mode.name",
name="fk_listener_client_authentication_mode_name"),
nullable=False, default=constants.CLIENT_AUTH_NONE)
client_crl_container_id = sa.Column(sa.String(255), nullable=True)
_tags = orm.relationship(
'Tags',

View File

@ -555,7 +555,8 @@ class TestListener(base.BaseAPITest):
# TODO(johnsom) Fix this when there is a noop certificate manager
@mock.patch('octavia.common.tls_utils.cert_parser.load_certificates_data')
def test_create(self, mock_cert_data, response_status=201, **optionals):
def test_create(self, mock_cert_data,
response_status=201, **optionals):
cert1 = data_models.TLSContainer(certificate='cert 1')
cert2 = data_models.TLSContainer(certificate='cert 2')
cert3 = data_models.TLSContainer(certificate='cert 3')
@ -767,6 +768,18 @@ class TestListener(base.BaseAPITest):
self.assertIn(
'be provided with a client CA container reference.', fault)
def test_create_crl_without_ca_cert(self):
optionals = {
'protocol': constants.PROTOCOL_TERMINATED_HTTPS,
'client_ca_tls_container_ref': None,
'client_crl_container_ref': uuidutils.generate_uuid()
}
resp = self.test_create(response_status=400, **optionals).json
fault = resp.get('faultstring')
self.assertIn(
'A client authentication CA reference is required to specify a '
'client authentication revocation list.', fault)
def test_create_with_default_pool_id(self):
lb_listener = {'name': 'listener1',
'default_pool_id': self.pool_id,
@ -973,11 +986,36 @@ class TestListener(base.BaseAPITest):
self.assertEqual(optionals['client_authentication'],
listener_api.get('client_authentication'))
def test_create_with_ca_cert_negative_cases(self):
# create just with option, no client_ca_tls_container_ref specified.
def test_create_with_ca_cert_and_crl(self):
# Load up sample certs to test the validation
self.cert_manager_mock().get_secret.side_effect = [
sample_certs.X509_CA_CERT, sample_certs.X509_CA_CRL,
sample_certs.X509_CA_CERT, sample_certs.X509_CA_CRL]
optionals = {
'client_authentication': constants.CLIENT_AUTH_MANDATORY
'client_ca_tls_container_ref': uuidutils.generate_uuid(),
'client_crl_container_ref': uuidutils.generate_uuid()
}
listener_api = self.test_create(**optionals)
self.assertEqual(optionals['client_ca_tls_container_ref'],
listener_api.get('client_ca_tls_container_ref'))
self.assertEqual(constants.CLIENT_AUTH_NONE,
listener_api.get('client_authentication'))
self.assertEqual(optionals['client_crl_container_ref'],
listener_api.get('client_crl_container_ref'))
# TODO(johnsom) Fix this when there is a noop certificate manager
@mock.patch('octavia.common.tls_utils.cert_parser.load_certificates_data')
def test_create_with_crl_mismatch_ca_cert(self, mock_cert_data):
cert1 = data_models.TLSContainer(certificate='cert 1')
cert2 = data_models.TLSContainer(certificate='cert 2')
cert3 = data_models.TLSContainer(certificate='cert 3')
mock_cert_data.return_value = {'tls_cert': cert1,
'sni_certs': [cert2, cert3]}
self.cert_manager_mock().get_secret.side_effect = [
sample_certs.X509_CERT, sample_certs.X509_CA_CRL,
sample_certs.X509_CERT, sample_certs.X509_CA_CRL]
sni1 = uuidutils.generate_uuid()
sni2 = uuidutils.generate_uuid()
lb_listener = {
@ -989,14 +1027,45 @@ class TestListener(base.BaseAPITest):
'default_tls_container_ref': uuidutils.generate_uuid(),
'sni_container_refs': [sni1, sni2],
'project_id': self.project_id,
'loadbalancer_id': self.lb_id}
lb_listener.update(optionals)
'loadbalancer_id': self.lb_id,
'client_ca_tls_container_ref': uuidutils.generate_uuid(),
'client_crl_container_ref': uuidutils.generate_uuid()
}
body = self._build_body(lb_listener)
response = self.post(self.LISTENERS_PATH, body, status=400).json
self.assertEqual(
"Validation failure: Client authentication setting %s "
"requires a client CA container reference." %
constants.CLIENT_AUTH_MANDATORY, response['faultstring'])
"Validation failure: The CRL specified is not valid for client "
"certificate authority reference supplied.",
response['faultstring'])
@mock.patch('octavia.common.tls_utils.cert_parser.load_certificates_data')
def test_create_with_ca_cert_negative_cases(self, mock_load_cert):
# create just with option or crl,
# no client_ca_tls_container_ref specified.
sni1 = uuidutils.generate_uuid()
sni2 = uuidutils.generate_uuid()
for opt in [{'client_authentication': constants.CLIENT_AUTH_MANDATORY,
'client_crl_container_ref': uuidutils.generate_uuid()},
{'client_authentication': constants.CLIENT_AUTH_OPTIONAL,
'client_crl_container_ref': uuidutils.generate_uuid()}]:
lb_listener = {
'name': 'listener1', 'default_pool_id': None,
'description': 'desc1',
'admin_state_up': False,
'protocol': constants.PROTOCOL_TERMINATED_HTTPS,
'protocol_port': 80,
'default_tls_container_ref': uuidutils.generate_uuid(),
'sni_container_refs': [sni1, sni2],
'project_id': self.project_id,
'loadbalancer_id': self.lb_id}
lb_listener.update(opt)
body = self._build_body(lb_listener)
response = self.post(self.LISTENERS_PATH, body, status=400).json
self.assertEqual(
"Validation failure: Client authentication setting %s "
"requires a client CA container reference." %
opt['client_authentication'], response['faultstring'])
def test_create_with_bad_ca_cert_ref(self):
sni1 = uuidutils.generate_uuid()
@ -1018,7 +1087,30 @@ class TestListener(base.BaseAPITest):
self.cert_manager_mock().get_secret.side_effect = [
Exception('bad ca cert')]
response = self.post(self.LISTENERS_PATH, body, status=400).json
self.assertIn(lb_listener['client_ca_tls_container_ref'],
self.assertEqual("Could not retrieve certificate: ['%s']" %
lb_listener['client_ca_tls_container_ref'],
response['faultstring'])
def test_create_with_unreachable_crl(self):
sni1 = uuidutils.generate_uuid()
sni2 = uuidutils.generate_uuid()
lb_listener = {
'name': 'listener1', 'default_pool_id': None,
'description': 'desc1',
'admin_state_up': False,
'protocol': constants.PROTOCOL_TERMINATED_HTTPS,
'protocol_port': 80,
'default_tls_container_ref': uuidutils.generate_uuid(),
'sni_container_refs': [sni1, sni2],
'project_id': self.project_id,
'loadbalancer_id': self.lb_id,
'client_ca_tls_container_ref': uuidutils.generate_uuid(),
'client_crl_container_ref': uuidutils.generate_uuid()}
body = self._build_body(lb_listener)
self.cert_manager_mock().get_secret.side_effect = Exception(
'bad CRL ref')
response = self.post(self.LISTENERS_PATH, body, status=400).json
self.assertIn(lb_listener['client_crl_container_ref'],
response['faultstring'])
def test_create_with_bad_ca_cert(self):
@ -1213,6 +1305,22 @@ class TestListener(base.BaseAPITest):
self.assertNotEqual(ori_listener['client_authentication'],
optionals['client_authentication'])
def test_update_with_crl(self):
# Load up sample certs to test the validation
self.cert_manager_mock().get_secret.side_effect = [
sample_certs.X509_CA_CERT, sample_certs.X509_CA_CRL,
sample_certs.X509_CA_CERT, sample_certs.X509_CA_CRL,
sample_certs.X509_CA_CERT, sample_certs.X509_CA_CRL]
optionals = {
'client_crl_container_ref': uuidutils.generate_uuid()
}
ori_listener, update_listener = self.test_update(**optionals)
self.assertEqual(optionals['client_crl_container_ref'],
update_listener.get('client_crl_container_ref'))
self.assertNotEqual(ori_listener['client_crl_container_ref'],
optionals['client_crl_container_ref'])
@mock.patch('octavia.common.tls_utils.cert_parser.load_certificates_data')
def test_update_from_nonexist_ca_cert_to_new_ca_cert(self, mock_cert_data):
cert1 = data_models.TLSContainer(certificate='cert 1')
@ -1244,7 +1352,7 @@ class TestListener(base.BaseAPITest):
api_listener['client_authentication'])
@mock.patch('octavia.common.tls_utils.cert_parser.load_certificates_data')
def test_update_with_ca_cert_negative_cases(self, mock_cert_data):
def test_update_with_ca_cert_missing(self, mock_cert_data):
# update a listener, no ca cert exist
cert1 = data_models.TLSContainer(certificate='cert 1')
mock_cert_data.return_value = {'tls_cert': cert1}
@ -1256,16 +1364,42 @@ class TestListener(base.BaseAPITest):
default_tls_container_ref=tls_uuid,
default_pool_id=None).get(self.root_tag)
self.set_lb_status(self.lb_id)
lb_listener = {
'client_authentication': constants.CLIENT_AUTH_OPTIONAL}
body = self._build_body(lb_listener)
for opt in [{'client_authentication': constants.CLIENT_AUTH_OPTIONAL,
'client_crl_container_ref': uuidutils.generate_uuid()},
{'client_authentication': constants.CLIENT_AUTH_MANDATORY,
'client_crl_container_ref': uuidutils.generate_uuid()}]:
body = self._build_body(opt)
listener_path = self.LISTENER_PATH.format(
listener_id=listener['id'])
response = self.put(listener_path, body, status=400).json
self.assertEqual(
"Validation failure: Client authentication setting %s "
"requires a client CA container reference." %
opt['client_authentication'], response['faultstring'])
@mock.patch('octavia.common.tls_utils.cert_parser.load_certificates_data')
def test_update_with_crl_but_ca_cert_missing(self, mock_cert_data):
# update a listener, no ca cert exist
cert1 = data_models.TLSContainer(certificate='cert 1')
mock_cert_data.return_value = {'tls_cert': cert1,
'client_ca_cert': None}
tls_uuid = uuidutils.generate_uuid()
listener = self.create_listener(
constants.PROTOCOL_TERMINATED_HTTPS, 80, self.lb_id,
name='listener1', description='desc1',
admin_state_up=False, connection_limit=10,
default_tls_container_ref=tls_uuid,
default_pool_id=None).get(self.root_tag)
self.set_lb_status(self.lb_id)
body = self._build_body(
{'client_crl_container_ref': uuidutils.generate_uuid()})
listener_path = self.LISTENER_PATH.format(
listener_id=listener['id'])
response = self.put(listener_path, body, status=400).json
self.assertEqual(
"Validation failure: Client authentication setting %s "
"requires a client CA container reference." %
constants.CLIENT_AUTH_OPTIONAL, response['faultstring'])
"Validation failure: A client authentication CA reference is "
"required to specify a client authentication revocation list.",
response['faultstring'])
@mock.patch('octavia.common.tls_utils.cert_parser.load_certificates_data')
def test_update_unset_ca_cert(self, mock_cert_data):
@ -1290,6 +1424,41 @@ class TestListener(base.BaseAPITest):
api_listener = self.put(listener_path, body).json.get(self.root_tag)
self.assertIsNone(api_listener.get('client_ca_tls_container_ref'))
self.assertIsNone(api_listener.get('client_auth_option'))
self.assertIsNone(api_listener.get('client_crl_container_ref'))
@mock.patch(
'octavia.common.tls_utils.cert_parser.load_certificates_data')
def test_update_unset_crl(self, mock_cert_data):
# Load up sample certs to test the validation
self.cert_manager_mock().get_secret.side_effect = [
sample_certs.X509_CA_CERT, sample_certs.X509_CA_CRL,
sample_certs.X509_CA_CERT, sample_certs.X509_CA_CRL,
sample_certs.X509_CA_CERT, sample_certs.X509_CA_CRL,
sample_certs.X509_CA_CERT, sample_certs.X509_CA_CRL]
cert1 = data_models.TLSContainer(certificate='cert 1')
mock_cert_data.return_value = {'tls_cert': cert1}
listener = self.create_listener(
constants.PROTOCOL_TERMINATED_HTTPS, 80, self.lb_id,
name='listener1', description='desc1',
admin_state_up=False, connection_limit=10,
default_tls_container_ref=uuidutils.generate_uuid(),
default_pool_id=None,
client_ca_tls_container_ref=uuidutils.generate_uuid(),
client_crl_container_ref=uuidutils.generate_uuid(),
client_authentication=constants.CLIENT_AUTH_MANDATORY).get(
self.root_tag)
self.set_lb_status(self.lb_id)
lb_listener = {'client_crl_container_ref': None}
body = self._build_body(lb_listener)
listener_path = self.LISTENER_PATH.format(
listener_id=listener['id'])
api_listener = self.put(listener_path, body).json.get(self.root_tag)
self.assertEqual(listener.get('client_ca_tls_container_ref'),
api_listener.get('client_ca_tls_container_ref'))
self.assertEqual(listener.get('client_authentication'),
api_listener.get('client_authentication'))
self.assertIsNone(api_listener.get('client_crl_container_ref'))
@mock.patch('octavia.common.tls_utils.cert_parser.load_certificates_data')
def test_update_with_bad_ca_cert(self, mock_cert_data):
@ -1310,6 +1479,8 @@ class TestListener(base.BaseAPITest):
self.set_lb_status(self.lb_id)
self.cert_manager_mock().get_secret.side_effect = Exception(
'bad ca cert')
self.cert_manager_mock().get_secret.side_effect = Exception(
'bad secret')
lb_listener = {
'client_ca_tls_container_ref': uuidutils.generate_uuid()}
body = self._build_body(lb_listener)
@ -1319,6 +1490,72 @@ class TestListener(base.BaseAPITest):
self.assertIn(lb_listener['client_ca_tls_container_ref'],
response['faultstring'])
@mock.patch('octavia.common.tls_utils.cert_parser.load_certificates_data')
def test_update_with_unreachable_crl(self, mock_cert_data):
# Load up sample certs to test the validation
tls_cert_mock = mock.MagicMock()
tls_cert_mock.get_certificate.return_value = sample_certs.X509_CA_CERT
self.cert_manager_mock().get_cert.return_value = tls_cert_mock
self.cert_manager_mock().get_secret.side_effect = [
sample_certs.X509_CA_CERT, sample_certs.X509_CA_CRL,
sample_certs.X509_CA_CERT, sample_certs.X509_CA_CRL,
sample_certs.X509_CA_CERT, Exception('bad CRL ref')]
cert1 = data_models.TLSContainer(certificate='cert 1')
mock_cert_data.return_value = {'tls_cert': cert1}
listener = self.create_listener(
constants.PROTOCOL_TERMINATED_HTTPS, 80, self.lb_id,
name='listener1', description='desc1',
admin_state_up=False, connection_limit=10,
default_tls_container_ref=uuidutils.generate_uuid(),
default_pool_id=None,
client_ca_tls_container_ref=uuidutils.generate_uuid(),
client_crl_container_ref=uuidutils.generate_uuid()).get(
self.root_tag)
self.set_lb_status(self.lb_id)
lb_listener = {
'client_crl_container_ref': uuidutils.generate_uuid()}
body = self._build_body(lb_listener)
listener_path = self.LISTENER_PATH.format(
listener_id=listener['id'])
response = self.put(listener_path, body, status=400).json
self.assertIn(lb_listener['client_crl_container_ref'],
response['faultstring'])
@mock.patch('octavia.common.tls_utils.cert_parser.load_certificates_data')
def test_update_with_bad_crl(self, mock_cert_data):
# Load up sample certs to test the validation
tls_cert_mock = mock.MagicMock()
tls_cert_mock.get_certificate.return_value = sample_certs.X509_CA_CERT
self.cert_manager_mock().get_cert.return_value = tls_cert_mock
self.cert_manager_mock().get_secret.side_effect = [
sample_certs.X509_CA_CERT, sample_certs.X509_CA_CRL,
sample_certs.X509_CA_CERT, sample_certs.X509_CA_CRL,
sample_certs.X509_CA_CERT, 'bad CRL']
cert1 = data_models.TLSContainer(certificate='cert 1')
mock_cert_data.return_value = {'tls_cert': cert1}
listener = self.create_listener(
constants.PROTOCOL_TERMINATED_HTTPS, 80, self.lb_id,
name='listener1', description='desc1',
admin_state_up=False, connection_limit=10,
default_tls_container_ref=uuidutils.generate_uuid(),
default_pool_id=None,
client_ca_tls_container_ref=uuidutils.generate_uuid(),
client_crl_container_ref=uuidutils.generate_uuid()).get(
self.root_tag)
self.set_lb_status(self.lb_id)
lb_listener = {
'client_crl_container_ref': uuidutils.generate_uuid()}
body = self._build_body(lb_listener)
listener_path = self.LISTENER_PATH.format(
listener_id=listener['id'])
response = self.put(listener_path, body, status=400).json
self.assertIn("The client authentication certificate revocation list "
"is invalid. It must be a valid x509 PEM format "
"certificate revocation list.",
response['faultstring'])
def test_update_authorized(self):
listener = self.create_listener(
constants.PROTOCOL_TCP, 80, self.lb_id,
@ -1586,6 +1823,8 @@ class TestListener(base.BaseAPITest):
listener_id=api_listener['id'])
self.cert_manager_mock().get_cert.side_effect = [
Exception("bad cert"), None, Exception("bad cert")]
self.cert_manager_mock().get_secret.side_effect = [
Exception("bad secret"), Exception("bad secret")]
response = self.put(listener_path, body, status=400).json
self.assertIn(tls_ref2, response['faultstring'])
self.assertIn(sni1, response['faultstring'])

View File

@ -2358,7 +2358,9 @@ class TestLoadBalancerGraph(base.BaseAPITest):
expected_client_ca_tls_container=None,
create_protocol=constants.PROTOCOL_HTTP,
create_client_authentication=None,
expected_client_authentication=constants.CLIENT_AUTH_NONE):
expected_client_authentication=constants.CLIENT_AUTH_NONE,
create_client_crl_container=None,
expected_client_crl_container=None):
create_listener = {
'name': name,
'protocol_port': protocol_port,
@ -2380,7 +2382,8 @@ class TestLoadBalancerGraph(base.BaseAPITest):
'timeout_tcp_inspect': constants.DEFAULT_TIMEOUT_TCP_INSPECT,
'tags': [],
'client_ca_tls_container_ref': None,
'client_authentication': constants.CLIENT_AUTH_NONE
'client_authentication': constants.CLIENT_AUTH_NONE,
'client_crl_container_ref': None
}
if create_sni_containers:
create_listener['sni_container_refs'] = create_sni_containers
@ -2402,6 +2405,9 @@ class TestLoadBalancerGraph(base.BaseAPITest):
if create_client_authentication:
create_listener['client_authentication'] = (
create_client_authentication)
if create_client_crl_container:
create_listener['client_crl_container_ref'] = (
create_client_crl_container)
if expected_sni_containers:
expected_listener['sni_container_refs'] = expected_sni_containers
if expected_l7policies:
@ -2416,6 +2422,9 @@ class TestLoadBalancerGraph(base.BaseAPITest):
if expected_client_authentication:
expected_listener[
'client_authentication'] = expected_client_authentication
if expected_client_crl_container:
expected_listener['client_crl_container_ref'] = (
expected_client_crl_container)
return create_listener, expected_listener
def _get_pool_bodies(self, name='pool1', create_members=None,
@ -2664,23 +2673,27 @@ class TestLoadBalancerGraph(base.BaseAPITest):
self._assert_graphs_equal(expected_lb, api_lb)
@mock.patch('cryptography.hazmat.backends.default_backend')
@mock.patch('cryptography.x509.load_pem_x509_crl')
@mock.patch('cryptography.x509.load_pem_x509_certificate')
@mock.patch('octavia.api.drivers.utils._get_secret_data')
@mock.patch('octavia.common.tls_utils.cert_parser.load_certificates_data')
def test_with_full_listener_certs(self, mock_cert_data, mock_get_secret,
mock_x509_cert, mock_backend):
mock_x509_cert, mock_x509_crl,
mock_backend):
cert1 = data_models.TLSContainer(certificate='cert 1')
cert2 = data_models.TLSContainer(certificate='cert 2')
cert3 = data_models.TLSContainer(certificate='cert 3')
mock_get_secret.side_effect = ['ca cert', 'X509 CRL FILE']
mock_cert_data.return_value = {'tls_cert': cert1,
'sni_certs': [cert2, cert3]}
mock_get_secret.side_effect = ['ca cert']
cert_mock = mock.MagicMock()
mock_x509_cert.return_value = cert_mock
create_client_ca_tls_container = uuidutils.generate_uuid()
create_client_ca_tls_container, create_client_crl_container = (
uuidutils.generate_uuid(), uuidutils.generate_uuid())
expected_client_ca_tls_container = create_client_ca_tls_container
create_client_authentication = constants.CLIENT_AUTH_MANDATORY
expected_client_authentication = constants.CLIENT_AUTH_MANDATORY
expected_client_crl_container = create_client_crl_container
create_sni_containers, expected_sni_containers = (
self._get_sni_container_bodies())
create_listener, expected_listener = self._get_listener_bodies(
@ -2690,7 +2703,9 @@ class TestLoadBalancerGraph(base.BaseAPITest):
create_client_ca_tls_container=create_client_ca_tls_container,
expected_client_ca_tls_container=expected_client_ca_tls_container,
create_client_authentication=create_client_authentication,
expected_client_authentication=expected_client_authentication)
expected_client_authentication=expected_client_authentication,
create_client_crl_container=create_client_crl_container,
expected_client_crl_container=expected_client_crl_container)
create_lb, expected_lb = self._get_lb_bodies(
create_listeners=[create_listener],
expected_listeners=[expected_listener])

View File

@ -66,7 +66,7 @@ class TestHaproxyAmphoraLoadBalancerDriverTest(base.TestCase):
# Build sample Listener and VIP configs
self.sl = sample_configs.sample_listener_tuple(
tls=True, sni=True, client_ca_cert=True)
tls=True, sni=True, client_ca_cert=True, client_crl_cert=True)
self.sl_udp = sample_configs.sample_listener_tuple(
proto=constants.PROTOCOL_UDP,
persistence_type=constants.SESSION_PERSISTENCE_SOURCE_IP,
@ -148,7 +148,7 @@ class TestHaproxyAmphoraLoadBalancerDriverTest(base.TestCase):
@mock.patch('octavia.common.tls_utils.cert_parser.get_host_names')
def test_update(self, mock_cert, mock_load_crt, mock_secret):
mock_cert.return_value = {'cn': sample_certs.X509_CERT_CN}
mock_secret.return_value = 'filename.pem'
mock_secret.side_effect = ['filename.pem', 'crl-filename.pem']
sconts = []
for sni_container in self.sl.sni_containers:
sconts.append(sni_container.tls_container)
@ -168,7 +168,7 @@ class TestHaproxyAmphoraLoadBalancerDriverTest(base.TestCase):
self.driver.update(self.sl, self.sv)
# verify result
# this is called 4 times
# this is called 5 times
gcm_calls = [
mock.call(self.amp, self.sl.id,
self.sl.default_tls_container.id + '.pem',
@ -209,6 +209,11 @@ class TestHaproxyAmphoraLoadBalancerDriverTest(base.TestCase):
# start should be called once
self.driver.client.reload_listener.assert_called_once_with(
self.amp, self.sl.id)
secret_calls = [
mock.call(self.sl, self.sl.client_ca_tls_certificate_id),
mock.call(self.sl, self.sl.client_crl_container_id)
]
mock_secret.assert_has_calls(secret_calls)
def test_udp_update(self):
self.driver.udp_jinja.build_config.side_effect = ['fake_udp_config']

View File

@ -38,6 +38,7 @@ class SampleDriverDataModels(object):
self.sni_container_ref_1 = uuidutils.generate_uuid()
self.sni_container_ref_2 = uuidutils.generate_uuid()
self.client_ca_tls_certificate_ref = uuidutils.generate_uuid()
self.client_crl_container_ref = uuidutils.generate_uuid()
self.pool1_id = uuidutils.generate_uuid()
self.pool2_id = uuidutils.generate_uuid()
@ -383,7 +384,8 @@ class SampleDriverDataModels(object):
'timeout_member_data': 3000,
'timeout_tcp_inspect': 4000,
'client_ca_tls_certificate_id': self.client_ca_tls_certificate_ref,
'client_authentication': constants.CLIENT_AUTH_NONE
'client_authentication': constants.CLIENT_AUTH_NONE,
'client_crl_container_id': self.client_crl_container_ref
}
self.test_listener1_dict.update(self._common_test_dict)
@ -397,6 +399,7 @@ class SampleDriverDataModels(object):
del self.test_listener2_dict['l7policies']
del self.test_listener2_dict['sni_containers']
del self.test_listener2_dict['client_ca_tls_certificate_id']
del self.test_listener2_dict['client_crl_container_id']
self.test_listeners = [self.test_listener1_dict,
self.test_listener2_dict]
@ -416,6 +419,7 @@ class SampleDriverDataModels(object):
cert2 = data_models.TLSContainer(certificate='cert 2')
cert3 = data_models.TLSContainer(certificate='cert 3')
ca_cert = 'ca cert'
crl_file_content = 'X509 CRL FILE'
self.provider_listener1_dict = {
'admin_state_up': True,
@ -441,7 +445,9 @@ class SampleDriverDataModels(object):
'timeout_tcp_inspect': 4000,
'client_ca_tls_container_ref': self.client_ca_tls_certificate_ref,
'client_ca_tls_container_data': ca_cert,
'client_authentication': constants.CLIENT_AUTH_NONE
'client_authentication': constants.CLIENT_AUTH_NONE,
'client_crl_container_ref': self.client_crl_container_ref,
'client_crl_container_data': crl_file_content
}
self.provider_listener2_dict = copy.deepcopy(
@ -456,6 +462,8 @@ class SampleDriverDataModels(object):
del self.provider_listener2_dict['client_ca_tls_container_data']
self.provider_listener2_dict['client_authentication'] = (
constants.CLIENT_AUTH_NONE)
self.provider_listener2_dict['client_crl_container_ref'] = None
del self.provider_listener2_dict['client_crl_container_data']
self.provider_listener1 = driver_dm.Listener(
**self.provider_listener1_dict)

View File

@ -90,7 +90,7 @@ class TestUtils(base.TestCase):
cert1 = data_models.TLSContainer(certificate='cert 1')
cert2 = data_models.TLSContainer(certificate='cert 2')
cert3 = data_models.TLSContainer(certificate='cert 3')
mock_secret.return_value = 'ca cert'
mock_secret.side_effect = ['ca cert', 'X509 CRL FILE']
mock_load_cert.return_value = {'tls_cert': cert1,
'sni_certs': [cert2, cert3]}
test_lb_dict = {'name': 'lb1',
@ -162,7 +162,7 @@ class TestUtils(base.TestCase):
@mock.patch('octavia.common.tls_utils.cert_parser.load_certificates_data')
def test_db_listeners_to_provider_listeners(self, mock_load_cert,
mock_secret):
mock_secret.return_value = 'ca cert'
mock_secret.side_effect = ['ca cert', 'X509 CRL FILE']
cert1 = data_models.TLSContainer(certificate='cert 1')
cert2 = data_models.TLSContainer(certificate='cert 2')
cert3 = data_models.TLSContainer(certificate='cert 3')
@ -176,16 +176,24 @@ class TestUtils(base.TestCase):
@mock.patch('octavia.api.drivers.utils._get_secret_data')
@mock.patch('octavia.common.tls_utils.cert_parser.load_certificates_data')
def test_listener_dict_to_provider_dict(self, mock_load_cert, mock_secret):
mock_secret.return_value = 'ca cert'
mock_secret.side_effect = ['ca cert', 'X509 CRL FILE']
cert1 = data_models.TLSContainer(certificate='cert 1')
cert2 = data_models.TLSContainer(certificate='cert 2')
cert3 = data_models.TLSContainer(certificate='cert 3')
mock_load_cert.return_value = {'tls_cert': cert1,
'sni_certs': [cert2, cert3]}
# The reason to do this, as before the logic arrives the test func,
# there are two data sources, one is from db_dict, the other is from
# the api layer model_dict, actually, they are different and contain
# different fields. That's why the test_listener1_dict from sample data
# just contain the client_ca_tls_certificate_id for client certificate,
# not any other related fields. So we need to delete them.
expect_prov = copy.deepcopy(self.sample_data.provider_listener1_dict)
provider_listener = utils.listener_dict_to_provider_dict(
self.sample_data.test_listener1_dict)
self.assertEqual(self.sample_data.provider_listener1_dict,
provider_listener)
expect_prov.pop('client_crl_container_ref')
provider_listener.pop('client_crl_container_ref')
self.assertEqual(expect_prov, provider_listener)
@mock.patch('octavia.api.drivers.utils._get_secret_data')
@mock.patch('octavia.common.tls_utils.cert_parser.load_certificates_data')

View File

@ -48,7 +48,6 @@ class TestBarbicanManager(base.TestCase):
self.fake_secret = 'Fake secret'
self.secret = secrets.Secret(api=mock.MagicMock(),
payload=self.fake_secret)
self.empty_secret = mock.Mock(spec=secrets.Secret)
# Mock out the client

View File

@ -40,7 +40,8 @@ class TestHaproxyCfg(base.TestCase):
"sample_listener_id_1/tls_container_id.pem "
"crt /var/lib/octavia/certs/sample_listener_id_1 "
"ca-file /var/lib/octavia/certs/sample_listener_id_1/"
"client_ca.pem verify required\n"
"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\n").format(
@ -71,8 +72,10 @@ class TestHaproxyCfg(base.TestCase):
sample_configs.sample_amphora_tuple(),
sample_configs.sample_listener_tuple(proto='TERMINATED_HTTPS',
tls=True, sni=True,
client_ca_cert=True),
tls_tupe, client_ca_filename='client_ca.pem')
client_ca_cert=True,
client_crl_cert=True),
tls_tupe, client_ca_filename='client_ca.pem',
client_crl='SHA_ID.pem')
self.assertEqual(
sample_configs.sample_base_expected_config(
frontend=fe, backend=be),

View File

@ -856,3 +856,22 @@ pDEMmP7TJMJ3dG63RtAzQiGfRO18BIVOrRUfQpR32FkrYd9wCE02cnv0QZzY9NYt
6hAlAa6Motve8UFewoO4pNknj3MBEN+64wDzHaP6VPysNJwrAlgaHfGDU6xJffAd
uCWDmw==
-----END CERTIFICATE-----"""
X509_CA_CRL = b"""-----BEGIN X509 CRL-----
MIIC7zCB2AIBATANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJVUzEPMA0GA1UE
CAwGT3JlZ29uMRIwEAYDVQQKDAlPcGVuU3RhY2sxEDAOBgNVBAsMB09jdGF2aWEx
FzAVBgNVBAMMDmNhLmV4YW1wbGUub3JnFw0xOTAyMTkwMjAxNTlaFw0xOTAzMjEw
MjAxNTlaMBUwEwICEAAXDTE5MDIxOTAyMDAyMlqgMDAuMB8GA1UdIwQYMBaAFN/4
bLQKWNMwoLzQ2du9NT33x7+DMAsGA1UdFAQEAgIQADANBgkqhkiG9w0BAQsFAAOC
AgEAcPtYSLEkJwvqaAfMGXwI2uTTKWURqtwfcBMYdVF1u2xsBsrKR6ogpBjzc1sX
A5WN9Tz5TXPVd38DTEGlCGLQ7wZ8wwYAR2sArHjw/zcsOJcFVTWtpX+2UAbpqis9
rBq7K6TF2m1fYb0RJg0AUbja/wfpghoEjfFx8FjIa8WAqqazyWR9vslm7kSoEgr+
MDV7agVK+h1n68hdLA9osUyPaAobus5FcVlXePPp5Ab8/vx1b2/Y+VXHaJXTZCin
FLQaxaH0PsMCKN/T52GPMRKa2Cc6IEaDFgE1ZlA8nP5t2tA7MFORI8dix6jIzBJD
W2CRf1Oxkrd3iqs1IljtlKHKMUTS67lfA9EwKlt8dR+KwH/WT23LSIoC9NnS3DP+
aT3t52soCpjXbfl8fgs62bome1/88BoNIa2T1Mj6F0aPvepLsFB/UrXWhADFj+DX
7WclP62BNBCTlUNvMF0eC9o7r5xeazo53KH1KI62qlFrz5MbRCG8g0JtTFqsMJld
phYuPfZekoNbsOIPDTiPFniuP2saOF4TSRCW4KnpgblRkds6c8X+1ExdlSo5GjNa
PftOKlYtE7T7Kw4CI9+O2H38IUOYjDt/c2twy954K4pKe4x9Ud8mImpS/oEzOsoz
/Mn++bjO55LdaAUKQ3wa8LZ5WFB+Gs6b2kmBfzGarWEiX64=
-----END X509 CRL-----"""

View File

@ -512,7 +512,7 @@ def sample_listener_tuple(proto=None, monitor=True, alloc_default_pool=True,
timeout_member_connect=5000,
timeout_member_data=50000,
timeout_tcp_inspect=0,
client_ca_cert=False):
client_ca_cert=False, client_crl_cert=False):
proto = 'HTTP' if proto is None else proto
if be_proto is None:
be_proto = 'HTTP' if proto is 'TERMINATED_HTTPS' else proto
@ -527,8 +527,9 @@ def sample_listener_tuple(proto=None, monitor=True, alloc_default_pool=True,
'sni_containers, load_balancer, peer_port, pools, '
'l7policies, enabled, insert_headers, timeout_client_data,'
'timeout_member_connect, timeout_member_data, '
'timeout_tcp_inspect, client_ca_tls_certificate_id,'
'client_ca_tls_certificate, client_authentication')
'timeout_tcp_inspect, client_ca_tls_certificate_id, '
'client_ca_tls_certificate, client_authentication, '
'client_crl_container_id')
if l7:
pools = [
sample_pool_tuple(
@ -614,7 +615,8 @@ def sample_listener_tuple(proto=None, monitor=True, alloc_default_pool=True,
) if client_ca_cert else '',
client_authentication=(
constants.CLIENT_AUTH_MANDATORY if client_ca_cert else
constants.CLIENT_AUTH_NONE)
constants.CLIENT_AUTH_NONE),
client_crl_container_id='cont_id_crl' if client_crl_cert else '',
)

View File

@ -0,0 +1,10 @@
---
features:
- |
You can now provide a certificate revocation list reference for listeners
using TLS client authentication.
security:
- |
Note that the amphora provider currently only supports the crl-file
provided to check for revocation. Remote revocation lists and/or OCSP
will not be used by the amphora provider.