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:
ZhaoBo 2018-10-18 17:38:19 +08:00 committed by Michael Johnson
parent aa1bca0271
commit f77d7d0220
11 changed files with 504 additions and 4 deletions

View File

@ -770,14 +770,16 @@ l7rule-key-optional:
l7rule-type: l7rule-type:
description: | description: |
The L7 rule type. One of ``COOKIE``, ``FILE_TYPE``, ``HEADER``, 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 in: body
required: true required: true
type: string type: string
l7rule-type-optional: l7rule-type-optional:
description: | description: |
The L7 rule type. One of ``COOKIE``, ``FILE_TYPE``, ``HEADER``, 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 in: body
required: false required: false
type: string type: string

View File

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

View File

@ -86,6 +86,15 @@ L7 rules have the following types:
compares it against the value parameter in the rule. compares it against the value parameter in the rule.
* ``COOKIE``: The rule looks for a cookie named by the key parameter and * ``COOKIE``: The rule looks for a cookie named by the key parameter and
compares it against the value parameter in the rule. 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 Comparison types
________________ ________________
@ -183,3 +192,4 @@ Useful links
* `Octavia API Reference <https://developer.openstack.org/api-ref/load-balancer/>`_ * `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>`_ * `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>`_ * `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>`_

View File

@ -143,9 +143,15 @@ L7RULE_TYPE_PATH = 'PATH'
L7RULE_TYPE_FILE_TYPE = 'FILE_TYPE' L7RULE_TYPE_FILE_TYPE = 'FILE_TYPE'
L7RULE_TYPE_HEADER = 'HEADER' L7RULE_TYPE_HEADER = 'HEADER'
L7RULE_TYPE_COOKIE = 'COOKIE' 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, SUPPORTED_L7RULE_TYPES = (L7RULE_TYPE_HOST_NAME, L7RULE_TYPE_PATH,
L7RULE_TYPE_FILE_TYPE, L7RULE_TYPE_HEADER, 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_REGEX = 'REGEX'
L7RULE_COMPARE_TYPE_STARTS_WITH = 'STARTS_WITH' L7RULE_COMPARE_TYPE_STARTS_WITH = 'STARTS_WITH'

View File

@ -86,6 +86,14 @@ bind {{ lb_vip_address }}:{{ listener.protocol_port }} {{
acl {{ l7rule.id }} req.cook({{ l7rule.key }}) {{ acl {{ l7rule.id }} req.cook({{ l7rule.key }}) {{
l7rule_compare_type_macro( l7rule_compare_type_macro(
constants, l7rule.compare_type) }} {{ l7rule.value }} 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 %} {% endif %}
{% endmacro %} {% endmacro %}

View File

@ -26,6 +26,7 @@ import netaddr
from oslo_config import cfg from oslo_config import cfg
import rfc3986 import rfc3986
import six import six
from wsme import types as wtypes
from octavia.common import constants from octavia.common import constants
from octavia.common import exceptions from octavia.common import exceptions
@ -161,11 +162,72 @@ def l7rule_data(l7rule):
raise exceptions.InvalidL7Rule(msg='invalid comparison type ' raise exceptions.InvalidL7Rule(msg='invalid comparison type '
'for rule 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: else:
raise exceptions.InvalidL7Rule(msg='invalid rule type') raise exceptions.InvalidL7Rule(msg='invalid rule type')
return True 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): def sanitize_l7policy_api_args(l7policy, create=False):
"""Validate and make consistent L7Policy API arguments. """Validate and make consistent L7Policy API arguments.

View File

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

View File

@ -682,6 +682,135 @@ class TestL7Rule(base.BaseAPITest):
self.assertIn('Provider \'bad_driver\' reports error: broken', self.assertIn('Provider \'bad_driver\' reports error: broken',
response.json.get('faultstring')) 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): def test_update(self):
api_l7rule = self.create_l7rule( api_l7rule = self.create_l7rule(
self.l7policy_id, constants.L7RULE_TYPE_PATH, 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'), l7policy_id=self.l7policy_id, l7rule_id=api_l7rule.get('id'),
l7rule_prov_status=constants.ACTIVE) 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): def test_delete(self):
api_l7rule = self.create_l7rule( api_l7rule = self.create_l7rule(
self.l7policy_id, constants.L7RULE_TYPE_PATH, self.l7policy_id, constants.L7RULE_TYPE_PATH,

View File

@ -942,3 +942,85 @@ class TestHaproxyCfg(base.TestCase):
self.assertEqual( self.assertEqual(
sample_configs.sample_base_expected_config(backend=be), sample_configs.sample_base_expected_config(backend=be),
rendered_obj) 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)

View File

@ -512,7 +512,8 @@ def sample_listener_tuple(proto=None, monitor=True, alloc_default_pool=True,
timeout_member_connect=5000, timeout_member_connect=5000,
timeout_member_data=50000, timeout_member_data=50000,
timeout_tcp_inspect=0, 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 proto = 'HTTP' if proto is None else proto
if be_proto is None: if be_proto is None:
be_proto = 'HTTP' if proto is 'TERMINATED_HTTPS' else proto 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_5', sample_policy=5),
sample_l7policy_tuple('sample_l7policy_id_6', sample_policy=6), sample_l7policy_tuple('sample_l7policy_id_6', sample_policy=6),
sample_l7policy_tuple('sample_l7policy_id_7', sample_policy=7)] 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: else:
pools = [ pools = [
sample_pool_tuple( sample_pool_tuple(
@ -799,6 +803,14 @@ def sample_l7policy_tuple(id,
redirect_prefix = 'https://example.com' redirect_prefix = 'https://example.com'
l7rules = [sample_l7rule_tuple('sample_l7rule_id_2', sample_rule=2), l7rules = [sample_l7rule_tuple('sample_l7rule_id_2', sample_rule=2),
sample_l7rule_tuple('sample_l7rule_id_3', sample_rule=3)] 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( return in_l7policy(
id=id, id=id,
action=action, action=action,
@ -855,6 +867,34 @@ def sample_l7rule_tuple(id,
value = '.example.com' value = '.example.com'
invert = False invert = False
enabled = 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( return in_l7rule(
id=id, id=id,
type=type, type=type,

View File

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