Add ability to set TLS cipher list for listeners
Listeners will now be able to each be assigned their own OpenSSL cipher string with a new field: tls_ciphers. There is also a new configuration option, default_listener_ciphers, which specifies the cipher string to assign to new listeners when one is not explicitly specified. Change-Id: I77da6f14063877af0077f2c12df1aab5d5ead187 Depends-On: Id5f4c20abd40dd092558a711987953012d4ae67f Story: 2006627 Task: 36839
This commit is contained in:
parent
73fca169ab
commit
cd176e55c5
@ -1503,6 +1503,22 @@ timeout_tcp_inspect-optional:
|
||||
min_version: 2.1
|
||||
required: false
|
||||
type: integer
|
||||
tls_ciphers:
|
||||
description: |
|
||||
List of ciphers in OpenSSL format (colon-separated).
|
||||
See https://www.openssl.org/docs/man1.1.1/man1/ciphers.html
|
||||
in: body
|
||||
min_version: 2.15
|
||||
required: true
|
||||
type: string
|
||||
tls_ciphers-optional:
|
||||
description: |
|
||||
List of ciphers in OpenSSL format (colon-separated).
|
||||
See https://www.openssl.org/docs/man1.1.1/man1/ciphers.html
|
||||
in: body
|
||||
min_version: 2.15
|
||||
required: false
|
||||
type: string
|
||||
tls_container_ref:
|
||||
description: |
|
||||
The reference to 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", "client_authentication": "MANDATORY", "client_crl_container_ref": "http://198.51.100.10:9311/v1/containers/e222b065-b93b-4e2a-9a02-804b7a118c3c", "allowed_cidrs": ["192.0.2.0/24", "198.51.100.0/24"]}}' 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", "allowed_cidrs": ["192.0.2.0/24", "198.51.100.0/24"], "tls_ciphers": "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256"}}' http://198.51.100.10:9876/v2/lbaas/listeners
|
||||
|
@ -27,6 +27,7 @@
|
||||
"allowed_cidrs": [
|
||||
"192.0.2.0/24",
|
||||
"198.51.100.0/24"
|
||||
]
|
||||
],
|
||||
"tls_ciphers": "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256"
|
||||
}
|
||||
}
|
||||
|
@ -42,6 +42,7 @@
|
||||
"allowed_cidrs": [
|
||||
"192.0.2.0/24",
|
||||
"198.51.100.0/24"
|
||||
]
|
||||
],
|
||||
"tls_ciphers": "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256"
|
||||
}
|
||||
}
|
||||
|
@ -42,6 +42,7 @@
|
||||
"allowed_cidrs": [
|
||||
"192.0.2.0/24",
|
||||
"198.51.100.0/24"
|
||||
]
|
||||
],
|
||||
"tls_ciphers": "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256"
|
||||
}
|
||||
}
|
||||
|
@ -1 +1 @@
|
||||
curl -X PUT -H "Content-Type: application/json" -H "X-Auth-Token: <token>" -d '{"listener": {"description": "An updated great TLS listener", "admin_state_up": true, "connection_limit": 200, "name": "great_updated_tls_listener", "insert_headers": {"X-Forwarded-For": "false", "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": 100000, "timeout_member_connect": 1000, "timeout_member_data": 100000, "timeout_tcp_inspect": 5, "tags": ["updated_tag"], "client_ca_tls_container_ref": null, "allowed_cidrs": ["192.0.2.0/24", "198.51.100.0/24"]}}' http://198.51.100.10:9876/v2/lbaas/listeners/023f2e34-7806-443b-bfae-16c324569a3d
|
||||
curl -X PUT -H "Content-Type: application/json" -H "X-Auth-Token: <token>" -d '{"listener": {"description": "An updated great TLS listener", "admin_state_up": true, "connection_limit": 200, "name": "great_updated_tls_listener", "insert_headers": {"X-Forwarded-For": "false", "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": 100000, "timeout_member_connect": 1000, "timeout_member_data": 100000, "timeout_tcp_inspect": 5, "tags": ["updated_tag"], "client_ca_tls_container_ref": null, "allowed_cidrs": ["192.0.2.0/24", "198.51.100.0/24"], "tls_ciphers": "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256"}}' http://198.51.100.10:9876/v2/lbaas/listeners/023f2e34-7806-443b-bfae-16c324569a3d
|
||||
|
@ -23,6 +23,7 @@
|
||||
"allowed_cidrs": [
|
||||
"192.0.2.0/24",
|
||||
"198.51.100.0/24"
|
||||
]
|
||||
],
|
||||
"tls_ciphers": "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256"
|
||||
}
|
||||
}
|
||||
|
@ -42,6 +42,7 @@
|
||||
"allowed_cidrs": [
|
||||
"192.0.2.0/24",
|
||||
"198.51.100.0/24"
|
||||
]
|
||||
],
|
||||
"tls_ciphers": "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256"
|
||||
}
|
||||
}
|
||||
|
@ -44,7 +44,8 @@
|
||||
"allowed_cidrs": [
|
||||
"192.0.2.0/24",
|
||||
"198.51.100.0/24"
|
||||
]
|
||||
],
|
||||
"tls_ciphers": "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -72,6 +72,7 @@ Response Parameters
|
||||
- timeout_member_connect: timeout_member_connect
|
||||
- timeout_member_data: timeout_member_data
|
||||
- timeout_tcp_inspect: timeout_tcp_inspect
|
||||
- tls_ciphers: tls_ciphers
|
||||
- updated_at: updated_at
|
||||
|
||||
Response Example
|
||||
@ -163,6 +164,7 @@ Request
|
||||
- timeout_member_connect: timeout_member_connect-optional
|
||||
- timeout_member_data: timeout_member_data-optional
|
||||
- timeout_tcp_inspect: timeout_tcp_inspect-optional
|
||||
- tls_ciphers: tls_ciphers-optional
|
||||
|
||||
.. _header_insertions:
|
||||
|
||||
@ -287,6 +289,7 @@ Response Parameters
|
||||
- timeout_member_connect: timeout_member_connect
|
||||
- timeout_member_data: timeout_member_data
|
||||
- timeout_tcp_inspect: timeout_tcp_inspect
|
||||
- tls_ciphers: tls_ciphers
|
||||
- updated_at: updated_at
|
||||
|
||||
Response Example
|
||||
@ -365,6 +368,7 @@ Response Parameters
|
||||
- timeout_member_connect: timeout_member_connect
|
||||
- timeout_member_data: timeout_member_data
|
||||
- timeout_tcp_inspect: timeout_tcp_inspect
|
||||
- tls_ciphers: tls_ciphers
|
||||
- updated_at: updated_at
|
||||
|
||||
Response Example
|
||||
@ -424,6 +428,7 @@ Request
|
||||
- timeout_member_connect: timeout_member_connect-optional
|
||||
- timeout_member_data: timeout_member_data-optional
|
||||
- timeout_tcp_inspect: timeout_tcp_inspect-optional
|
||||
- tls_ciphers: tls_ciphers-optional
|
||||
|
||||
Request Example
|
||||
---------------
|
||||
@ -469,6 +474,7 @@ Response Parameters
|
||||
- timeout_member_connect: timeout_member_connect
|
||||
- timeout_member_data: timeout_member_data
|
||||
- timeout_tcp_inspect: timeout_tcp_inspect
|
||||
- tls_ciphers: tls_ciphers
|
||||
- updated_at: updated_at
|
||||
|
||||
Response Example
|
||||
|
@ -56,6 +56,12 @@
|
||||
# Boolean to enable/disable oslo middleware /healthcheck in the Octavia API
|
||||
# healthcheck_enabled = False
|
||||
|
||||
# Default cipher string for new TLS-terminated listeners
|
||||
# Cipher strings are in OpenSSL format, see https://www.openssl.org/docs/man1.1.1/man1/ciphers.html
|
||||
# This example is the "Broad Compatibility" cipher string from OWASP,
|
||||
# see https://cheatsheetseries.owasp.org/cheatsheets/TLS_Cipher_String_Cheat_Sheet.html
|
||||
# default_listener_ciphers = TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256
|
||||
|
||||
[database]
|
||||
# This line MUST be changed to actually run the plugin.
|
||||
# Example:
|
||||
|
@ -73,7 +73,7 @@ munch==2.2.0
|
||||
netaddr==0.7.19
|
||||
netifaces==0.10.4
|
||||
networkx==1.11
|
||||
octavia-lib==1.5.0
|
||||
octavia-lib==2.0.0
|
||||
openstacksdk==0.12.0
|
||||
os-client-config==1.29.0
|
||||
os-service-types==1.2.0
|
||||
|
@ -30,7 +30,7 @@ LOG = logging.getLogger(__name__)
|
||||
|
||||
FRONTEND_BACKEND_PATTERN = re.compile(r'\n(frontend|backend)\s+(\S+)\n')
|
||||
LISTENER_MODE_PATTERN = re.compile(r'^\s+mode\s+(.*)$', re.MULTILINE)
|
||||
TLS_CERT_PATTERN = re.compile(r'^\s+bind\s+\S+\s+ssl crt-list\s+(.*)$',
|
||||
TLS_CERT_PATTERN = re.compile(r'^\s+bind\s+\S+\s+ssl crt-list\s+(\S*)',
|
||||
re.MULTILINE)
|
||||
STATS_SOCKET_PATTERN = re.compile(r'stats socket\s+(\S+)')
|
||||
|
||||
|
@ -107,6 +107,10 @@ class RootController(object):
|
||||
self._add_a_version(versions, 'v2.13', 'v2', 'SUPPORTED',
|
||||
'2019-09-13T00:00:00Z', host_url)
|
||||
# Availability Zones
|
||||
self._add_a_version(versions, 'v2.14', 'v2', 'CURRENT',
|
||||
self._add_a_version(versions, 'v2.14', 'v2', 'SUPPORTED',
|
||||
'2019-11-10T00:00:00Z', host_url)
|
||||
|
||||
# TLS version and cipher options
|
||||
self._add_a_version(versions, 'v2.15', 'v2', 'CURRENT',
|
||||
'2020-03-10T00:00:00Z', host_url)
|
||||
return {'versions': versions}
|
||||
|
@ -62,6 +62,7 @@ class ListenerResponse(BaseListenerType):
|
||||
client_authentication = wtypes.wsattr(wtypes.StringType())
|
||||
client_crl_container_ref = wtypes.wsattr(wtypes.StringType())
|
||||
allowed_cidrs = wtypes.wsattr([types.CidrType()])
|
||||
tls_ciphers = wtypes.StringType()
|
||||
|
||||
@classmethod
|
||||
def from_data_model(cls, data_model, children=False):
|
||||
@ -150,6 +151,7 @@ class ListenerPOST(BaseListenerType):
|
||||
default=constants.CLIENT_AUTH_NONE)
|
||||
client_crl_container_ref = wtypes.StringType(max_length=255)
|
||||
allowed_cidrs = wtypes.wsattr([types.CidrType()])
|
||||
tls_ciphers = wtypes.StringType(max_length=2048)
|
||||
|
||||
|
||||
class ListenerRootPOST(types.BaseType):
|
||||
@ -187,6 +189,7 @@ class ListenerPUT(BaseListenerType):
|
||||
wtypes.Enum(str, *constants.SUPPORTED_CLIENT_AUTH_MODES))
|
||||
client_crl_container_ref = wtypes.StringType(max_length=255)
|
||||
allowed_cidrs = wtypes.wsattr([types.CidrType()])
|
||||
tls_ciphers = wtypes.StringType(max_length=2048)
|
||||
|
||||
|
||||
class ListenerRootPUT(types.BaseType):
|
||||
@ -237,6 +240,7 @@ class ListenerSingleCreate(BaseListenerType):
|
||||
default=constants.CLIENT_AUTH_NONE)
|
||||
client_crl_container_ref = wtypes.StringType(max_length=255)
|
||||
allowed_cidrs = wtypes.wsattr([types.CidrType()])
|
||||
tls_ciphers = wtypes.StringType(max_length=2048)
|
||||
|
||||
|
||||
class ListenerStatusResponse(BaseListenerType):
|
||||
|
@ -103,6 +103,10 @@ api_opts = [
|
||||
cfg.BoolOpt('healthcheck_enabled', default=False,
|
||||
help=_("When True, the oslo middleware healthcheck endpoint "
|
||||
"is enabled in the Octavia API.")),
|
||||
cfg.StrOpt('default_listener_ciphers',
|
||||
default=constants.CIPHERS_OWASP_SUITE_B,
|
||||
help=_("Default OpenSSL cipher string (colon-separated) for "
|
||||
"new TLS-enabled listeners.")),
|
||||
]
|
||||
|
||||
# Options only used by the amphora agent
|
||||
|
@ -775,3 +775,12 @@ CINDER_ACTION_CREATE_VOLUME = 'create volume'
|
||||
|
||||
# The nil UUID (used in octavia for deleted references) - RFC 4122
|
||||
NIL_UUID = '00000000-0000-0000-0000-000000000000'
|
||||
|
||||
# OpenSSL cipher strings
|
||||
CIPHERS_OWASP_SUITE_B = ('TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:'
|
||||
'TLS_AES_128_GCM_SHA256:DHE-RSA-AES256-GCM-SHA384:'
|
||||
'DHE-RSA-AES128-GCM-SHA256:'
|
||||
'ECDHE-RSA-AES256-GCM-SHA384:'
|
||||
'ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-SHA256:'
|
||||
'DHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:'
|
||||
'ECDHE-RSA-AES128-SHA256')
|
||||
|
@ -384,7 +384,7 @@ class Listener(BaseDataModel):
|
||||
timeout_member_data=None, timeout_tcp_inspect=None,
|
||||
tags=None, client_ca_tls_certificate_id=None,
|
||||
client_authentication=None, client_crl_container_id=None,
|
||||
allowed_cidrs=None):
|
||||
allowed_cidrs=None, tls_ciphers=None):
|
||||
self.id = id
|
||||
self.project_id = project_id
|
||||
self.name = name
|
||||
@ -417,6 +417,7 @@ class Listener(BaseDataModel):
|
||||
self.client_authentication = client_authentication
|
||||
self.client_crl_container_id = client_crl_container_id
|
||||
self.allowed_cidrs = allowed_cidrs or []
|
||||
self.tls_ciphers = tls_ciphers
|
||||
|
||||
def update(self, update_dict):
|
||||
for key, value in update_dict.items():
|
||||
|
@ -282,6 +282,10 @@ class JinjaTemplater(object):
|
||||
os.path.join(self.base_crt_dir, loadbalancer.id,
|
||||
tls_certs[listener.client_crl_container_id]))
|
||||
|
||||
if (listener.protocol == constants.PROTOCOL_TERMINATED_HTTPS and
|
||||
listener.tls_ciphers is not None):
|
||||
ret_value['tls_ciphers'] = listener.tls_ciphers
|
||||
|
||||
pools = []
|
||||
pool_gen = (pool for pool in listener.pools if
|
||||
pool.provisioning_status != constants.PENDING_DELETE)
|
||||
|
@ -43,8 +43,13 @@ peers {{ "%s_peers"|format(loadbalancer.id.replace("-", ""))|trim() }}
|
||||
{% else %}
|
||||
{% set ca_crl_opt = "" %}
|
||||
{% endif %}
|
||||
{% if listener.tls_ciphers is defined %}
|
||||
{% set ciphers_opt = "ciphers %s"|format(listener.tls_ciphers)|trim() %}
|
||||
{% else %}
|
||||
{% set ciphers_opt = "" %}
|
||||
{% endif %}
|
||||
bind {{ lb_vip_address }}:{{ listener.protocol_port }} {{
|
||||
"%s %s %s"|format(def_crt_opt, client_ca_opt, ca_crl_opt)|trim() }}
|
||||
"%s %s %s %s"|format(def_crt_opt, client_ca_opt, ca_crl_opt, ciphers_opt)|trim() }}
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
|
@ -0,0 +1,35 @@
|
||||
# Copyright 2020 Dawson Coleman
|
||||
#
|
||||
# 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 listener ciphers column
|
||||
|
||||
Revision ID: 7c36b277bfb0
|
||||
Revises: 8ac4ed24df3a
|
||||
Create Date: 2020-03-11 02:23:49.097485
|
||||
|
||||
"""
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '7c36b277bfb0'
|
||||
down_revision = '8ac4ed24df3a'
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.add_column(
|
||||
'listener',
|
||||
sa.Column('tls_ciphers', sa.String(2048), nullable=True)
|
||||
)
|
@ -520,6 +520,7 @@ class Listener(base_models.BASE, base_models.IdMixin,
|
||||
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)
|
||||
tls_ciphers = sa.Column(sa.String(2048), nullable=True)
|
||||
|
||||
_tags = orm.relationship(
|
||||
'Tags',
|
||||
|
@ -103,6 +103,12 @@ def create_listener(listener_dict, lb_id):
|
||||
|
||||
if 'client_authentication' not in listener_dict:
|
||||
listener_dict['client_authentication'] = constants.CLIENT_AUTH_NONE
|
||||
|
||||
if (listener_dict['protocol'] == constants.PROTOCOL_TERMINATED_HTTPS and
|
||||
('tls_ciphers' not in listener_dict or
|
||||
listener_dict['tls_ciphers'] is None)):
|
||||
listener_dict['tls_ciphers'] = (
|
||||
CONF.api_settings.default_listener_ciphers)
|
||||
return listener_dict
|
||||
|
||||
|
||||
|
@ -294,7 +294,9 @@ class SampleDriverDataModels(object):
|
||||
lib_consts.CA_TLS_CONTAINER_DATA: pool_ca_file_content,
|
||||
lib_consts.CRL_CONTAINER_REF: self.pool_crl_container_ref,
|
||||
lib_consts.CRL_CONTAINER_DATA: pool_crl_file_content,
|
||||
lib_consts.TLS_ENABLED: True
|
||||
lib_consts.TLS_ENABLED: True,
|
||||
lib_consts.TLS_CIPHERS: None,
|
||||
lib_consts.TLS_VERSIONS: None,
|
||||
}
|
||||
|
||||
self.provider_pool2_dict = copy.deepcopy(self.provider_pool1_dict)
|
||||
@ -463,7 +465,8 @@ class SampleDriverDataModels(object):
|
||||
self.client_ca_tls_certificate_ref,
|
||||
lib_consts.CLIENT_AUTHENTICATION: constants.CLIENT_AUTH_NONE,
|
||||
constants.CLIENT_CRL_CONTAINER_ID: self.client_crl_container_ref,
|
||||
lib_consts.ALLOWED_CIDRS: ['192.0.2.0/24', '198.51.100.0/24']
|
||||
lib_consts.ALLOWED_CIDRS: ['192.0.2.0/24', '198.51.100.0/24'],
|
||||
lib_consts.TLS_CIPHERS: constants.CIPHERS_OWASP_SUITE_B
|
||||
}
|
||||
|
||||
self.test_listener1_dict.update(self._common_test_dict)
|
||||
@ -530,7 +533,9 @@ class SampleDriverDataModels(object):
|
||||
lib_consts.CLIENT_CA_TLS_CONTAINER_DATA: ca_cert,
|
||||
lib_consts.CLIENT_AUTHENTICATION: constants.CLIENT_AUTH_NONE,
|
||||
lib_consts.CLIENT_CRL_CONTAINER_REF: self.client_crl_container_ref,
|
||||
lib_consts.CLIENT_CRL_CONTAINER_DATA: crl_file_content
|
||||
lib_consts.CLIENT_CRL_CONTAINER_DATA: crl_file_content,
|
||||
lib_consts.TLS_CIPHERS: constants.CIPHERS_OWASP_SUITE_B,
|
||||
lib_consts.TLS_VERSIONS: None
|
||||
}
|
||||
|
||||
self.provider_listener2_dict = copy.deepcopy(
|
||||
|
@ -45,7 +45,7 @@ class TestRootController(base_db_test.OctaviaDBTestBase):
|
||||
def test_api_versions(self):
|
||||
versions = self._get_versions_with_config()
|
||||
version_ids = tuple(v.get('id') for v in versions)
|
||||
self.assertEqual(15, len(version_ids))
|
||||
self.assertEqual(16, len(version_ids))
|
||||
self.assertIn('v2.0', version_ids)
|
||||
self.assertIn('v2.1', version_ids)
|
||||
self.assertIn('v2.2', version_ids)
|
||||
@ -61,6 +61,7 @@ class TestRootController(base_db_test.OctaviaDBTestBase):
|
||||
self.assertIn('v2.12', version_ids)
|
||||
self.assertIn('v2.13', version_ids)
|
||||
self.assertIn('v2.14', version_ids)
|
||||
self.assertIn('v2.15', version_ids)
|
||||
|
||||
# Each version should have a 'self' 'href' to the API version URL
|
||||
# [{u'rel': u'self', u'href': u'http://localhost/v2'}]
|
||||
|
@ -2622,7 +2622,8 @@ class TestLoadBalancerGraph(base.BaseAPITest):
|
||||
'client_ca_tls_container_ref': None,
|
||||
'client_authentication': constants.CLIENT_AUTH_NONE,
|
||||
'client_crl_container_ref': None,
|
||||
'allowed_cidrs': None
|
||||
'allowed_cidrs': None,
|
||||
'tls_ciphers': None
|
||||
}
|
||||
if create_sni_containers:
|
||||
create_listener['sni_container_refs'] = create_sni_containers
|
||||
@ -2668,6 +2669,8 @@ class TestLoadBalancerGraph(base.BaseAPITest):
|
||||
expected_client_crl_container)
|
||||
if expected_allowed_cidrs:
|
||||
expected_listener['allowed_cidrs'] = expected_allowed_cidrs
|
||||
if create_protocol == constants.PROTOCOL_TERMINATED_HTTPS:
|
||||
expected_listener['tls_ciphers'] = constants.CIPHERS_OWASP_SUITE_B
|
||||
|
||||
return create_listener, expected_listener
|
||||
|
||||
|
@ -16,6 +16,7 @@ from unittest import mock
|
||||
|
||||
from octavia_lib.api.drivers import data_models as driver_dm
|
||||
from octavia_lib.api.drivers import exceptions as lib_exceptions
|
||||
from octavia_lib.common import constants as lib_constants
|
||||
|
||||
from octavia.api.drivers import exceptions as driver_exceptions
|
||||
from octavia.api.drivers import utils
|
||||
@ -139,6 +140,13 @@ class TestUtils(base.TestCase):
|
||||
'flavor_id': 'flavor_id',
|
||||
'provider': 'noop_driver'}
|
||||
ref_listeners = copy.deepcopy(self.sample_data.provider_listeners)
|
||||
# TODO(johnsom) Remove when versions implemented
|
||||
for listener in ref_listeners:
|
||||
delattr(listener, lib_constants.TLS_VERSIONS)
|
||||
expect_pools = copy.deepcopy(self.sample_data.provider_pools,)
|
||||
for pool in expect_pools:
|
||||
delattr(pool, lib_constants.TLS_VERSIONS)
|
||||
delattr(pool, lib_constants.TLS_CIPHERS)
|
||||
ref_prov_lb_dict = {
|
||||
'vip_address': self.sample_data.ip_address,
|
||||
'admin_state_up': True,
|
||||
@ -150,7 +158,7 @@ class TestUtils(base.TestCase):
|
||||
'vip_port_id': self.sample_data.port_id,
|
||||
'vip_qos_policy_id': self.sample_data.qos_policy_id,
|
||||
'vip_network_id': self.sample_data.network_id,
|
||||
'pools': self.sample_data.provider_pools,
|
||||
'pools': expect_pools,
|
||||
'flavor': {'shaved_ice': 'cherry'},
|
||||
'name': 'lb1'}
|
||||
vip = data_models.Vip(ip_address=self.sample_data.ip_address,
|
||||
@ -211,6 +219,9 @@ class TestUtils(base.TestCase):
|
||||
provider_listeners = utils.db_listeners_to_provider_listeners(
|
||||
self.sample_data.test_db_listeners)
|
||||
ref_listeners = copy.deepcopy(self.sample_data.provider_listeners)
|
||||
# TODO(johnsom) Remove when versions implemented
|
||||
for listener in ref_listeners:
|
||||
delattr(listener, lib_constants.TLS_VERSIONS)
|
||||
self.assertEqual(ref_listeners, provider_listeners)
|
||||
|
||||
@mock.patch('oslo_context.context.RequestContext', return_value=None)
|
||||
@ -250,6 +261,10 @@ class TestUtils(base.TestCase):
|
||||
# not any other related fields. So we need to delete them.
|
||||
expect_prov = copy.deepcopy(self.sample_data.provider_listener1_dict)
|
||||
expect_pool_prov = copy.deepcopy(self.sample_data.provider_pool1_dict)
|
||||
# TODO(johnsom) Remove when versions and ciphers are implemented
|
||||
expect_pool_prov.pop(lib_constants.TLS_VERSIONS)
|
||||
expect_pool_prov.pop(lib_constants.TLS_CIPHERS)
|
||||
expect_prov.pop(lib_constants.TLS_VERSIONS)
|
||||
expect_prov['default_pool'] = expect_pool_prov
|
||||
provider_listener = utils.listener_dict_to_provider_dict(
|
||||
self.sample_data.test_listener1_dict)
|
||||
@ -283,6 +298,10 @@ class TestUtils(base.TestCase):
|
||||
expect_prov = copy.deepcopy(self.sample_data.provider_listener1_dict)
|
||||
expect_pool_prov = copy.deepcopy(self.sample_data.provider_pool1_dict)
|
||||
del expect_pool_prov['tls_container_data']
|
||||
# TODO(johnsom) Remove when versions and ciphers are implemented
|
||||
expect_pool_prov.pop(lib_constants.TLS_VERSIONS)
|
||||
expect_pool_prov.pop(lib_constants.TLS_CIPHERS)
|
||||
expect_prov.pop(lib_constants.TLS_VERSIONS)
|
||||
expect_prov['default_pool'] = expect_pool_prov
|
||||
del expect_prov['default_tls_container_data']
|
||||
del expect_prov['sni_container_data']
|
||||
@ -318,7 +337,11 @@ class TestUtils(base.TestCase):
|
||||
'X509 POOL CRL FILE']
|
||||
provider_pool = utils.db_pool_to_provider_pool(
|
||||
self.sample_data.db_pool1)
|
||||
self.assertEqual(self.sample_data.provider_pool1, provider_pool)
|
||||
# TODO(johnsom) Remove when versions and ciphers are implemented
|
||||
expect_prov_pool = copy.deepcopy(self.sample_data.provider_pool1)
|
||||
delattr(expect_prov_pool, lib_constants.TLS_VERSIONS)
|
||||
delattr(expect_prov_pool, lib_constants.TLS_CIPHERS)
|
||||
self.assertEqual(expect_prov_pool, provider_pool)
|
||||
|
||||
@mock.patch('octavia.api.drivers.utils._get_secret_data')
|
||||
@mock.patch('octavia.common.tls_utils.cert_parser.load_certificates_data')
|
||||
@ -333,7 +356,11 @@ class TestUtils(base.TestCase):
|
||||
test_db_pool = self.sample_data.db_pool1
|
||||
test_db_pool.members = [self.sample_data.db_member1]
|
||||
provider_pool = utils.db_pool_to_provider_pool(test_db_pool)
|
||||
self.assertEqual(self.sample_data.provider_pool1, provider_pool)
|
||||
# TODO(johnsom) Remove when versions and ciphers are implemented
|
||||
expect_prov_pool = copy.deepcopy(self.sample_data.provider_pool1)
|
||||
delattr(expect_prov_pool, lib_constants.TLS_VERSIONS)
|
||||
delattr(expect_prov_pool, lib_constants.TLS_CIPHERS)
|
||||
self.assertEqual(expect_prov_pool, provider_pool)
|
||||
|
||||
@mock.patch('octavia.api.drivers.utils._get_secret_data')
|
||||
@mock.patch('octavia.common.tls_utils.cert_parser.load_certificates_data')
|
||||
@ -346,7 +373,12 @@ class TestUtils(base.TestCase):
|
||||
'X509 POOL CRL FILE']
|
||||
provider_pools = utils.db_pools_to_provider_pools(
|
||||
self.sample_data.test_db_pools)
|
||||
self.assertEqual(self.sample_data.provider_pools, provider_pools)
|
||||
# TODO(johnsom) Remove when versions and ciphers are implemented
|
||||
expect_prov_pools = copy.deepcopy(self.sample_data.provider_pools)
|
||||
for prov_pool in expect_prov_pools:
|
||||
delattr(prov_pool, lib_constants.TLS_VERSIONS)
|
||||
delattr(prov_pool, lib_constants.TLS_CIPHERS)
|
||||
self.assertEqual(expect_prov_pools, provider_pools)
|
||||
|
||||
@mock.patch('octavia.api.drivers.utils._get_secret_data')
|
||||
@mock.patch('octavia.common.tls_utils.cert_parser.load_certificates_data')
|
||||
@ -362,6 +394,9 @@ class TestUtils(base.TestCase):
|
||||
provider_pool_dict = utils.pool_dict_to_provider_dict(
|
||||
self.sample_data.test_pool1_dict)
|
||||
provider_pool_dict.pop('crl_container_ref')
|
||||
# TODO(johnsom) Remove when versions and ciphers are implemented
|
||||
expect_prov.pop(lib_constants.TLS_VERSIONS)
|
||||
expect_prov.pop(lib_constants.TLS_CIPHERS)
|
||||
self.assertEqual(expect_prov, provider_pool_dict)
|
||||
|
||||
@mock.patch('octavia.api.drivers.utils._get_secret_data')
|
||||
@ -393,6 +428,9 @@ class TestUtils(base.TestCase):
|
||||
provider_pool_dict = utils.pool_dict_to_provider_dict(
|
||||
self.sample_data.test_pool1_dict, for_delete=True)
|
||||
provider_pool_dict.pop('crl_container_ref')
|
||||
# TODO(johnsom) Remove when versions and ciphers are implemented
|
||||
expect_prov.pop(lib_constants.TLS_VERSIONS)
|
||||
expect_prov.pop(lib_constants.TLS_CIPHERS)
|
||||
self.assertEqual(expect_prov, provider_pool_dict)
|
||||
|
||||
def test_db_HM_to_provider_HM(self):
|
||||
|
@ -51,12 +51,13 @@ class TestHaproxyCfg(base.TestCase):
|
||||
"ssl crt-list {crt_list} "
|
||||
"ca-file /var/lib/octavia/certs/sample_loadbalancer_id_1/"
|
||||
"client_ca.pem verify required crl-file /var/lib/octavia/"
|
||||
"certs/sample_loadbalancer_id_1/SHA_ID.pem\n"
|
||||
"certs/sample_loadbalancer_id_1/SHA_ID.pem ciphers {ciphers}\n"
|
||||
" mode http\n"
|
||||
" default_backend sample_pool_id_1:sample_listener_id_1\n"
|
||||
" timeout client 50000\n").format(
|
||||
maxconn=constants.HAPROXY_MAX_MAXCONN,
|
||||
crt_list=FAKE_CRT_LIST_FILENAME)
|
||||
crt_list=FAKE_CRT_LIST_FILENAME,
|
||||
ciphers=constants.CIPHERS_OWASP_SUITE_B)
|
||||
be = ("backend sample_pool_id_1:sample_listener_id_1\n"
|
||||
" mode http\n"
|
||||
" balance roundrobin\n"
|
||||
@ -94,6 +95,56 @@ class TestHaproxyCfg(base.TestCase):
|
||||
rendered_obj)
|
||||
|
||||
def test_render_template_tls_no_sni(self):
|
||||
conf = oslo_fixture.Config(cfg.CONF)
|
||||
conf.config(group="haproxy_amphora", base_cert_dir='/fake_cert_dir')
|
||||
FAKE_CRT_LIST_FILENAME = os.path.join(
|
||||
CONF.haproxy_amphora.base_cert_dir,
|
||||
'sample_loadbalancer_id_1/sample_listener_id_1.pem')
|
||||
fe = ("frontend sample_listener_id_1\n"
|
||||
" maxconn {maxconn}\n"
|
||||
" redirect scheme https if !{{ ssl_fc }}\n"
|
||||
" bind 10.0.0.2:443 ssl crt-list {crt_list}"
|
||||
" ciphers {ciphers}\n"
|
||||
" mode http\n"
|
||||
" default_backend sample_pool_id_1:sample_listener_id_1\n"
|
||||
" timeout client 50000\n").format(
|
||||
maxconn=constants.HAPROXY_MAX_MAXCONN,
|
||||
crt_list=FAKE_CRT_LIST_FILENAME,
|
||||
ciphers=constants.CIPHERS_OWASP_SUITE_B)
|
||||
be = ("backend sample_pool_id_1:sample_listener_id_1\n"
|
||||
" mode http\n"
|
||||
" balance roundrobin\n"
|
||||
" cookie SRV insert indirect nocache\n"
|
||||
" timeout check 31s\n"
|
||||
" option httpchk GET /index.html HTTP/1.0\\r\\n\n"
|
||||
" http-check expect rstatus 418\n"
|
||||
" fullconn {maxconn}\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").format(
|
||||
maxconn=constants.HAPROXY_MAX_MAXCONN)
|
||||
rendered_obj = self.jinja_cfg.render_loadbalancer_obj(
|
||||
sample_configs_combined.sample_amphora_tuple(),
|
||||
[sample_configs_combined.sample_listener_tuple(
|
||||
proto='TERMINATED_HTTPS', tls=True)],
|
||||
tls_certs={'cont_id_1':
|
||||
sample_configs_combined.sample_tls_container_tuple(
|
||||
id='tls_container_id',
|
||||
certificate='ImAalsdkfjCert',
|
||||
private_key='ImAsdlfksdjPrivateKey',
|
||||
primary_cn="FakeCN")})
|
||||
self.assertEqual(
|
||||
sample_configs_combined.sample_base_expected_config(
|
||||
frontend=fe, backend=be),
|
||||
rendered_obj)
|
||||
|
||||
def test_render_template_tls_no_ciphers(self):
|
||||
conf = oslo_fixture.Config(cfg.CONF)
|
||||
conf.config(group="haproxy_amphora", base_cert_dir='/fake_cert_dir')
|
||||
FAKE_CRT_LIST_FILENAME = os.path.join(
|
||||
@ -129,7 +180,7 @@ class TestHaproxyCfg(base.TestCase):
|
||||
rendered_obj = self.jinja_cfg.render_loadbalancer_obj(
|
||||
sample_configs_combined.sample_amphora_tuple(),
|
||||
[sample_configs_combined.sample_listener_tuple(
|
||||
proto='TERMINATED_HTTPS', tls=True)],
|
||||
proto='TERMINATED_HTTPS', tls=True, tls_ciphers=None)],
|
||||
tls_certs={'cont_id_1':
|
||||
sample_configs_combined.sample_tls_container_tuple(
|
||||
id='tls_container_id',
|
||||
@ -1121,7 +1172,8 @@ class TestHaproxyCfg(base.TestCase):
|
||||
fe = ("frontend sample_listener_id_1\n"
|
||||
" maxconn 1000000\n"
|
||||
" redirect scheme https if !{ ssl_fc }\n"
|
||||
" bind 10.0.0.2:443\n"
|
||||
" bind 10.0.0.2:443 ciphers " +
|
||||
constants.CIPHERS_OWASP_SUITE_B + "\n"
|
||||
" mode http\n"
|
||||
" acl sample_l7rule_id_1 path -m beg /api\n"
|
||||
" use_backend sample_pool_id_2:sample_listener_id_1"
|
||||
|
@ -599,10 +599,13 @@ def sample_listener_tuple(proto=None, monitor=True, alloc_default_pool=True,
|
||||
pool_ca_cert=False, pool_crl=False,
|
||||
tls_enabled=False, hm_host_http_check=False,
|
||||
id='sample_listener_id_1', recursive_nest=False,
|
||||
provisioning_status=constants.ACTIVE):
|
||||
provisioning_status=constants.ACTIVE,
|
||||
tls_ciphers=constants.CIPHERS_OWASP_SUITE_B):
|
||||
proto = 'HTTP' if proto is None else proto
|
||||
if be_proto is None:
|
||||
be_proto = 'HTTP' if proto == 'TERMINATED_HTTPS' else proto
|
||||
if proto != constants.PROTOCOL_TERMINATED_HTTPS:
|
||||
tls_ciphers = None
|
||||
topology = 'SINGLE' if topology is None else topology
|
||||
port = '443' if proto in ['HTTPS', 'TERMINATED_HTTPS'] else '80'
|
||||
peer_port = 1024 if peer_port is None else peer_port
|
||||
@ -616,7 +619,8 @@ def sample_listener_tuple(proto=None, monitor=True, alloc_default_pool=True,
|
||||
'timeout_member_connect, timeout_member_data, '
|
||||
'timeout_tcp_inspect, client_ca_tls_certificate_id, '
|
||||
'client_ca_tls_certificate, client_authentication, '
|
||||
'client_crl_container_id, provisioning_status')
|
||||
'client_crl_container_id, provisioning_status, '
|
||||
'tls_ciphers')
|
||||
if l7:
|
||||
pools = [
|
||||
sample_pool_tuple(
|
||||
@ -727,6 +731,7 @@ def sample_listener_tuple(proto=None, monitor=True, alloc_default_pool=True,
|
||||
constants.CLIENT_AUTH_NONE),
|
||||
client_crl_container_id='cont_id_crl' if client_crl_cert else '',
|
||||
provisioning_status=provisioning_status,
|
||||
tls_ciphers=tls_ciphers
|
||||
)
|
||||
if recursive_nest:
|
||||
listener.load_balancer.listeners.append(listener)
|
||||
|
@ -0,0 +1,7 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
HTTPS-terminated listeners can now be individually configured with an OpenSSL cipher string.
|
||||
The default cipher string for new listeners can be specified with ``default_tls_ciphers``
|
||||
in ``octavia.conf``. The built-in default is OWASP's "Suite B" recommendation. (https://cheatsheetseries.owasp.org/cheatsheets/TLS_Cipher_String_Cheat_Sheet.html)
|
||||
Existing listeners will be unaffected.
|
@ -44,7 +44,7 @@ tenacity>=5.0.4 # Apache-2.0
|
||||
distro>=1.2.0 # Apache-2.0
|
||||
jsonschema>=2.6.0 # MIT
|
||||
debtcollector>=1.19.0 # Apache-2.0
|
||||
octavia-lib>=1.5.0 # Apache-2.0
|
||||
octavia-lib>=2.0.0 # Apache-2.0
|
||||
netaddr>=0.7.19 # BSD
|
||||
simplejson>=3.13.2 # MIT
|
||||
setproctitle>=1.1.10 # BSD
|
||||
|
Loading…
Reference in New Issue
Block a user