Add an option to the Octavia V2 listener API for client cert
Listener API for client cerificate authentication with "None, Optional, Mandatory" options Story: 2002165 Task: 20019 Co-Authored-By: Michael Johnson <johnsomor@gmail.com> Change-Id: Ia753659981d99b315504f166c09afb8f5b14f195
This commit is contained in:
parent
0cc546a7c7
commit
7a8eb3ce22
@ -246,6 +246,22 @@ cert-expiration:
|
||||
in: body
|
||||
required: true
|
||||
type: string
|
||||
client_authentication:
|
||||
description: |
|
||||
The TLS client authentication mode. One of the options ``NONE``,
|
||||
``OPTIONAL`` or ``MANDATORY``.
|
||||
in: body
|
||||
min_version: 2.8
|
||||
required: true
|
||||
type: string
|
||||
client_authentication-optional:
|
||||
description: |
|
||||
The TLS client authentication mode. One of the options ``NONE``,
|
||||
``OPTIONAL`` or ``MANDATORY``.
|
||||
in: body
|
||||
min_version: 2.8
|
||||
required: false
|
||||
type: string
|
||||
client_ca_tls_container_ref:
|
||||
description: |
|
||||
The ref of the `key manager service
|
||||
|
@ -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"}}' 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"}}' http://198.51.100.10:9876/v2/lbaas/listeners
|
||||
|
@ -21,6 +21,7 @@
|
||||
"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_ca_tls_container_ref": "http://198.51.100.10:9311/v1/containers/35649991-49f3-4625-81ce-2465fe8932e5",
|
||||
"client_authentication": "MANDATORY"
|
||||
}
|
||||
}
|
||||
|
@ -36,6 +36,7 @@
|
||||
"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_ca_tls_container_ref": "http://198.51.100.10:9311/v1/containers/35649991-49f3-4625-81ce-2465fe8932e5",
|
||||
"client_authentication": "MANDATORY"
|
||||
}
|
||||
}
|
||||
|
@ -36,6 +36,7 @@
|
||||
"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_ca_tls_container_ref": "http://198.51.100.10:9311/v1/containers/35649991-49f3-4625-81ce-2465fe8932e5",
|
||||
"client_authentication": "MANDATORY"
|
||||
}
|
||||
}
|
||||
|
@ -36,6 +36,7 @@
|
||||
"timeout_member_data": 100000,
|
||||
"timeout_tcp_inspect": 5,
|
||||
"tags": ["updated_tag"],
|
||||
"client_ca_tls_container_ref": null
|
||||
"client_ca_tls_container_ref": null,
|
||||
"client_authentication": "NONE"
|
||||
}
|
||||
}
|
||||
|
@ -38,7 +38,8 @@
|
||||
"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_ca_tls_container_ref": "http://198.51.100.10:9311/v1/containers/35649991-49f3-4625-81ce-2465fe8932e5",
|
||||
"client_authentication": "NONE"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -46,6 +46,7 @@ Response Parameters
|
||||
.. rest_parameters:: ../parameters.yaml
|
||||
|
||||
- admin_state_up: admin_state_up
|
||||
- client_authentication: client_authentication
|
||||
- client_ca_tls_container_ref: client_ca_tls_container_ref
|
||||
- connection_limit: connection_limit
|
||||
- created_at: created_at
|
||||
@ -137,6 +138,7 @@ Request
|
||||
.. rest_parameters:: ../parameters.yaml
|
||||
|
||||
- admin_state_up: admin_state_up-default-optional
|
||||
- client_authentication: client_authentication-optional
|
||||
- client_ca_tls_container_ref: client_ca_tls_container_ref-optional
|
||||
- connection_limit: connection_limit-optional
|
||||
- default_pool: pool-optional
|
||||
@ -206,6 +208,7 @@ Response Parameters
|
||||
.. rest_parameters:: ../parameters.yaml
|
||||
|
||||
- admin_state_up: admin_state_up
|
||||
- client_authentication: client_authentication
|
||||
- client_ca_tls_container_ref: client_ca_tls_container_ref
|
||||
- connection_limit: connection_limit
|
||||
- created_at: created_at
|
||||
@ -281,6 +284,7 @@ Response Parameters
|
||||
.. rest_parameters:: ../parameters.yaml
|
||||
|
||||
- admin_state_up: admin_state_up
|
||||
- client_authentication: client_authentication
|
||||
- client_ca_tls_container_ref: client_ca_tls_container_ref
|
||||
- connection_limit: connection_limit
|
||||
- created_at: created_at
|
||||
@ -346,6 +350,7 @@ Request
|
||||
.. rest_parameters:: ../parameters.yaml
|
||||
|
||||
- admin_state_up: admin_state_up-default-optional
|
||||
- client_authentication: client_authentication-optional
|
||||
- client_ca_tls_container_ref: client_ca_tls_container_ref-optional
|
||||
- connection_limit: connection_limit-optional
|
||||
- default_pool_id: default_pool_id-optional
|
||||
@ -379,6 +384,7 @@ Response Parameters
|
||||
.. rest_parameters:: ../parameters.yaml
|
||||
|
||||
- admin_state_up: admin_state_up
|
||||
- client_authentication: client_authentication
|
||||
- client_ca_tls_container_ref: client_ca_tls_container_ref
|
||||
- connection_limit: connection_limit
|
||||
- created_at: created_at
|
||||
|
@ -364,6 +364,10 @@ contain the following:
|
||||
| admin_state_up | bool | Admin state: True if up, False if |
|
||||
| | | down. |
|
||||
+------------------------------+--------+-------------------------------------+
|
||||
| client_authentication | string | The TLS client authentication mode. |
|
||||
| | | One of the options ``NONE``, |
|
||||
| | | ``OPTIONAL`` or ``MANDATORY``. |
|
||||
+------------------------------+--------+-------------------------------------+
|
||||
| client_ca_tls_container_data | string | A PEM encoded certificate. |
|
||||
+------------------------------+--------+-------------------------------------+
|
||||
| client_ca_tls_container_ref | string | The reference to the secrets |
|
||||
|
@ -134,7 +134,8 @@ class Listener(BaseDataModel):
|
||||
sni_container_data=Unset, timeout_client_data=Unset,
|
||||
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_ca_tls_container_data=Unset,
|
||||
client_authentication=Unset):
|
||||
|
||||
self.admin_state_up = admin_state_up
|
||||
self.connection_limit = connection_limit
|
||||
@ -158,6 +159,7 @@ class Listener(BaseDataModel):
|
||||
self.timeout_tcp_inspect = timeout_tcp_inspect
|
||||
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
|
||||
|
||||
|
||||
class Pool(BaseDataModel):
|
||||
|
@ -230,6 +230,15 @@ class ListenersController(base.BaseController):
|
||||
"be provided for %s protocol listeners.") %
|
||||
constants.PROTOCOL_TERMINATED_HTTPS)
|
||||
|
||||
# Make sure we have a client CA cert if they enable client auth
|
||||
if (listener_dict.get('client_authentication') !=
|
||||
constants.CLIENT_AUTH_NONE and not
|
||||
listener_dict.get('client_ca_tls_certificate_id')):
|
||||
raise exceptions.ValidationException(detail=_(
|
||||
"Client authentication setting %s requires a client CA "
|
||||
"container reference.") %
|
||||
listener_dict.get('client_authentication'))
|
||||
|
||||
try:
|
||||
sni_containers = listener_dict.pop('sni_containers', [])
|
||||
tls_refs = [sni['tls_container_id'] for sni in sni_containers]
|
||||
@ -382,7 +391,16 @@ class ListenersController(base.BaseController):
|
||||
"%s protocol listeners.") %
|
||||
constants.PROTOCOL_TERMINATED_HTTPS)
|
||||
|
||||
# Make sure the refs are valid
|
||||
# Make sure we have a client CA cert if they enable client auth
|
||||
if ((listener.client_authentication != wtypes.Unset and
|
||||
listener.client_authentication != constants.CLIENT_AUTH_NONE)
|
||||
and not (db_listener.client_ca_tls_certificate_id or
|
||||
listener.client_ca_tls_container_ref)):
|
||||
raise exceptions.ValidationException(detail=_(
|
||||
"Client authentication setting %s requires a client CA "
|
||||
"container reference.") %
|
||||
listener.client_authentication)
|
||||
|
||||
sni_containers = listener.sni_container_refs or []
|
||||
tls_refs = [sni for sni in sni_containers]
|
||||
if listener.default_tls_container_ref:
|
||||
|
@ -58,6 +58,7 @@ class ListenerResponse(BaseListenerType):
|
||||
timeout_tcp_inspect = wtypes.wsattr(wtypes.IntegerType())
|
||||
tags = wtypes.wsattr(wtypes.ArrayType(wtypes.StringType()))
|
||||
client_ca_tls_container_ref = wtypes.StringType()
|
||||
client_authentication = wtypes.wsattr(wtypes.StringType())
|
||||
|
||||
@classmethod
|
||||
def from_data_model(cls, data_model, children=False):
|
||||
@ -138,6 +139,9 @@ class ListenerPOST(BaseListenerType):
|
||||
default=CONF.haproxy_amphora.timeout_tcp_inspect)
|
||||
tags = wtypes.wsattr(wtypes.ArrayType(wtypes.StringType(max_length=255)))
|
||||
client_ca_tls_container_ref = wtypes.StringType(max_length=255)
|
||||
client_authentication = wtypes.wsattr(
|
||||
wtypes.Enum(str, *constants.SUPPORTED_CLIENT_AUTH_MODES),
|
||||
default=constants.CLIENT_AUTH_NONE)
|
||||
|
||||
|
||||
class ListenerRootPOST(types.BaseType):
|
||||
@ -171,6 +175,8 @@ class ListenerPUT(BaseListenerType):
|
||||
maximum=constants.MAX_TIMEOUT))
|
||||
tags = wtypes.wsattr(wtypes.ArrayType(wtypes.StringType(max_length=255)))
|
||||
client_ca_tls_container_ref = wtypes.StringType(max_length=255)
|
||||
client_authentication = wtypes.wsattr(
|
||||
wtypes.Enum(str, *constants.SUPPORTED_CLIENT_AUTH_MODES))
|
||||
|
||||
|
||||
class ListenerRootPUT(types.BaseType):
|
||||
@ -215,6 +221,9 @@ class ListenerSingleCreate(BaseListenerType):
|
||||
default=CONF.haproxy_amphora.timeout_tcp_inspect)
|
||||
tags = wtypes.wsattr(wtypes.ArrayType(wtypes.StringType(max_length=255)))
|
||||
client_ca_tls_container_ref = wtypes.StringType(max_length=255)
|
||||
client_authentication = wtypes.wsattr(
|
||||
wtypes.Enum(str, *constants.SUPPORTED_CLIENT_AUTH_MODES),
|
||||
default=constants.CLIENT_AUTH_NONE)
|
||||
|
||||
|
||||
class ListenerStatusResponse(BaseListenerType):
|
||||
|
@ -580,3 +580,11 @@ FLAVOR_DATA = 'flavor_data'
|
||||
# Flavor metadata
|
||||
LOADBALANCER_TOPOLOGY = 'loadbalancer_topology'
|
||||
COMPUTE_FLAVOR = 'compute_flavor'
|
||||
|
||||
# TODO(johnsom) move to octavia_lib
|
||||
# client certification authorization option
|
||||
CLIENT_AUTH_NONE = 'NONE'
|
||||
CLIENT_AUTH_OPTIONAL = 'OPTIONAL'
|
||||
CLIENT_AUTH_MANDATORY = 'MANDATORY'
|
||||
SUPPORTED_CLIENT_AUTH_MODES = [CLIENT_AUTH_NONE, CLIENT_AUTH_OPTIONAL,
|
||||
CLIENT_AUTH_MANDATORY]
|
||||
|
@ -368,7 +368,8 @@ class Listener(BaseDataModel):
|
||||
created_at=None, updated_at=None,
|
||||
timeout_client_data=None, timeout_member_connect=None,
|
||||
timeout_member_data=None, timeout_tcp_inspect=None,
|
||||
tags=None, client_ca_tls_certificate_id=None):
|
||||
tags=None, client_ca_tls_certificate_id=None,
|
||||
client_authentication=None):
|
||||
self.id = id
|
||||
self.project_id = project_id
|
||||
self.name = name
|
||||
@ -398,6 +399,7 @@ class Listener(BaseDataModel):
|
||||
self.timeout_tcp_inspect = timeout_tcp_inspect
|
||||
self.tags = tags
|
||||
self.client_ca_tls_certificate_id = client_ca_tls_certificate_id
|
||||
self.client_authentication = client_authentication
|
||||
|
||||
def update(self, update_dict):
|
||||
for key, value in update_dict.items():
|
||||
|
@ -36,6 +36,10 @@ BALANCE_MAP = {
|
||||
constants.LB_ALGORITHM_SOURCE_IP: 'source'
|
||||
}
|
||||
|
||||
CLIENT_AUTH_MAP = {constants.CLIENT_AUTH_NONE: 'none',
|
||||
constants.CLIENT_AUTH_OPTIONAL: 'optional',
|
||||
constants.CLIENT_AUTH_MANDATORY: 'required'}
|
||||
|
||||
ACTIVE_PENDING_STATUSES = constants.SUPPORTED_PROVISIONING_STATUSES + (
|
||||
constants.DEGRADED,)
|
||||
|
||||
@ -239,6 +243,9 @@ class JinjaTemplater(object):
|
||||
ret_value['client_ca_tls_path'] = '%s' % (
|
||||
os.path.join(self.base_crt_dir, listener.id,
|
||||
client_ca_filename))
|
||||
ret_value['client_auth'] = CLIENT_AUTH_MAP.get(
|
||||
listener.client_authentication)
|
||||
|
||||
if listener.default_pool:
|
||||
ret_value['default_pool'] = self._transform_pool(
|
||||
listener.default_pool, feature_compatibility)
|
||||
|
@ -38,8 +38,8 @@ peers {{ "%s_peers"|format(listener.id.replace("-", ""))|trim() }}
|
||||
{% else %}
|
||||
{% set crt_dir_opt = "" %}
|
||||
{% endif %}
|
||||
{% if listener.client_ca_tls_path %}
|
||||
{% set client_ca_opt = "ca-file %s"|format(listener.client_ca_tls_path)|trim() %}
|
||||
{% 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 %}
|
||||
{% set client_ca_opt = "" %}
|
||||
{% endif %}
|
||||
|
@ -0,0 +1,61 @@
|
||||
# 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 Client Auth options
|
||||
|
||||
Revision ID: f21ae3f21adc
|
||||
Revises: 2ad093f6353f
|
||||
Create Date: 2018-10-01 20:47:52.405865
|
||||
|
||||
"""
|
||||
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
from octavia.common import constants
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'f21ae3f21adc'
|
||||
down_revision = '2ad093f6353f'
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.create_table(
|
||||
u'client_authentication_mode',
|
||||
sa.Column(u'name', sa.String(10), primary_key=True),
|
||||
)
|
||||
|
||||
# Create temporary table for table data seeding
|
||||
insert_table = sa.table(
|
||||
u'client_authentication_mode',
|
||||
sa.column(u'name', sa.String),
|
||||
)
|
||||
|
||||
op.bulk_insert(
|
||||
insert_table,
|
||||
[
|
||||
{'name': constants.CLIENT_AUTH_NONE},
|
||||
{'name': constants.CLIENT_AUTH_OPTIONAL},
|
||||
{'name': constants.CLIENT_AUTH_MANDATORY}
|
||||
]
|
||||
)
|
||||
|
||||
op.add_column(
|
||||
u'listener',
|
||||
sa.Column(u'client_authentication', sa.String(10),
|
||||
sa.ForeignKey('client_authentication_mode.name'),
|
||||
server_default=constants.CLIENT_AUTH_NONE, nullable=False)
|
||||
)
|
@ -33,6 +33,7 @@ from octavia.api.v2.types import load_balancer
|
||||
from octavia.api.v2.types import member
|
||||
from octavia.api.v2.types import pool
|
||||
from octavia.api.v2.types import quotas
|
||||
from octavia.common import constants
|
||||
from octavia.common import data_models
|
||||
from octavia.db import base_models
|
||||
from octavia.i18n import _
|
||||
@ -499,6 +500,11 @@ class Listener(base_models.BASE, base_models.IdMixin,
|
||||
timeout_member_data = sa.Column(sa.Integer, nullable=True)
|
||||
timeout_tcp_inspect = sa.Column(sa.Integer, nullable=True)
|
||||
client_ca_tls_certificate_id = sa.Column(sa.String(255), nullable=True)
|
||||
client_authentication = sa.Column(
|
||||
sa.String(10),
|
||||
sa.ForeignKey("client_authentication_mode.name",
|
||||
name="fk_listener_client_authentication_mode_name"),
|
||||
nullable=False, default=constants.CLIENT_AUTH_NONE)
|
||||
|
||||
_tags = orm.relationship(
|
||||
'Tags',
|
||||
@ -762,3 +768,10 @@ class Flavor(base_models.BASE,
|
||||
sa.ForeignKey("flavor_profile.id",
|
||||
name="fk_flavor_flavor_profile_id"),
|
||||
nullable=False)
|
||||
|
||||
|
||||
class ClientAuthenticationMode(base_models.BASE):
|
||||
|
||||
__tablename__ = "client_authentication_mode"
|
||||
|
||||
name = sa.Column(sa.String(10), primary_key=True, nullable=False)
|
||||
|
@ -101,6 +101,8 @@ def create_listener(listener_dict, lb_id):
|
||||
for sni_container_id in sni_container_ids]
|
||||
listener_dict['sni_containers'] = sni_containers
|
||||
|
||||
if 'client_authentication' not in listener_dict:
|
||||
listener_dict['client_authentication'] = constants.CLIENT_AUTH_NONE
|
||||
return listener_dict
|
||||
|
||||
|
||||
|
@ -957,6 +957,46 @@ class TestListener(base.BaseAPITest):
|
||||
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'))
|
||||
|
||||
def test_create_with_ca_cert_and_option(self):
|
||||
self.cert_manager_mock().get_secret.return_value = (
|
||||
sample_certs.X509_CA_CERT)
|
||||
optionals = {
|
||||
'client_ca_tls_container_ref': uuidutils.generate_uuid(),
|
||||
'client_authentication': constants.CLIENT_AUTH_MANDATORY
|
||||
}
|
||||
listener_api = self.test_create(**optionals)
|
||||
self.assertEqual(optionals['client_ca_tls_container_ref'],
|
||||
listener_api.get('client_ca_tls_container_ref'))
|
||||
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.
|
||||
optionals = {
|
||||
'client_authentication': constants.CLIENT_AUTH_MANDATORY
|
||||
}
|
||||
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}
|
||||
lb_listener.update(optionals)
|
||||
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'])
|
||||
|
||||
def test_create_with_bad_ca_cert_ref(self):
|
||||
sni1 = uuidutils.generate_uuid()
|
||||
@ -1163,6 +1203,70 @@ class TestListener(base.BaseAPITest):
|
||||
self.assertNotEqual(ori_listener['client_ca_tls_container_ref'],
|
||||
optionals['client_ca_tls_container_ref'])
|
||||
|
||||
def test_update_with_only_client_auth_option(self):
|
||||
optionals = {
|
||||
'client_authentication': constants.CLIENT_AUTH_OPTIONAL
|
||||
}
|
||||
ori_listener, update_listener = self.test_update(**optionals)
|
||||
self.assertEqual(optionals['client_authentication'],
|
||||
update_listener.get('client_authentication'))
|
||||
self.assertNotEqual(ori_listener['client_authentication'],
|
||||
optionals['client_authentication'])
|
||||
|
||||
@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')
|
||||
mock_cert_data.return_value = {'tls_cert': cert1}
|
||||
self.cert_manager_mock().get_secret.return_value = (
|
||||
sample_certs.X509_CA_CERT)
|
||||
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)
|
||||
ca_tls_uuid = uuidutils.generate_uuid()
|
||||
new_listener = {
|
||||
'client_ca_tls_container_ref': ca_tls_uuid}
|
||||
body = self._build_body(new_listener)
|
||||
listener_path = self.LISTENER_PATH.format(
|
||||
listener_id=listener['id'])
|
||||
api_listener = self.put(listener_path, body).json.get(self.root_tag)
|
||||
update_expect = {'provisioning_status': constants.PENDING_UPDATE,
|
||||
'operating_status': constants.ONLINE}
|
||||
update_expect.update(new_listener)
|
||||
listener.update(update_expect)
|
||||
self.assertEqual(ca_tls_uuid,
|
||||
api_listener['client_ca_tls_container_ref'])
|
||||
self.assertEqual(constants.CLIENT_AUTH_NONE,
|
||||
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):
|
||||
# update a listener, no ca cert exist
|
||||
cert1 = data_models.TLSContainer(certificate='cert 1')
|
||||
mock_cert_data.return_value = {'tls_cert': cert1}
|
||||
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)
|
||||
lb_listener = {
|
||||
'client_authentication': constants.CLIENT_AUTH_OPTIONAL}
|
||||
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.assertEqual(
|
||||
"Validation failure: Client authentication setting %s "
|
||||
"requires a client CA container reference." %
|
||||
constants.CLIENT_AUTH_OPTIONAL, response['faultstring'])
|
||||
|
||||
@mock.patch('octavia.common.tls_utils.cert_parser.load_certificates_data')
|
||||
def test_update_unset_ca_cert(self, mock_cert_data):
|
||||
cert1 = data_models.TLSContainer(certificate='cert 1')
|
||||
@ -1185,6 +1289,7 @@ class TestListener(base.BaseAPITest):
|
||||
listener_id=listener['id'])
|
||||
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'))
|
||||
|
||||
@mock.patch('octavia.common.tls_utils.cert_parser.load_certificates_data')
|
||||
def test_update_with_bad_ca_cert(self, mock_cert_data):
|
||||
|
@ -2349,16 +2349,16 @@ class TestLoadBalancerGraph(base.BaseAPITest):
|
||||
expected_lb['pools'] = create_pools or []
|
||||
return create_lb, expected_lb
|
||||
|
||||
def _get_listener_bodies(self, name='listener1', protocol_port=80,
|
||||
create_default_pool_name=None,
|
||||
create_default_pool_id=None,
|
||||
create_l7policies=None,
|
||||
expected_l7policies=None,
|
||||
create_sni_containers=None,
|
||||
expected_sni_containers=None,
|
||||
def _get_listener_bodies(
|
||||
self, name='listener1', protocol_port=80,
|
||||
create_default_pool_name=None, create_default_pool_id=None,
|
||||
create_l7policies=None, expected_l7policies=None,
|
||||
create_sni_containers=None, expected_sni_containers=None,
|
||||
create_client_ca_tls_container=None,
|
||||
expected_client_ca_tls_container=None,
|
||||
create_protocol=constants.PROTOCOL_HTTP):
|
||||
create_protocol=constants.PROTOCOL_HTTP,
|
||||
create_client_authentication=None,
|
||||
expected_client_authentication=constants.CLIENT_AUTH_NONE):
|
||||
create_listener = {
|
||||
'name': name,
|
||||
'protocol_port': protocol_port,
|
||||
@ -2379,7 +2379,8 @@ class TestLoadBalancerGraph(base.BaseAPITest):
|
||||
'timeout_member_data': constants.DEFAULT_TIMEOUT_MEMBER_DATA,
|
||||
'timeout_tcp_inspect': constants.DEFAULT_TIMEOUT_TCP_INSPECT,
|
||||
'tags': [],
|
||||
'client_ca_tls_container_ref': None
|
||||
'client_ca_tls_container_ref': None,
|
||||
'client_authentication': constants.CLIENT_AUTH_NONE
|
||||
}
|
||||
if create_sni_containers:
|
||||
create_listener['sni_container_refs'] = create_sni_containers
|
||||
@ -2398,6 +2399,9 @@ class TestLoadBalancerGraph(base.BaseAPITest):
|
||||
if create_client_ca_tls_container:
|
||||
create_listener['client_ca_tls_container_ref'] = (
|
||||
create_client_ca_tls_container)
|
||||
if create_client_authentication:
|
||||
create_listener['client_authentication'] = (
|
||||
create_client_authentication)
|
||||
if expected_sni_containers:
|
||||
expected_listener['sni_container_refs'] = expected_sni_containers
|
||||
if expected_l7policies:
|
||||
@ -2407,6 +2411,11 @@ class TestLoadBalancerGraph(base.BaseAPITest):
|
||||
if expected_client_ca_tls_container:
|
||||
expected_listener['client_ca_tls_container_ref'] = (
|
||||
expected_client_ca_tls_container)
|
||||
expected_listener['client_authentication'] = (
|
||||
constants.CLIENT_AUTH_NONE)
|
||||
if expected_client_authentication:
|
||||
expected_listener[
|
||||
'client_authentication'] = expected_client_authentication
|
||||
return create_listener, expected_listener
|
||||
|
||||
def _get_pool_bodies(self, name='pool1', create_members=None,
|
||||
@ -2670,6 +2679,8 @@ class TestLoadBalancerGraph(base.BaseAPITest):
|
||||
mock_x509_cert.return_value = cert_mock
|
||||
create_client_ca_tls_container = 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
|
||||
create_sni_containers, expected_sni_containers = (
|
||||
self._get_sni_container_bodies())
|
||||
create_listener, expected_listener = self._get_listener_bodies(
|
||||
@ -2677,7 +2688,9 @@ class TestLoadBalancerGraph(base.BaseAPITest):
|
||||
create_sni_containers=create_sni_containers,
|
||||
expected_sni_containers=expected_sni_containers,
|
||||
create_client_ca_tls_container=create_client_ca_tls_container,
|
||||
expected_client_ca_tls_container=expected_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)
|
||||
create_lb, expected_lb = self._get_lb_bodies(
|
||||
create_listeners=[create_listener],
|
||||
expected_listeners=[expected_listener])
|
||||
|
@ -79,6 +79,8 @@ class OctaviaDBTestBase(test_base.DbTestCase):
|
||||
models.L7RuleCompareType)
|
||||
self._seed_lookup_table(session, constants.SUPPORTED_L7POLICY_ACTIONS,
|
||||
models.L7PolicyAction)
|
||||
self._seed_lookup_table(session, constants.SUPPORTED_CLIENT_AUTH_MODES,
|
||||
models.ClientAuthenticationMode)
|
||||
|
||||
def _seed_lookup_table(self, session, name_list, model_cls):
|
||||
for name in name_list:
|
||||
|
@ -382,7 +382,8 @@ class SampleDriverDataModels(object):
|
||||
'timeout_member_connect': 2000,
|
||||
'timeout_member_data': 3000,
|
||||
'timeout_tcp_inspect': 4000,
|
||||
'client_ca_tls_certificate_id': self.client_ca_tls_certificate_ref
|
||||
'client_ca_tls_certificate_id': self.client_ca_tls_certificate_ref,
|
||||
'client_authentication': constants.CLIENT_AUTH_NONE
|
||||
}
|
||||
|
||||
self.test_listener1_dict.update(self._common_test_dict)
|
||||
@ -439,7 +440,8 @@ class SampleDriverDataModels(object):
|
||||
'timeout_member_data': 3000,
|
||||
'timeout_tcp_inspect': 4000,
|
||||
'client_ca_tls_container_ref': self.client_ca_tls_certificate_ref,
|
||||
'client_ca_tls_container_data': ca_cert
|
||||
'client_ca_tls_container_data': ca_cert,
|
||||
'client_authentication': constants.CLIENT_AUTH_NONE
|
||||
}
|
||||
|
||||
self.provider_listener2_dict = copy.deepcopy(
|
||||
@ -452,6 +454,8 @@ class SampleDriverDataModels(object):
|
||||
del self.provider_listener2_dict['l7policies']
|
||||
self.provider_listener2_dict['client_ca_tls_container_ref'] = None
|
||||
del self.provider_listener2_dict['client_ca_tls_container_data']
|
||||
self.provider_listener2_dict['client_authentication'] = (
|
||||
constants.CLIENT_AUTH_NONE)
|
||||
|
||||
self.provider_listener1 = driver_dm.Listener(
|
||||
**self.provider_listener1_dict)
|
||||
|
@ -40,7 +40,7 @@ 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\n"
|
||||
"client_ca.pem verify required\n"
|
||||
" mode http\n"
|
||||
" default_backend sample_pool_id_1\n"
|
||||
" timeout client 50000\n\n").format(
|
||||
|
@ -528,7 +528,7 @@ def sample_listener_tuple(proto=None, monitor=True, alloc_default_pool=True,
|
||||
'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_ca_tls_certificate, client_authentication')
|
||||
if l7:
|
||||
pools = [
|
||||
sample_pool_tuple(
|
||||
@ -611,7 +611,10 @@ def sample_listener_tuple(proto=None, monitor=True, alloc_default_pool=True,
|
||||
client_ca_tls_certificate=sample_tls_container_tuple(
|
||||
id='cont_id_ca', certificate=sample_certs.X509_CA_CERT,
|
||||
primary_cn=sample_certs.X509_CA_CERT_CN
|
||||
) if client_ca_cert else ''
|
||||
) if client_ca_cert else '',
|
||||
client_authentication=(
|
||||
constants.CLIENT_AUTH_MANDATORY if client_ca_cert else
|
||||
constants.CLIENT_AUTH_NONE)
|
||||
)
|
||||
|
||||
|
||||
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
You can now enable TLS client authentication on listeners.
|
Loading…
Reference in New Issue
Block a user