L7rule support client certificate cases
This patch add 4 new types for SSL connection ACL configuration. Which are: L7RULE_TYPE_SSL_CONN_HAS_CERT L7RULE_TYPE_VERIFY_RESULT L7RULE_TYPE_DN_FIELD The first type can just accept the compare type "EQUAL_TO" and value "True" string. The second can just accept the int value string to check the certificate verify result, also just support "EQUAL_TO" compare type. The third can accept key, the distinguished name field and a match string, this one supports all kind compare types. Story: 2002165 Task: 20025 Co-Authored-By: Michael Johnson <johnsomor@gmail.com> Change-Id: I71b57d0f32d4839a770396645d2b9945d24f2853
This commit is contained in:
parent
aa1bca0271
commit
f77d7d0220
@ -770,14 +770,16 @@ l7rule-key-optional:
|
||||
l7rule-type:
|
||||
description: |
|
||||
The L7 rule type. One of ``COOKIE``, ``FILE_TYPE``, ``HEADER``,
|
||||
``HOST_NAME``, or ``PATH``.
|
||||
``HOST_NAME``, ``PATH``, ``SSL_CONN_HAS_CERT``, ``SSL_VERIFY_RESULT``,
|
||||
or ``SSL_DN_FIELD``.
|
||||
in: body
|
||||
required: true
|
||||
type: string
|
||||
l7rule-type-optional:
|
||||
description: |
|
||||
The L7 rule type. One of ``COOKIE``, ``FILE_TYPE``, ``HEADER``,
|
||||
``HOST_NAME``, or ``PATH``.
|
||||
``HOST_NAME``, ``PATH``, ``SSL_CONN_HAS_CERT``, ``SSL_VERIFY_RESULT``,
|
||||
or ``SSL_DN_FIELD``.
|
||||
in: body
|
||||
required: false
|
||||
type: string
|
||||
|
@ -354,3 +354,67 @@ sent to *static_pool_B*, which is why *policy2* needs to be evaluated before
|
||||
openstack loadbalancer l7rule create --compare-type EQUAL_TO --key site_version --type COOKIE --value B policy2
|
||||
openstack loadbalancer l7policy create --action REDIRECT_TO_POOL --redirect-pool pool_B --name policy3 --position 2 listener1
|
||||
openstack loadbalancer l7rule create --compare-type EQUAL_TO --key site_version --type COOKIE --value B policy3
|
||||
|
||||
|
||||
Redirect requests with an invalid TLS client authentication certificate
|
||||
-----------------------------------------------------------------------
|
||||
**Scenario description**:
|
||||
|
||||
* Listener *listener1* on load balancer *lb1* is configured for ``OPTIONAL``
|
||||
client_authentication.
|
||||
* Web clients that do not present a TLS client authentication certificate
|
||||
should be redirected to a signup page at *http://www.example.com/signup*.
|
||||
|
||||
**Solution**:
|
||||
|
||||
1. Create the load balancer *lb1*.
|
||||
2. Create a listener *listner1* of type ``TERMINATED_TLS`` with a
|
||||
client_ca_tls_container_ref and client_authentication ``OPTIONAL``.
|
||||
3. Create a L7 Policy *policy1* on *listener1* with action ``REDIRECT_TO_URL``
|
||||
pointed at the URL *http://www.example.com/signup*.
|
||||
4. Add an L7 Rule to *policy1* that does not match ``SSL_CONN_HAS_CERT``.
|
||||
|
||||
**CLI commands**:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
openstack loadbalancer create --name lb1 --vip-subnet-id public-subnet
|
||||
openstack loadbalancer listener create --name listener1 --protocol TERMINATED_HTTPS --client-authentication OPTIONAL --protocol-port 443 --default-tls-container-ref http://192.0.2.15:9311/v1/secrets/697c2a6d-ffbe-40b8-be5e-7629fd636bca --client-ca-tls-container-ref http://192.0.2.15:9311/v1/secrets/dba60b77-8dad-4171-8a96-f21e1ca5fb46 lb1
|
||||
openstack loadbalancer l7policy create --action REDIRECT_TO_URL --redirect-url http://www.example.com/signup --name policy1 listener1
|
||||
openstack loadbalancer l7rule create --type SSL_CONN_HAS_CERT --invert --compare-type EQUAL_TO --value True policy1
|
||||
|
||||
|
||||
Send users from the finance department to pool2
|
||||
-----------------------------------------------
|
||||
**Scenario description**:
|
||||
|
||||
* Users from the finance department have client certificates with the OU field
|
||||
of the distinguished name set to ``finance``.
|
||||
* Only users with valid finance department client certificates should be able
|
||||
to access ``pool2``. Others will be rejected.
|
||||
|
||||
**Solution**:
|
||||
|
||||
1. Create the load balancer *lb1*.
|
||||
2. Create a listener *listner1* of type ``TERMINATED_TLS`` with a
|
||||
client_ca_tls_container_ref and client_authentication ``MANDATORY``.
|
||||
3. Create a pool *pool2* on load balancer *lb1*.
|
||||
4. Create a L7 Policy *policy1* on *listener1* with action ``REDIRECT_TO_POOL``
|
||||
pointed at *pool2*.
|
||||
5. Add an L7 Rule to *policy1* that matches ``SSL_CONN_HAS_CERT``.
|
||||
6. Add an L7 Rule to *policy1* that matches ``SSL_VERIFY_RESULT`` with a value
|
||||
of 0.
|
||||
7. Add an L7 Rule to *policy1* of type ``SSL_DN_FIELD`` that looks for
|
||||
"finance" in the "OU" field of the client authentication distinguished name.
|
||||
|
||||
**CLI commands**:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
openstack loadbalancer create --name lb1 --vip-subnet-id public-subnet
|
||||
openstack loadbalancer listener create --name listener1 --protocol TERMINATED_HTTPS --client-authentication MANDATORY --protocol-port 443 --default-tls-container-ref http://192.0.2.15:9311/v1/secrets/697c2a6d-ffbe-40b8-be5e-7629fd636bca --client-ca-tls-container-ref http://192.0.2.15:9311/v1/secrets/dba60b77-8dad-4171-8a96-f21e1ca5fb46 lb1
|
||||
openstack loadbalancer pool create --lb-algorithm ROUND_ROBIN --loadbalancer lb1 --name pool2 --protocol HTTP
|
||||
openstack loadbalancer l7policy create --action REDIRECT_TO_POOL --redirect-pool pool2 --name policy1 listener1
|
||||
openstack loadbalancer l7rule create --type SSL_CONN_HAS_CERT --compare-type EQUAL_TO --value True policy1
|
||||
openstack loadbalancer l7rule create --type SSL_VERIFY_RESULT --compare-type EQUAL_TO --value 0 policy1
|
||||
openstack loadbalancer l7rule create --type SSL_DN_FIELD --compare-type EQUAL_TO --key OU --value finance policy1
|
||||
|
@ -86,6 +86,15 @@ L7 rules have the following types:
|
||||
compares it against the value parameter in the rule.
|
||||
* ``COOKIE``: The rule looks for a cookie named by the key parameter and
|
||||
compares it against the value parameter in the rule.
|
||||
* ``SSL_CONN_HAS_CERT``: The rule will match if the client has presented a
|
||||
certificate for TLS client authentication. This does not imply the
|
||||
certificate is valid.
|
||||
* ``SSL_VERIFY_RESULT``: This rule will match the TLS client authentication
|
||||
certificate validation result. A value of '0' means the certificate was
|
||||
successfully validated. A value greater than '0' means the certificate
|
||||
failed validation. This value follows the `openssl-verify result codes <https://github.com/openssl/openssl/blob/master/include/openssl/x509_vfy.h#L99>`_.
|
||||
* ``SSL_DN_FIELD``: The rule looks for a Distinguished Name feild defined in
|
||||
the key parameter and compares it against the value parameter in the rule.
|
||||
|
||||
Comparison types
|
||||
________________
|
||||
@ -183,3 +192,4 @@ Useful links
|
||||
* `Octavia API Reference <https://developer.openstack.org/api-ref/load-balancer/>`_
|
||||
* `LBaaS Layer 7 rules <https://github.com/openstack/neutron-specs/blob/master/specs/mitaka/lbaas-l7-rules.rst>`_
|
||||
* `Using ACLs and fetching samples <http://cbonte.github.io/haproxy-dconv/1.6/configuration.html#7>`_
|
||||
* `OpenSSL openssl-verify command <https://www.openssl.org/docs/manmaster/man1/openssl-verify.html>`_
|
||||
|
@ -143,9 +143,15 @@ L7RULE_TYPE_PATH = 'PATH'
|
||||
L7RULE_TYPE_FILE_TYPE = 'FILE_TYPE'
|
||||
L7RULE_TYPE_HEADER = 'HEADER'
|
||||
L7RULE_TYPE_COOKIE = 'COOKIE'
|
||||
L7RULE_TYPE_SSL_CONN_HAS_CERT = 'SSL_CONN_HAS_CERT'
|
||||
L7RULE_TYPE_SSL_VERIFY_RESULT = 'SSL_VERIFY_RESULT'
|
||||
L7RULE_TYPE_SSL_DN_FIELD = 'SSL_DN_FIELD'
|
||||
SUPPORTED_L7RULE_TYPES = (L7RULE_TYPE_HOST_NAME, L7RULE_TYPE_PATH,
|
||||
L7RULE_TYPE_FILE_TYPE, L7RULE_TYPE_HEADER,
|
||||
L7RULE_TYPE_COOKIE)
|
||||
L7RULE_TYPE_COOKIE, L7RULE_TYPE_SSL_CONN_HAS_CERT,
|
||||
L7RULE_TYPE_SSL_VERIFY_RESULT,
|
||||
L7RULE_TYPE_SSL_DN_FIELD)
|
||||
DISTINGUISHED_NAME_FIELD_REGEX = '^([a-zA-Z][A-Za-z0-9-]*)$'
|
||||
|
||||
L7RULE_COMPARE_TYPE_REGEX = 'REGEX'
|
||||
L7RULE_COMPARE_TYPE_STARTS_WITH = 'STARTS_WITH'
|
||||
|
@ -86,6 +86,14 @@ bind {{ lb_vip_address }}:{{ listener.protocol_port }} {{
|
||||
acl {{ l7rule.id }} req.cook({{ l7rule.key }}) {{
|
||||
l7rule_compare_type_macro(
|
||||
constants, l7rule.compare_type) }} {{ l7rule.value }}
|
||||
{% elif l7rule.type == constants.L7RULE_TYPE_SSL_CONN_HAS_CERT %}
|
||||
acl {{ l7rule.id }} ssl_c_used
|
||||
{% elif l7rule.type == constants.L7RULE_TYPE_SSL_VERIFY_RESULT %}
|
||||
acl {{ l7rule.id }} ssl_c_verify eq {{ l7rule.value }}
|
||||
{% elif l7rule.type == constants.L7RULE_TYPE_SSL_DN_FIELD %}
|
||||
acl {{ l7rule.id }} ssl_c_s_dn({{ l7rule.key }}) {{
|
||||
l7rule_compare_type_macro(
|
||||
constants, l7rule.compare_type) }} {{ l7rule.value }}
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
|
@ -26,6 +26,7 @@ import netaddr
|
||||
from oslo_config import cfg
|
||||
import rfc3986
|
||||
import six
|
||||
from wsme import types as wtypes
|
||||
|
||||
from octavia.common import constants
|
||||
from octavia.common import exceptions
|
||||
@ -161,11 +162,72 @@ def l7rule_data(l7rule):
|
||||
raise exceptions.InvalidL7Rule(msg='invalid comparison type '
|
||||
'for rule type')
|
||||
|
||||
elif l7rule.type in [constants.L7RULE_TYPE_SSL_CONN_HAS_CERT,
|
||||
constants.L7RULE_TYPE_SSL_VERIFY_RESULT,
|
||||
constants.L7RULE_TYPE_SSL_DN_FIELD]:
|
||||
validate_l7rule_ssl_types(l7rule)
|
||||
|
||||
else:
|
||||
raise exceptions.InvalidL7Rule(msg='invalid rule type')
|
||||
return True
|
||||
|
||||
|
||||
def validate_l7rule_ssl_types(l7rule):
|
||||
if not l7rule.type or l7rule.type not in [
|
||||
constants.L7RULE_TYPE_SSL_CONN_HAS_CERT,
|
||||
constants.L7RULE_TYPE_SSL_VERIFY_RESULT,
|
||||
constants.L7RULE_TYPE_SSL_DN_FIELD]:
|
||||
return
|
||||
|
||||
rule_type = None if l7rule.type == wtypes.Unset else l7rule.type
|
||||
req_key = None if l7rule.key == wtypes.Unset else l7rule.key
|
||||
req_value = None if l7rule.value == wtypes.Unset else l7rule.value
|
||||
compare_type = (None if l7rule.compare_type == wtypes.Unset else
|
||||
l7rule.compare_type)
|
||||
msg = None
|
||||
if rule_type == constants.L7RULE_TYPE_SSL_CONN_HAS_CERT:
|
||||
# key and value are not allowed
|
||||
if req_key:
|
||||
# log error or raise
|
||||
msg = 'L7rule type {0} does not use the "key" field.'.format(
|
||||
rule_type)
|
||||
elif req_value.lower() != 'true':
|
||||
msg = 'L7rule value {0} is not a boolean True string.'.format(
|
||||
req_value)
|
||||
elif compare_type != constants.L7RULE_COMPARE_TYPE_EQUAL_TO:
|
||||
msg = 'L7rule type {0} only supports the {1} compare type.'.format(
|
||||
rule_type, constants.L7RULE_COMPARE_TYPE_EQUAL_TO)
|
||||
|
||||
if rule_type == constants.L7RULE_TYPE_SSL_VERIFY_RESULT:
|
||||
if req_key:
|
||||
# log or raise req_key not used
|
||||
msg = 'L7rule type {0} does not use the "key" field.'.format(
|
||||
rule_type)
|
||||
elif not req_value.isdigit() or int(req_value) < 0:
|
||||
# log or raise req_value must be int
|
||||
msg = 'L7rule type {0} needs a int value, which is >= 0'.format(
|
||||
rule_type)
|
||||
elif compare_type != constants.L7RULE_COMPARE_TYPE_EQUAL_TO:
|
||||
msg = 'L7rule type {0} only supports the {1} compare type.'.format(
|
||||
rule_type, constants.L7RULE_COMPARE_TYPE_EQUAL_TO)
|
||||
|
||||
if rule_type == constants.L7RULE_TYPE_SSL_DN_FIELD:
|
||||
dn_regex = re.compile(constants.DISTINGUISHED_NAME_FIELD_REGEX)
|
||||
if compare_type == constants.L7RULE_COMPARE_TYPE_REGEX:
|
||||
regex(l7rule.value)
|
||||
|
||||
if not req_key or not req_value:
|
||||
# log or raise key and value must be specified.
|
||||
msg = 'L7rule type {0} needs to specify a key and a value.'.format(
|
||||
rule_type)
|
||||
# log or raise the key must be splited by '-'
|
||||
elif not dn_regex.match(req_key):
|
||||
msg = ('Invalid L7rule distinguished name field.')
|
||||
|
||||
if msg:
|
||||
raise exceptions.InvalidL7Rule(msg=msg)
|
||||
|
||||
|
||||
def sanitize_l7policy_api_args(l7policy, create=False):
|
||||
"""Validate and make consistent L7Policy API arguments.
|
||||
|
||||
|
@ -0,0 +1,44 @@
|
||||
# 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.
|
||||
#
|
||||
|
||||
"""Extend the l7rule type for support client certificate cases
|
||||
|
||||
Revision ID: 1afc932f1ca2
|
||||
Revises: ffad172e98c1
|
||||
Create Date: 2018-10-03 20:47:52.405865
|
||||
|
||||
"""
|
||||
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy import sql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '1afc932f1ca2'
|
||||
down_revision = 'ffad172e98c1'
|
||||
|
||||
new_fields = ['SSL_CONN_HAS_CERT', 'SSL_VERIFY_RESULT', 'SSL_DN_FIELD']
|
||||
|
||||
|
||||
def upgrade():
|
||||
|
||||
insert_table = sql.table(
|
||||
u'l7rule_type',
|
||||
sql.column(u'name', sa.String),
|
||||
sql.column(u'description', sa.String)
|
||||
)
|
||||
cows = [{'name': field} for field in new_fields]
|
||||
op.bulk_insert(insert_table, cows)
|
@ -682,6 +682,135 @@ class TestL7Rule(base.BaseAPITest):
|
||||
self.assertIn('Provider \'bad_driver\' reports error: broken',
|
||||
response.json.get('faultstring'))
|
||||
|
||||
def test_create_with_ssl_rule_types(self):
|
||||
test_mapping = {
|
||||
constants.L7RULE_TYPE_SSL_CONN_HAS_CERT: {
|
||||
'value': 'tRuE',
|
||||
'compare_type': constants.L7RULE_COMPARE_TYPE_EQUAL_TO},
|
||||
constants.L7RULE_TYPE_SSL_VERIFY_RESULT: {
|
||||
'value': '0',
|
||||
'compare_type': constants.L7RULE_COMPARE_TYPE_EQUAL_TO},
|
||||
constants.L7RULE_TYPE_SSL_DN_FIELD: {
|
||||
'key': 'st-1', 'value': 'ST-FIELD1-PREFIX',
|
||||
'compare_type': constants.L7RULE_COMPARE_TYPE_STARTS_WITH}
|
||||
}
|
||||
for l7rule_type, test_body in test_mapping.items():
|
||||
self.set_lb_status(self.lb_id)
|
||||
test_body.update({'type': l7rule_type})
|
||||
api_l7rule = self.create_l7rule(
|
||||
self.l7policy_id, l7rule_type,
|
||||
test_body['compare_type'], test_body['value'],
|
||||
key=test_body.get('key')).get(self.root_tag)
|
||||
self.assertEqual(l7rule_type, api_l7rule.get('type'))
|
||||
self.assertEqual(test_body['compare_type'],
|
||||
api_l7rule.get('compare_type'))
|
||||
self.assertEqual(test_body['value'], api_l7rule.get('value'))
|
||||
if test_body.get('key'):
|
||||
self.assertEqual(test_body['key'], api_l7rule.get('key'))
|
||||
self.assertFalse(api_l7rule.get('invert'))
|
||||
self.assert_correct_status(
|
||||
lb_id=self.lb_id, listener_id=self.listener_id,
|
||||
l7policy_id=self.l7policy_id, l7rule_id=api_l7rule.get('id'),
|
||||
lb_prov_status=constants.PENDING_UPDATE,
|
||||
listener_prov_status=constants.PENDING_UPDATE,
|
||||
l7policy_prov_status=constants.PENDING_UPDATE,
|
||||
l7rule_prov_status=constants.PENDING_CREATE,
|
||||
l7rule_op_status=constants.OFFLINE)
|
||||
|
||||
def _test_bad_cases_with_ssl_rule_types(self, is_create=True,
|
||||
rule_id=None):
|
||||
if is_create:
|
||||
req_func = self.post
|
||||
first_req_arg = self.l7rules_path
|
||||
else:
|
||||
req_func = self.put
|
||||
first_req_arg = self.l7rule_path.format(l7rule_id=rule_id)
|
||||
|
||||
# test bad cases of L7RULE_TYPE_SSL_CONN_HAS_CERT
|
||||
l7rule = {'compare_type': constants.L7RULE_COMPARE_TYPE_EQUAL_TO,
|
||||
'invert': False,
|
||||
'type': constants.L7RULE_TYPE_SSL_CONN_HAS_CERT,
|
||||
'value': 'true',
|
||||
'admin_state_up': True,
|
||||
'key': 'no-need-key'}
|
||||
response = req_func(first_req_arg, self._build_body(l7rule),
|
||||
status=400).json
|
||||
self.assertIn('L7rule type {0} does not use the "key" field.'.format(
|
||||
constants.L7RULE_TYPE_SSL_CONN_HAS_CERT),
|
||||
response.get('faultstring'))
|
||||
|
||||
l7rule.pop('key')
|
||||
l7rule['value'] = 'not-true-string'
|
||||
response = req_func(first_req_arg, self._build_body(l7rule),
|
||||
status=400).json
|
||||
self.assertIn(
|
||||
'L7rule value {0} is not a boolean True string.'.format(
|
||||
l7rule['value']), response.get('faultstring'))
|
||||
|
||||
l7rule['value'] = 'tRUe'
|
||||
l7rule['compare_type'] = constants.L7RULE_COMPARE_TYPE_STARTS_WITH
|
||||
response = req_func(first_req_arg, self._build_body(l7rule),
|
||||
status=400).json
|
||||
self.assertIn(
|
||||
'L7rule type {0} only supports the {1} compare type.'.format(
|
||||
constants.L7RULE_TYPE_SSL_CONN_HAS_CERT,
|
||||
constants.L7RULE_COMPARE_TYPE_EQUAL_TO),
|
||||
response.get('faultstring'))
|
||||
|
||||
# test bad cases of L7RULE_TYPE_SSL_VERIFY_RES
|
||||
l7rule = {'compare_type': constants.L7RULE_COMPARE_TYPE_EQUAL_TO,
|
||||
'invert': False,
|
||||
'type': constants.L7RULE_TYPE_SSL_VERIFY_RESULT,
|
||||
'value': 'true',
|
||||
'admin_state_up': True,
|
||||
'key': 'no-need-key'}
|
||||
response = req_func(first_req_arg, self._build_body(l7rule),
|
||||
status=400).json
|
||||
self.assertIn(
|
||||
'L7rule type {0} does not use the "key" field.'.format(
|
||||
l7rule['type']), response.get('faultstring'))
|
||||
|
||||
l7rule.pop('key')
|
||||
response = req_func(first_req_arg, self._build_body(l7rule),
|
||||
status=400).json
|
||||
self.assertIn(
|
||||
'L7rule type {0} needs a int value, which is >= 0'.format(
|
||||
l7rule['type']), response.get('faultstring'))
|
||||
|
||||
l7rule['value'] = '0'
|
||||
l7rule['compare_type'] = constants.L7RULE_COMPARE_TYPE_STARTS_WITH
|
||||
response = req_func(first_req_arg, self._build_body(l7rule),
|
||||
status=400).json
|
||||
self.assertIn(
|
||||
'L7rule type {0} only supports the {1} compare type.'.format(
|
||||
l7rule['type'], constants.L7RULE_COMPARE_TYPE_EQUAL_TO),
|
||||
response.get('faultstring'))
|
||||
|
||||
# test bad cases of L7RULE_TYPE_SSL_DN_FIELD
|
||||
l7rule = {'compare_type': constants.L7RULE_COMPARE_TYPE_REGEX,
|
||||
'invert': False,
|
||||
'type': constants.L7RULE_TYPE_SSL_DN_FIELD,
|
||||
'value': 'bad regex\\',
|
||||
'admin_state_up': True}
|
||||
# This case just test that fail to parse the regex from the value
|
||||
req_func(first_req_arg, self._build_body(l7rule), status=400).json
|
||||
|
||||
l7rule['value'] = '^.test*$'
|
||||
response = req_func(first_req_arg, self._build_body(l7rule),
|
||||
status=400).json
|
||||
self.assertIn(
|
||||
'L7rule type {0} needs to specify a key and a value.'.format(
|
||||
l7rule['type']), response.get('faultstring'))
|
||||
|
||||
l7rule['key'] = 'NOT_SUPPORTED_DN_FIELD'
|
||||
response = req_func(first_req_arg, self._build_body(l7rule),
|
||||
status=400).json
|
||||
self.assertIn('Invalid L7rule distinguished name field.',
|
||||
response.get('faultstring'))
|
||||
|
||||
def test_create_bad_cases_with_ssl_rule_types(self):
|
||||
self._test_bad_cases_with_ssl_rule_types()
|
||||
|
||||
def test_update(self):
|
||||
api_l7rule = self.create_l7rule(
|
||||
self.l7policy_id, constants.L7RULE_TYPE_PATH,
|
||||
@ -811,6 +940,53 @@ class TestL7Rule(base.BaseAPITest):
|
||||
l7policy_id=self.l7policy_id, l7rule_id=api_l7rule.get('id'),
|
||||
l7rule_prov_status=constants.ACTIVE)
|
||||
|
||||
def test_update_with_ssl_rule_types(self):
|
||||
test_mapping = {
|
||||
constants.L7RULE_TYPE_SSL_CONN_HAS_CERT: {
|
||||
'value': 'tRuE',
|
||||
'compare_type': constants.L7RULE_COMPARE_TYPE_EQUAL_TO},
|
||||
constants.L7RULE_TYPE_SSL_VERIFY_RESULT: {
|
||||
'value': '0',
|
||||
'compare_type': constants.L7RULE_COMPARE_TYPE_EQUAL_TO},
|
||||
constants.L7RULE_TYPE_SSL_DN_FIELD: {
|
||||
'key': 'st-1', 'value': 'ST-FIELD1-PREFIX',
|
||||
'compare_type': constants.L7RULE_COMPARE_TYPE_STARTS_WITH}
|
||||
}
|
||||
|
||||
for l7rule_type, test_body in test_mapping.items():
|
||||
self.set_lb_status(self.lb_id)
|
||||
api_l7rule = self.create_l7rule(
|
||||
self.l7policy_id, constants.L7RULE_TYPE_PATH,
|
||||
constants.L7RULE_COMPARE_TYPE_STARTS_WITH,
|
||||
'/api').get(self.root_tag)
|
||||
self.set_lb_status(self.lb_id)
|
||||
test_body.update({'type': l7rule_type})
|
||||
response = self.put(self.l7rule_path.format(
|
||||
l7rule_id=api_l7rule.get('id')),
|
||||
self._build_body(test_body)).json.get(self.root_tag)
|
||||
self.assertEqual(l7rule_type, response.get('type'))
|
||||
self.assertEqual(test_body['compare_type'],
|
||||
response.get('compare_type'))
|
||||
self.assertEqual(test_body['value'], response.get('value'))
|
||||
if test_body.get('key'):
|
||||
self.assertEqual(test_body['key'], response.get('key'))
|
||||
self.assertFalse(response.get('invert'))
|
||||
self.assert_correct_status(
|
||||
lb_id=self.lb_id, listener_id=self.listener_id,
|
||||
l7policy_id=self.l7policy_id, l7rule_id=response.get('id'),
|
||||
lb_prov_status=constants.PENDING_UPDATE,
|
||||
listener_prov_status=constants.PENDING_UPDATE,
|
||||
l7policy_prov_status=constants.PENDING_UPDATE,
|
||||
l7rule_prov_status=constants.PENDING_UPDATE)
|
||||
|
||||
def test_update_bad_cases_with_ssl_rule_types(self):
|
||||
api_l7rule = self.create_l7rule(
|
||||
self.l7policy_id, constants.L7RULE_TYPE_PATH,
|
||||
constants.L7RULE_COMPARE_TYPE_STARTS_WITH,
|
||||
'/api').get(self.root_tag)
|
||||
self._test_bad_cases_with_ssl_rule_types(
|
||||
is_create=False, rule_id=api_l7rule.get('id'))
|
||||
|
||||
def test_delete(self):
|
||||
api_l7rule = self.create_l7rule(
|
||||
self.l7policy_id, constants.L7RULE_TYPE_PATH,
|
||||
|
@ -942,3 +942,85 @@ class TestHaproxyCfg(base.TestCase):
|
||||
self.assertEqual(
|
||||
sample_configs.sample_base_expected_config(backend=be),
|
||||
rendered_obj)
|
||||
|
||||
def test_ssl_types_l7rules(self):
|
||||
j_cfg = jinja_cfg.JinjaTemplater(
|
||||
base_amp_path='/var/lib/octavia',
|
||||
base_crt_dir='/var/lib/octavia/certs')
|
||||
fe = ("frontend sample_listener_id_1\n"
|
||||
" option httplog\n"
|
||||
" maxconn 1000000\n"
|
||||
" redirect scheme https if !{ ssl_fc }\n"
|
||||
" bind 10.0.0.2:443\n"
|
||||
" mode http\n"
|
||||
" acl sample_l7rule_id_1 path -m beg /api\n"
|
||||
" use_backend sample_pool_id_2 if sample_l7rule_id_1\n"
|
||||
" acl sample_l7rule_id_2 req.hdr(Some-header) -m sub "
|
||||
"This\\ string\\\\\\ with\\ stuff\n"
|
||||
" acl sample_l7rule_id_3 req.cook(some-cookie) -m reg "
|
||||
"this.*|that\n"
|
||||
" redirect location http://www.example.com "
|
||||
"if !sample_l7rule_id_2 sample_l7rule_id_3\n"
|
||||
" acl sample_l7rule_id_4 path_end -m str jpg\n"
|
||||
" acl sample_l7rule_id_5 req.hdr(host) -i -m end "
|
||||
".example.com\n"
|
||||
" http-request deny "
|
||||
"if sample_l7rule_id_4 sample_l7rule_id_5\n"
|
||||
" acl sample_l7rule_id_2 req.hdr(Some-header) -m sub "
|
||||
"This\\ string\\\\\\ with\\ stuff\n"
|
||||
" acl sample_l7rule_id_3 req.cook(some-cookie) -m reg "
|
||||
"this.*|that\n"
|
||||
" redirect prefix https://example.com "
|
||||
"if !sample_l7rule_id_2 sample_l7rule_id_3\n"
|
||||
" acl sample_l7rule_id_7 ssl_c_used\n"
|
||||
" acl sample_l7rule_id_8 ssl_c_verify eq 1\n"
|
||||
" acl sample_l7rule_id_9 ssl_c_s_dn(STREET) -m reg "
|
||||
"^STREET.*NO\\\\.$\n"
|
||||
" acl sample_l7rule_id_10 ssl_c_s_dn(OU-3) -m beg "
|
||||
"Orgnization\\ Bala\n"
|
||||
" acl sample_l7rule_id_11 path -m beg /api\n"
|
||||
" redirect location http://www.ssl-type-l7rule-test.com "
|
||||
"if sample_l7rule_id_7 !sample_l7rule_id_8 !sample_l7rule_id_9 "
|
||||
"!sample_l7rule_id_10 sample_l7rule_id_11\n"
|
||||
" default_backend sample_pool_id_1\n"
|
||||
" timeout client 50000\n\n")
|
||||
be = ("backend sample_pool_id_1\n"
|
||||
" mode http\n"
|
||||
" balance roundrobin\n"
|
||||
" cookie SRV insert indirect nocache\n"
|
||||
" timeout check 31s\n"
|
||||
" option httpchk GET /index.html\n"
|
||||
" http-check expect rstatus 418\n"
|
||||
" fullconn 1000000\n"
|
||||
" option allbackups\n"
|
||||
" timeout connect 5000\n"
|
||||
" timeout server 50000\n"
|
||||
" server sample_member_id_1 10.0.0.99:82 weight 13 check "
|
||||
"inter 30s fall 3 rise 2 cookie sample_member_id_1\n"
|
||||
" server sample_member_id_2 10.0.0.98:82 weight 13 check "
|
||||
"inter 30s fall 3 rise 2 cookie sample_member_id_2\n\n"
|
||||
"backend sample_pool_id_2\n"
|
||||
" mode http\n"
|
||||
" balance roundrobin\n"
|
||||
" cookie SRV insert indirect nocache\n"
|
||||
" timeout check 31s\n"
|
||||
" option httpchk GET /healthmon.html\n"
|
||||
" http-check expect rstatus 418\n"
|
||||
" fullconn 1000000\n"
|
||||
" option allbackups\n"
|
||||
" timeout connect 5000\n"
|
||||
" timeout server 50000\n"
|
||||
" server sample_member_id_3 10.0.0.97:82 weight 13 check "
|
||||
"inter 30s fall 3 rise 2 cookie sample_member_id_3\n\n")
|
||||
sample_listener = sample_configs.sample_listener_tuple(
|
||||
proto=constants.PROTOCOL_TERMINATED_HTTPS, l7=True,
|
||||
ssl_type_l7=True)
|
||||
rendered_obj = j_cfg.build_config(
|
||||
sample_configs.sample_amphora_tuple(),
|
||||
sample_listener,
|
||||
tls_cert=None,
|
||||
haproxy_versions=("1", "5", "18"))
|
||||
self.assertEqual(
|
||||
sample_configs.sample_base_expected_config(
|
||||
frontend=fe, backend=be),
|
||||
rendered_obj)
|
||||
|
@ -512,7 +512,8 @@ 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_crl_cert=False):
|
||||
client_ca_cert=False, client_crl_cert=False,
|
||||
ssl_type_l7=False):
|
||||
proto = 'HTTP' if proto is None else proto
|
||||
if be_proto is None:
|
||||
be_proto = 'HTTP' if proto is 'TERMINATED_HTTPS' else proto
|
||||
@ -550,6 +551,9 @@ def sample_listener_tuple(proto=None, monitor=True, alloc_default_pool=True,
|
||||
sample_l7policy_tuple('sample_l7policy_id_5', sample_policy=5),
|
||||
sample_l7policy_tuple('sample_l7policy_id_6', sample_policy=6),
|
||||
sample_l7policy_tuple('sample_l7policy_id_7', sample_policy=7)]
|
||||
if ssl_type_l7:
|
||||
l7policies.append(sample_l7policy_tuple(
|
||||
'sample_l7policy_id_8', sample_policy=8))
|
||||
else:
|
||||
pools = [
|
||||
sample_pool_tuple(
|
||||
@ -799,6 +803,14 @@ def sample_l7policy_tuple(id,
|
||||
redirect_prefix = 'https://example.com'
|
||||
l7rules = [sample_l7rule_tuple('sample_l7rule_id_2', sample_rule=2),
|
||||
sample_l7rule_tuple('sample_l7rule_id_3', sample_rule=3)]
|
||||
elif sample_policy == 8:
|
||||
action = constants.L7POLICY_ACTION_REDIRECT_TO_URL
|
||||
redirect_url = 'http://www.ssl-type-l7rule-test.com'
|
||||
l7rules = [sample_l7rule_tuple('sample_l7rule_id_7', sample_rule=7),
|
||||
sample_l7rule_tuple('sample_l7rule_id_8', sample_rule=8),
|
||||
sample_l7rule_tuple('sample_l7rule_id_9', sample_rule=9),
|
||||
sample_l7rule_tuple('sample_l7rule_id_10', sample_rule=10),
|
||||
sample_l7rule_tuple('sample_l7rule_id_11', sample_rule=11)]
|
||||
return in_l7policy(
|
||||
id=id,
|
||||
action=action,
|
||||
@ -855,6 +867,34 @@ def sample_l7rule_tuple(id,
|
||||
value = '.example.com'
|
||||
invert = False
|
||||
enabled = False
|
||||
if sample_rule == 7:
|
||||
type = constants.L7RULE_TYPE_SSL_CONN_HAS_CERT
|
||||
compare_type = constants.L7RULE_COMPARE_TYPE_EQUAL_TO
|
||||
key = None
|
||||
value = 'tRuE'
|
||||
invert = False
|
||||
enabled = True
|
||||
if sample_rule == 8:
|
||||
type = constants.L7RULE_TYPE_SSL_VERIFY_RESULT
|
||||
compare_type = constants.L7RULE_COMPARE_TYPE_EQUAL_TO
|
||||
key = None
|
||||
value = '1'
|
||||
invert = True
|
||||
enabled = True
|
||||
if sample_rule == 9:
|
||||
type = constants.L7RULE_TYPE_SSL_DN_FIELD
|
||||
compare_type = constants.L7RULE_COMPARE_TYPE_REGEX
|
||||
key = 'STREET'
|
||||
value = '^STREET.*NO\.$'
|
||||
invert = True
|
||||
enabled = True
|
||||
if sample_rule == 10:
|
||||
type = constants.L7RULE_TYPE_SSL_DN_FIELD
|
||||
compare_type = constants.L7RULE_COMPARE_TYPE_STARTS_WITH
|
||||
key = 'OU-3'
|
||||
value = 'Orgnization Bala'
|
||||
invert = True
|
||||
enabled = True
|
||||
return in_l7rule(
|
||||
id=id,
|
||||
type=type,
|
||||
|
@ -0,0 +1,6 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Adds the ability to define L7 rules based on TLS client authentication
|
||||
information. The new L7 rules are\: "L7RULE_TYPE_SSL_CONN_HAS_CERT",
|
||||
"L7RULE_TYPE_VERIFY_RESULT", and "L7RULE_TYPE_DN_FIELD".
|
Loading…
Reference in New Issue
Block a user