Add TLS version configuration for listeners

Add field tls_versions to listeners for restricting TLS versions used.
This is a list of versions to be used.
Available values (as defined in octavia-lib):
SSLv3, TLSv1, TLSv1.1, TLSv1.2, TLSv1.3

Add default_listener_tls_versions in octavia.conf.

Note that at this time TLS 1.3 ciphersuites are not impelemented,
so any TLS 1.3 connections will use haproxy's default ciphers
instead of what's specified by tls_ciphers.

Change-Id: Ic33d9b9a256490ae1b048cdfd2475d6340509fdb
Story: 2006733
Task: 37170
Task: 37169
This commit is contained in:
Dawson Coleman 2020-04-20 17:47:26 -05:00 committed by Michael Johnson
parent 8ef7d60c91
commit 6aad5d8b9f
32 changed files with 325 additions and 44 deletions

View File

@ -1567,6 +1567,22 @@ tls_enabled-optional:
min_version: 2.8 min_version: 2.8
required: false required: false
type: boolean type: boolean
tls_versions:
description: |
A list of TLS protocol versions.
Available versions: SSLv3, TLSv1, TLSv1.1, TLSv1.2, TLSv1.3
in: body
min_version: 2.17
required: true
type: array
tls_versions-optional:
description: |
A list of TLS protocol versions.
Available versions: SSLv3, TLSv1, TLSv1.1, TLSv1.2, TLSv1.3
in: body
min_version: 2.17
required: false
type: array
total_connections: total_connections:
description: | description: |
The total connections handled. The total connections handled.

View File

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

View File

@ -28,6 +28,7 @@
"192.0.2.0/24", "192.0.2.0/24",
"198.51.100.0/24" "198.51.100.0/24"
], ],
"tls_ciphers": "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256" "tls_ciphers": "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256",
"tls_versions": ["TLSv1.2", "TLSv1.3"]
} }
} }

View File

@ -43,6 +43,7 @@
"192.0.2.0/24", "192.0.2.0/24",
"198.51.100.0/24" "198.51.100.0/24"
], ],
"tls_ciphers": "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256" "tls_ciphers": "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256",
"tls_versions": ["TLSv1.2", "TLSv1.3"]
} }
} }

View File

@ -43,6 +43,7 @@
"192.0.2.0/24", "192.0.2.0/24",
"198.51.100.0/24" "198.51.100.0/24"
], ],
"tls_ciphers": "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256" "tls_ciphers": "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256",
"tls_versions": ["TLSv1.2", "TLSv1.3"]
} }
} }

View File

@ -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"], "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 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", "tls_versions": ["TLSv1.2", "TLSv1.3"]}}' http://198.51.100.10:9876/v2/lbaas/listeners/023f2e34-7806-443b-bfae-16c324569a3d

View File

@ -24,6 +24,7 @@
"192.0.2.0/24", "192.0.2.0/24",
"198.51.100.0/24" "198.51.100.0/24"
], ],
"tls_ciphers": "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256" "tls_ciphers": "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256",
"tls_versions": ["TLSv1.2", "TLSv1.3"]
} }
} }

View File

@ -43,6 +43,7 @@
"192.0.2.0/24", "192.0.2.0/24",
"198.51.100.0/24" "198.51.100.0/24"
], ],
"tls_ciphers": "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256" "tls_ciphers": "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256",
"tls_versions": ["TLSv1.2", "TLSv1.3"]
} }
} }

View File

@ -45,7 +45,8 @@
"192.0.2.0/24", "192.0.2.0/24",
"198.51.100.0/24" "198.51.100.0/24"
], ],
"tls_ciphers": "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256" "tls_ciphers": "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256",
"tls_versions": ["TLSv1.2", "TLSv1.3"]
} }
] ]
} }

View File

@ -73,6 +73,7 @@ Response Parameters
- timeout_member_data: timeout_member_data - timeout_member_data: timeout_member_data
- timeout_tcp_inspect: timeout_tcp_inspect - timeout_tcp_inspect: timeout_tcp_inspect
- tls_ciphers: tls_ciphers - tls_ciphers: tls_ciphers
- tls_versions: tls_versions
- updated_at: updated_at - updated_at: updated_at
Response Example Response Example
@ -165,6 +166,7 @@ Request
- timeout_member_data: timeout_member_data-optional - timeout_member_data: timeout_member_data-optional
- timeout_tcp_inspect: timeout_tcp_inspect-optional - timeout_tcp_inspect: timeout_tcp_inspect-optional
- tls_ciphers: tls_ciphers-optional - tls_ciphers: tls_ciphers-optional
- tls_versions: tls_versions-optional
.. _header_insertions: .. _header_insertions:
@ -290,6 +292,7 @@ Response Parameters
- timeout_member_data: timeout_member_data - timeout_member_data: timeout_member_data
- timeout_tcp_inspect: timeout_tcp_inspect - timeout_tcp_inspect: timeout_tcp_inspect
- tls_ciphers: tls_ciphers - tls_ciphers: tls_ciphers
- tls_versions: tls_versions
- updated_at: updated_at - updated_at: updated_at
Response Example Response Example
@ -369,6 +372,7 @@ Response Parameters
- timeout_member_data: timeout_member_data - timeout_member_data: timeout_member_data
- timeout_tcp_inspect: timeout_tcp_inspect - timeout_tcp_inspect: timeout_tcp_inspect
- tls_ciphers: tls_ciphers - tls_ciphers: tls_ciphers
- tls_versions: tls_versions
- updated_at: updated_at - updated_at: updated_at
Response Example Response Example
@ -429,6 +433,7 @@ Request
- timeout_member_data: timeout_member_data-optional - timeout_member_data: timeout_member_data-optional
- timeout_tcp_inspect: timeout_tcp_inspect-optional - timeout_tcp_inspect: timeout_tcp_inspect-optional
- tls_ciphers: tls_ciphers-optional - tls_ciphers: tls_ciphers-optional
- tls_versions: tls_versions-optional
Request Example Request Example
--------------- ---------------
@ -475,6 +480,7 @@ Response Parameters
- timeout_member_data: timeout_member_data - timeout_member_data: timeout_member_data
- timeout_tcp_inspect: timeout_tcp_inspect - timeout_tcp_inspect: timeout_tcp_inspect
- tls_ciphers: tls_ciphers - tls_ciphers: tls_ciphers
- tls_versions: tls_versions
- updated_at: updated_at - updated_at: updated_at
Response Example Response Example

View File

@ -72,6 +72,11 @@
# allowed on listeners, pools, or the default values for either. # allowed on listeners, pools, or the default values for either.
# tls_cipher_blacklist = # tls_cipher_blacklist =
# List of default TLS versions to be used on new TLS-terminated
# listeners. Available versions: SSLv3, TLSv1, TLSv1.1, TLSv1.2, TLSv1.3
# default_listener_tls_versions = TLSv1.2, TLSv1.3
[database] [database]
# This line MUST be changed to actually run the plugin. # This line MUST be changed to actually run the plugin.
# Example: # Example:

View File

@ -109,10 +109,13 @@ class RootController(object):
# Availability Zones # Availability Zones
self._add_a_version(versions, 'v2.14', 'v2', 'SUPPORTED', self._add_a_version(versions, 'v2.14', 'v2', 'SUPPORTED',
'2019-11-10T00:00:00Z', host_url) '2019-11-10T00:00:00Z', host_url)
# TLS version and cipher options # TLS cipher options
self._add_a_version(versions, 'v2.15', 'v2', 'SUPPORTED', self._add_a_version(versions, 'v2.15', 'v2', 'SUPPORTED',
'2020-03-10T00:00:00Z', host_url) '2020-03-10T00:00:00Z', host_url)
# Additional UDP Healthcheck Types (HTTP/TCP) # Additional UDP Healthcheck Types (HTTP/TCP)
self._add_a_version(versions, 'v2.16', 'v2', 'CURRENT', self._add_a_version(versions, 'v2.16', 'v2', 'SUPPORTED',
'2020-03-15T00:00:00Z', host_url) '2020-03-15T00:00:00Z', host_url)
# Listener TLS versions
self._add_a_version(versions, 'v2.17', 'v2', 'CURRENT',
'2020-04-29T00:00:00Z', host_url)
return {'versions': versions} return {'versions': versions}

View File

@ -288,6 +288,10 @@ class ListenersController(base.BaseController):
vip_address = vip_db.ip_address vip_address = vip_db.ip_address
self._validate_cidr_compatible_with_vip(vip_address, allowed_cidrs) self._validate_cidr_compatible_with_vip(vip_address, allowed_cidrs)
# Validate TLS version list
if listener_protocol == constants.PROTOCOL_TERMINATED_HTTPS:
validate.check_tls_version_list(listener_dict['tls_versions'])
try: try:
db_listener = self.repositories.listener.create( db_listener = self.repositories.listener.create(
lock_session, **listener_dict) lock_session, **listener_dict)
@ -494,6 +498,10 @@ class ListenersController(base.BaseController):
'The following ciphers have been blacklisted by an ' 'The following ciphers have been blacklisted by an '
'administrator: ' + ', '.join(rejected_ciphers))) 'administrator: ' + ', '.join(rejected_ciphers)))
# Validate TLS version list
if listener.tls_versions is not wtypes.Unset:
validate.check_tls_version_list(listener.tls_versions)
def _set_default_on_none(self, listener): def _set_default_on_none(self, listener):
"""Reset settings to their default values if None/null was passed in """Reset settings to their default values if None/null was passed in

View File

@ -63,6 +63,7 @@ class ListenerResponse(BaseListenerType):
client_crl_container_ref = wtypes.wsattr(wtypes.StringType()) client_crl_container_ref = wtypes.wsattr(wtypes.StringType())
allowed_cidrs = wtypes.wsattr([types.CidrType()]) allowed_cidrs = wtypes.wsattr([types.CidrType()])
tls_ciphers = wtypes.StringType() tls_ciphers = wtypes.StringType()
tls_versions = wtypes.wsattr(wtypes.ArrayType(wtypes.StringType()))
@classmethod @classmethod
def from_data_model(cls, data_model, children=False): def from_data_model(cls, data_model, children=False):
@ -84,6 +85,8 @@ class ListenerResponse(BaseListenerType):
listener.l7policies = [ listener.l7policies = [
l7policy_type.from_data_model(i) for i in data_model.l7policies] l7policy_type.from_data_model(i) for i in data_model.l7policies]
listener.tls_versions = data_model.tls_versions
return listener return listener
@ -152,6 +155,8 @@ class ListenerPOST(BaseListenerType):
client_crl_container_ref = wtypes.StringType(max_length=255) client_crl_container_ref = wtypes.StringType(max_length=255)
allowed_cidrs = wtypes.wsattr([types.CidrType()]) allowed_cidrs = wtypes.wsattr([types.CidrType()])
tls_ciphers = wtypes.StringType(max_length=2048) tls_ciphers = wtypes.StringType(max_length=2048)
tls_versions = wtypes.wsattr(wtypes.ArrayType(wtypes.StringType(
max_length=32)))
class ListenerRootPOST(types.BaseType): class ListenerRootPOST(types.BaseType):
@ -190,6 +195,8 @@ class ListenerPUT(BaseListenerType):
client_crl_container_ref = wtypes.StringType(max_length=255) client_crl_container_ref = wtypes.StringType(max_length=255)
allowed_cidrs = wtypes.wsattr([types.CidrType()]) allowed_cidrs = wtypes.wsattr([types.CidrType()])
tls_ciphers = wtypes.StringType(max_length=2048) tls_ciphers = wtypes.StringType(max_length=2048)
tls_versions = wtypes.wsattr(wtypes.ArrayType(wtypes.StringType(
max_length=32)))
class ListenerRootPUT(types.BaseType): class ListenerRootPUT(types.BaseType):
@ -241,6 +248,8 @@ class ListenerSingleCreate(BaseListenerType):
client_crl_container_ref = wtypes.StringType(max_length=255) client_crl_container_ref = wtypes.StringType(max_length=255)
allowed_cidrs = wtypes.wsattr([types.CidrType()]) allowed_cidrs = wtypes.wsattr([types.CidrType()])
tls_ciphers = wtypes.StringType(max_length=2048) tls_ciphers = wtypes.StringType(max_length=2048)
tls_versions = wtypes.wsattr(wtypes.ArrayType(wtypes.StringType(
max_length=32)))
class ListenerStatusResponse(BaseListenerType): class ListenerStatusResponse(BaseListenerType):

View File

@ -115,7 +115,11 @@ api_opts = [
"new TLS-enabled pools.")), "new TLS-enabled pools.")),
cfg.StrOpt('tls_cipher_blacklist', default='', cfg.StrOpt('tls_cipher_blacklist', default='',
help=_("Colon separated list of OpenSSL ciphers. " help=_("Colon separated list of OpenSSL ciphers. "
"Usage of these ciphers will be blocked.")) "Usage of these ciphers will be blocked.")),
cfg.ListOpt('default_listener_tls_versions',
default=constants.TLS_VERSIONS_OWASP_SUITE_B,
help=_('List of TLS versions to use for new TLS-enabled '
'listeners.'))
] ]
# Options only used by the amphora agent # Options only used by the amphora agent

View File

@ -785,3 +785,14 @@ CIPHERS_OWASP_SUITE_B = ('TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:'
'ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-SHA256:' 'ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-SHA256:'
'DHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:' 'DHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:'
'ECDHE-RSA-AES128-SHA256') 'ECDHE-RSA-AES128-SHA256')
TLS_VERSIONS_OWASP_SUITE_B = [lib_consts.TLS_VERSION_1_2,
lib_consts.TLS_VERSION_1_3]
# All supported TLS versions in ascending order (oldest to newest)
TLS_ALL_VERSIONS = [
lib_consts.SSL_VERSION_3,
lib_consts.TLS_VERSION_1,
lib_consts.TLS_VERSION_1_1,
lib_consts.TLS_VERSION_1_2,
lib_consts.TLS_VERSION_1_3
]

View File

@ -390,7 +390,7 @@ class Listener(BaseDataModel):
timeout_member_data=None, timeout_tcp_inspect=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, client_crl_container_id=None, client_authentication=None, client_crl_container_id=None,
allowed_cidrs=None, tls_ciphers=None): allowed_cidrs=None, tls_ciphers=None, tls_versions=None):
self.id = id self.id = id
self.project_id = project_id self.project_id = project_id
self.name = name self.name = name
@ -424,6 +424,7 @@ class Listener(BaseDataModel):
self.client_crl_container_id = client_crl_container_id self.client_crl_container_id = client_crl_container_id
self.allowed_cidrs = allowed_cidrs or [] self.allowed_cidrs = allowed_cidrs or []
self.tls_ciphers = tls_ciphers self.tls_ciphers = tls_ciphers
self.tls_versions = tls_versions
def update(self, update_dict): def update(self, update_dict):
for key, value in update_dict.items(): for key, value in update_dict.items():

View File

@ -16,6 +16,7 @@ import os
import re import re
import jinja2 import jinja2
from octavia_lib.common import constants as lib_consts
from octavia.common.config import cfg from octavia.common.config import cfg
from octavia.common import constants from octavia.common import constants
@ -167,7 +168,7 @@ class JinjaTemplater(object):
CONF.amphora_agent.administrative_log_facility, CONF.amphora_agent.administrative_log_facility,
'user_log_facility': CONF.amphora_agent.user_log_facility, 'user_log_facility': CONF.amphora_agent.user_log_facility,
'connection_logging': self.connection_logging}, 'connection_logging': self.connection_logging},
constants=constants) constants=constants, lib_consts=lib_consts)
def _transform_loadbalancer(self, host_amphora, loadbalancer, listeners, def _transform_loadbalancer(self, host_amphora, loadbalancer, listeners,
tls_certs, feature_compatibility): tls_certs, feature_compatibility):
@ -282,9 +283,11 @@ class JinjaTemplater(object):
os.path.join(self.base_crt_dir, loadbalancer.id, os.path.join(self.base_crt_dir, loadbalancer.id,
tls_certs[listener.client_crl_container_id])) tls_certs[listener.client_crl_container_id]))
if (listener.protocol == constants.PROTOCOL_TERMINATED_HTTPS and if listener.protocol == constants.PROTOCOL_TERMINATED_HTTPS:
listener.tls_ciphers is not None): if listener.tls_ciphers is not None:
ret_value['tls_ciphers'] = listener.tls_ciphers ret_value['tls_ciphers'] = listener.tls_ciphers
if listener.tls_versions is not None:
ret_value['tls_versions'] = listener.tls_versions
pools = [] pools = []
pool_gen = (pool for pool in listener.pools if pool_gen = (pool for pool in listener.pools if

View File

@ -31,7 +31,7 @@
{% block proxies %} {% block proxies %}
{% if loadbalancer.enabled %} {% if loadbalancer.enabled %}
{% for listener in loadbalancer.listeners if listener.enabled %} {% for listener in loadbalancer.listeners if listener.enabled %}
{{- frontend_macro(constants, listener, loadbalancer.vip_address) }} {{- frontend_macro(constants, lib_consts, listener, loadbalancer.vip_address) }}
{% for pool in listener.pools if pool.enabled %} {% for pool in listener.pools if pool.enabled %}
{{- backend_macro(constants, listener, pool, loadbalancer) }} {{- backend_macro(constants, listener, pool, loadbalancer) }}
{% endfor %} {% endfor %}

View File

@ -26,7 +26,7 @@ peers {{ "%s_peers"|format(loadbalancer.id.replace("-", ""))|trim() }}
{% endmacro %} {% endmacro %}
{% macro bind_macro(constants, listener, lb_vip_address) %} {% macro bind_macro(constants, lib_consts, listener, lb_vip_address) %}
{% if listener.crt_list_filename is defined %} {% if listener.crt_list_filename is defined %}
{% set def_crt_opt = ("ssl crt-list %s"|format( {% set def_crt_opt = ("ssl crt-list %s"|format(
listener.crt_list_filename)|trim()) %} listener.crt_list_filename)|trim()) %}
@ -48,8 +48,26 @@ peers {{ "%s_peers"|format(loadbalancer.id.replace("-", ""))|trim() }}
{% else %} {% else %}
{% set ciphers_opt = "" %} {% set ciphers_opt = "" %}
{% endif %} {% endif %}
{% set tls_versions_opt = "" %}
{% if listener.tls_versions is defined %}
{% if lib_consts.SSL_VERSION_3 not in listener.tls_versions %}
{% set tls_versions_opt = tls_versions_opt + " no-sslv3" %}
{% endif %}
{% if lib_consts.TLS_VERSION_1 not in listener.tls_versions %}
{% set tls_versions_opt = tls_versions_opt + " no-tlsv10" %}
{% endif %}
{% if lib_consts.TLS_VERSION_1_1 not in listener.tls_versions %}
{% set tls_versions_opt = tls_versions_opt + " no-tlsv11" %}
{% endif %}
{% if lib_consts.TLS_VERSION_1_2 not in listener.tls_versions %}
{% set tls_versions_opt = tls_versions_opt + " no-tlsv12" %}
{% endif %}
{% if lib_consts.TLS_VERSION_1_3 not in listener.tls_versions %}
{% set tls_versions_opt = tls_versions_opt + " no-tlsv13" %}
{% endif %}
{% endif %}
bind {{ lb_vip_address }}:{{ listener.protocol_port }} {{ bind {{ lb_vip_address }}:{{ listener.protocol_port }} {{
"%s %s %s %s"|format(def_crt_opt, client_ca_opt, ca_crl_opt, ciphers_opt)|trim() }} "%s %s %s %s%s"|format(def_crt_opt, client_ca_opt, ca_crl_opt, ciphers_opt, tls_versions_opt)|trim() }}
{% endmacro %} {% endmacro %}
@ -134,7 +152,7 @@ bind {{ lb_vip_address }}:{{ listener.protocol_port }} {{
{% endmacro %} {% endmacro %}
{% macro frontend_macro(constants, listener, lb_vip_address) %} {% macro frontend_macro(constants, lib_consts, listener, lb_vip_address) %}
frontend {{ listener.id }} frontend {{ listener.id }}
{% if listener.connection_limit is defined %} {% if listener.connection_limit is defined %}
maxconn {{ listener.connection_limit }} maxconn {{ listener.connection_limit }}
@ -143,7 +161,7 @@ frontend {{ listener.id }}
constants.PROTOCOL_TERMINATED_HTTPS.lower()) %} constants.PROTOCOL_TERMINATED_HTTPS.lower()) %}
redirect scheme https if !{ ssl_fc } redirect scheme https if !{ ssl_fc }
{% endif %} {% endif %}
{{ bind_macro(constants, listener, lb_vip_address)|trim() }} {{ bind_macro(constants, lib_consts, listener, lb_vip_address)|trim() }}
mode {{ listener.protocol_mode }} mode {{ listener.protocol_mode }}
{% for l7policy in listener.l7policies if (l7policy.enabled and {% for l7policy in listener.l7policies if (l7policy.enabled and
l7policy.l7rules|length > 0) %} l7policy.l7rules|length > 0) %}

View File

@ -459,3 +459,15 @@ def check_default_ciphers_blacklist_conflict():
raise exceptions.ValidationException( raise exceptions.ValidationException(
detail=_('Default pool ciphers conflict with blacklist. ' detail=_('Default pool ciphers conflict with blacklist. '
'Conflicting ciphers: ' + ', '.join(pool_rejected))) 'Conflicting ciphers: ' + ', '.join(pool_rejected)))
def check_tls_version_list(versions):
if versions == []:
raise exceptions.ValidationException(
detail=_('Empty TLS version list. Either specify at least one TLS '
'version or remove this parameter to use the default.'))
invalid_versions = [v for v in versions
if v not in constants.TLS_ALL_VERSIONS]
if invalid_versions:
raise exceptions.ValidationException(
detail=_('Invalid TLS versions: ' + ', '.join(invalid_versions)))

View File

@ -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 tls versions column
Revision ID: e5493ae5f9a7
Revises: fbd705961c3a
Create Date: 2020-04-19 02:35:28.502424
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'e5493ae5f9a7'
down_revision = 'fbd705961c3a'
def upgrade():
op.add_column(
'listener',
sa.Column('tls_versions', sa.String(512), nullable=True)
)

View File

@ -21,6 +21,7 @@ from sqlalchemy.ext import orderinglist
from sqlalchemy import orm from sqlalchemy import orm
from sqlalchemy.orm import validates from sqlalchemy.orm import validates
from sqlalchemy.sql import func from sqlalchemy.sql import func
from sqlalchemy_utils import ScalarListType
from octavia.api.v2.types import amphora from octavia.api.v2.types import amphora
from octavia.api.v2.types import availability_zone_profile from octavia.api.v2.types import availability_zone_profile
@ -522,6 +523,7 @@ class Listener(base_models.BASE, base_models.IdMixin,
nullable=False, default=constants.CLIENT_AUTH_NONE) nullable=False, default=constants.CLIENT_AUTH_NONE)
client_crl_container_id = sa.Column(sa.String(255), nullable=True) client_crl_container_id = sa.Column(sa.String(255), nullable=True)
tls_ciphers = sa.Column(sa.String(2048), nullable=True) tls_ciphers = sa.Column(sa.String(2048), nullable=True)
tls_versions = sa.Column(ScalarListType(), nullable=True)
_tags = orm.relationship( _tags = orm.relationship(
'Tags', 'Tags',

View File

@ -104,11 +104,16 @@ def create_listener(listener_dict, lb_id):
if 'client_authentication' not in listener_dict: if 'client_authentication' not in listener_dict:
listener_dict['client_authentication'] = constants.CLIENT_AUTH_NONE listener_dict['client_authentication'] = constants.CLIENT_AUTH_NONE
if (listener_dict['protocol'] == constants.PROTOCOL_TERMINATED_HTTPS and if listener_dict['protocol'] == constants.PROTOCOL_TERMINATED_HTTPS:
('tls_ciphers' not in listener_dict or if ('tls_ciphers' not in listener_dict or
listener_dict['tls_ciphers'] is None)): listener_dict['tls_ciphers'] is None):
listener_dict['tls_ciphers'] = ( listener_dict['tls_ciphers'] = (
CONF.api_settings.default_listener_ciphers) CONF.api_settings.default_listener_ciphers)
if ('tls_versions' not in listener_dict or
listener_dict['tls_versions'] is None):
listener_dict['tls_versions'] = (
CONF.api_settings.default_listener_tls_versions)
return listener_dict return listener_dict

View File

@ -467,7 +467,8 @@ class SampleDriverDataModels(object):
lib_consts.CLIENT_AUTHENTICATION: constants.CLIENT_AUTH_NONE, lib_consts.CLIENT_AUTHENTICATION: constants.CLIENT_AUTH_NONE,
constants.CLIENT_CRL_CONTAINER_ID: self.client_crl_container_ref, 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 lib_consts.TLS_CIPHERS: constants.CIPHERS_OWASP_SUITE_B,
lib_consts.TLS_VERSIONS: constants.TLS_VERSIONS_OWASP_SUITE_B
} }
self.test_listener1_dict.update(self._common_test_dict) self.test_listener1_dict.update(self._common_test_dict)
@ -536,7 +537,7 @@ class SampleDriverDataModels(object):
lib_consts.CLIENT_CRL_CONTAINER_REF: self.client_crl_container_ref, 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_CIPHERS: constants.CIPHERS_OWASP_SUITE_B,
lib_consts.TLS_VERSIONS: None lib_consts.TLS_VERSIONS: constants.TLS_VERSIONS_OWASP_SUITE_B
} }
self.provider_listener2_dict = copy.deepcopy( self.provider_listener2_dict = copy.deepcopy(

View File

@ -45,7 +45,7 @@ class TestRootController(base_db_test.OctaviaDBTestBase):
def test_api_versions(self): def test_api_versions(self):
versions = self._get_versions_with_config() versions = self._get_versions_with_config()
version_ids = tuple(v.get('id') for v in versions) version_ids = tuple(v.get('id') for v in versions)
self.assertEqual(17, len(version_ids)) self.assertEqual(18, len(version_ids))
self.assertIn('v2.0', version_ids) self.assertIn('v2.0', version_ids)
self.assertIn('v2.1', version_ids) self.assertIn('v2.1', version_ids)
self.assertIn('v2.2', version_ids) self.assertIn('v2.2', version_ids)
@ -63,6 +63,7 @@ class TestRootController(base_db_test.OctaviaDBTestBase):
self.assertIn('v2.14', version_ids) self.assertIn('v2.14', version_ids)
self.assertIn('v2.15', version_ids) self.assertIn('v2.15', version_ids)
self.assertIn('v2.16', version_ids) self.assertIn('v2.16', version_ids)
self.assertIn('v2.17', version_ids)
# Each version should have a 'self' 'href' to the API version URL # Each version should have a 'self' 'href' to the API version URL
# [{u'rel': u'self', u'href': u'http://localhost/v2'}] # [{u'rel': u'self', u'href': u'http://localhost/v2'}]

View File

@ -2622,7 +2622,8 @@ class TestLoadBalancerGraph(base.BaseAPITest):
'client_authentication': constants.CLIENT_AUTH_NONE, 'client_authentication': constants.CLIENT_AUTH_NONE,
'client_crl_container_ref': None, 'client_crl_container_ref': None,
'allowed_cidrs': None, 'allowed_cidrs': None,
'tls_ciphers': None 'tls_ciphers': None,
'tls_versions': None
} }
if create_sni_containers: if create_sni_containers:
create_listener['sni_container_refs'] = create_sni_containers create_listener['sni_container_refs'] = create_sni_containers
@ -2670,6 +2671,8 @@ class TestLoadBalancerGraph(base.BaseAPITest):
expected_listener['allowed_cidrs'] = expected_allowed_cidrs expected_listener['allowed_cidrs'] = expected_allowed_cidrs
if create_protocol == constants.PROTOCOL_TERMINATED_HTTPS: if create_protocol == constants.PROTOCOL_TERMINATED_HTTPS:
expected_listener['tls_ciphers'] = constants.CIPHERS_OWASP_SUITE_B expected_listener['tls_ciphers'] = constants.CIPHERS_OWASP_SUITE_B
expected_listener['tls_versions'] = (
constants.TLS_VERSIONS_OWASP_SUITE_B)
return create_listener, expected_listener return create_listener, expected_listener

View File

@ -127,8 +127,6 @@ class TestUtils(base.TestCase):
'provider': 'noop_driver'} 'provider': 'noop_driver'}
ref_listeners = copy.deepcopy(self.sample_data.provider_listeners) ref_listeners = copy.deepcopy(self.sample_data.provider_listeners)
# TODO(johnsom) Remove when versions implemented # 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,) expect_pools = copy.deepcopy(self.sample_data.provider_pools,)
for pool in expect_pools: for pool in expect_pools:
delattr(pool, lib_constants.TLS_VERSIONS) delattr(pool, lib_constants.TLS_VERSIONS)
@ -204,9 +202,6 @@ class TestUtils(base.TestCase):
provider_listeners = utils.db_listeners_to_provider_listeners( provider_listeners = utils.db_listeners_to_provider_listeners(
self.sample_data.test_db_listeners) self.sample_data.test_db_listeners)
ref_listeners = copy.deepcopy(self.sample_data.provider_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) self.assertEqual(ref_listeners, provider_listeners)
@mock.patch('oslo_context.context.RequestContext', return_value=None) @mock.patch('oslo_context.context.RequestContext', return_value=None)
@ -248,7 +243,6 @@ class TestUtils(base.TestCase):
expect_pool_prov = copy.deepcopy(self.sample_data.provider_pool1_dict) expect_pool_prov = copy.deepcopy(self.sample_data.provider_pool1_dict)
# TODO(johnsom) Remove when versions and ciphers are implemented # TODO(johnsom) Remove when versions and ciphers are implemented
expect_pool_prov.pop(lib_constants.TLS_VERSIONS) expect_pool_prov.pop(lib_constants.TLS_VERSIONS)
expect_prov.pop(lib_constants.TLS_VERSIONS)
expect_prov['default_pool'] = expect_pool_prov expect_prov['default_pool'] = expect_pool_prov
provider_listener = utils.listener_dict_to_provider_dict( provider_listener = utils.listener_dict_to_provider_dict(
self.sample_data.test_listener1_dict) self.sample_data.test_listener1_dict)
@ -284,7 +278,6 @@ class TestUtils(base.TestCase):
del expect_pool_prov['tls_container_data'] del expect_pool_prov['tls_container_data']
# TODO(johnsom) Remove when versions and ciphers are implemented # TODO(johnsom) Remove when versions and ciphers are implemented
expect_pool_prov.pop(lib_constants.TLS_VERSIONS) expect_pool_prov.pop(lib_constants.TLS_VERSIONS)
expect_prov.pop(lib_constants.TLS_VERSIONS)
expect_prov['default_pool'] = expect_pool_prov expect_prov['default_pool'] = expect_pool_prov
del expect_prov['default_tls_container_data'] del expect_prov['default_tls_container_data']
del expect_prov['sni_container_data'] del expect_prov['sni_container_data']

View File

@ -51,7 +51,8 @@ class TestHaproxyCfg(base.TestCase):
"ssl crt-list {crt_list} " "ssl crt-list {crt_list} "
"ca-file /var/lib/octavia/certs/sample_loadbalancer_id_1/" "ca-file /var/lib/octavia/certs/sample_loadbalancer_id_1/"
"client_ca.pem verify required crl-file /var/lib/octavia/" "client_ca.pem verify required crl-file /var/lib/octavia/"
"certs/sample_loadbalancer_id_1/SHA_ID.pem ciphers {ciphers}\n" "certs/sample_loadbalancer_id_1/SHA_ID.pem ciphers {ciphers} "
"no-sslv3 no-tlsv10 no-tlsv11\n"
" mode http\n" " mode http\n"
" default_backend sample_pool_id_1:sample_listener_id_1\n" " default_backend sample_pool_id_1:sample_listener_id_1\n"
" timeout client 50000\n").format( " timeout client 50000\n").format(
@ -104,7 +105,7 @@ class TestHaproxyCfg(base.TestCase):
" maxconn {maxconn}\n" " maxconn {maxconn}\n"
" redirect scheme https if !{{ ssl_fc }}\n" " redirect scheme https if !{{ ssl_fc }}\n"
" bind 10.0.0.2:443 ssl crt-list {crt_list}" " bind 10.0.0.2:443 ssl crt-list {crt_list}"
" ciphers {ciphers}\n" " ciphers {ciphers} no-sslv3 no-tlsv10 no-tlsv11\n"
" mode http\n" " mode http\n"
" default_backend sample_pool_id_1:sample_listener_id_1\n" " default_backend sample_pool_id_1:sample_listener_id_1\n"
" timeout client 50000\n").format( " timeout client 50000\n").format(
@ -153,7 +154,8 @@ class TestHaproxyCfg(base.TestCase):
fe = ("frontend sample_listener_id_1\n" fe = ("frontend sample_listener_id_1\n"
" maxconn {maxconn}\n" " maxconn {maxconn}\n"
" redirect scheme https if !{{ ssl_fc }}\n" " redirect scheme https if !{{ ssl_fc }}\n"
" bind 10.0.0.2:443 ssl crt-list {crt_list}\n" " bind 10.0.0.2:443 ssl crt-list {crt_list} "
"no-sslv3 no-tlsv10 no-tlsv11\n"
" mode http\n" " mode http\n"
" default_backend sample_pool_id_1:sample_listener_id_1\n" " default_backend sample_pool_id_1:sample_listener_id_1\n"
" timeout client 50000\n").format( " timeout client 50000\n").format(
@ -192,6 +194,111 @@ class TestHaproxyCfg(base.TestCase):
frontend=fe, backend=be), frontend=fe, backend=be),
rendered_obj) rendered_obj)
def test_render_template_tls_no_versions(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} "
"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 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)
tls_tupe = {'cont_id_1':
sample_configs_combined.sample_tls_container_tuple(
id='tls_container_id',
certificate='imaCert1', private_key='imaPrivateKey1',
primary_cn='FakeCN'),
'cont_id_ca': 'client_ca.pem',
'cont_id_crl': 'SHA_ID.pem'}
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, sni=True,
client_ca_cert=True, client_crl_cert=True, tls_versions=None)],
tls_tupe)
self.assertEqual(
sample_configs_combined.sample_base_expected_config(
frontend=fe, backend=be),
rendered_obj)
def test_render_template_tls_no_ciphers_or_versions(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}\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)
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_ciphers=None,
tls_versions=None)],
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_http(self): def test_render_template_http(self):
be = ("backend sample_pool_id_1:sample_listener_id_1\n" be = ("backend sample_pool_id_1:sample_listener_id_1\n"
" mode http\n" " mode http\n"
@ -1180,7 +1287,8 @@ class TestHaproxyCfg(base.TestCase):
" maxconn 1000000\n" " maxconn 1000000\n"
" redirect scheme https if !{ ssl_fc }\n" " redirect scheme https if !{ ssl_fc }\n"
" bind 10.0.0.2:443 ciphers " + " bind 10.0.0.2:443 ciphers " +
constants.CIPHERS_OWASP_SUITE_B + "\n" constants.CIPHERS_OWASP_SUITE_B +
" no-sslv3 no-tlsv10 no-tlsv11\n"
" mode http\n" " mode http\n"
" acl sample_l7rule_id_1 path -m beg /api\n" " acl sample_l7rule_id_1 path -m beg /api\n"
" use_backend sample_pool_id_2:sample_listener_id_1" " use_backend sample_pool_id_2:sample_listener_id_1"

View File

@ -602,12 +602,14 @@ def sample_listener_tuple(proto=None, monitor=True, alloc_default_pool=True,
id='sample_listener_id_1', recursive_nest=False, id='sample_listener_id_1', recursive_nest=False,
provisioning_status=constants.ACTIVE, provisioning_status=constants.ACTIVE,
tls_ciphers=constants.CIPHERS_OWASP_SUITE_B, tls_ciphers=constants.CIPHERS_OWASP_SUITE_B,
backend_tls_ciphers=None): backend_tls_ciphers=None,
tls_versions=constants.TLS_VERSIONS_OWASP_SUITE_B):
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 == 'TERMINATED_HTTPS' else proto be_proto = 'HTTP' if proto == 'TERMINATED_HTTPS' else proto
if proto != constants.PROTOCOL_TERMINATED_HTTPS: if proto != constants.PROTOCOL_TERMINATED_HTTPS:
tls_ciphers = None tls_ciphers = None
tls_versions = None
topology = 'SINGLE' if topology is None else topology topology = 'SINGLE' if topology is None else topology
port = '443' if proto in ['HTTPS', 'TERMINATED_HTTPS'] else '80' port = '443' if proto in ['HTTPS', 'TERMINATED_HTTPS'] else '80'
peer_port = 1024 if peer_port is None else peer_port peer_port = 1024 if peer_port is None else peer_port
@ -622,7 +624,7 @@ def sample_listener_tuple(proto=None, monitor=True, alloc_default_pool=True,
'timeout_tcp_inspect, client_ca_tls_certificate_id, ' 'timeout_tcp_inspect, client_ca_tls_certificate_id, '
'client_ca_tls_certificate, client_authentication, ' 'client_ca_tls_certificate, client_authentication, '
'client_crl_container_id, provisioning_status, ' 'client_crl_container_id, provisioning_status, '
'tls_ciphers') 'tls_ciphers, tls_versions')
if l7: if l7:
pools = [ pools = [
sample_pool_tuple( sample_pool_tuple(
@ -737,7 +739,8 @@ def sample_listener_tuple(proto=None, monitor=True, alloc_default_pool=True,
constants.CLIENT_AUTH_NONE), constants.CLIENT_AUTH_NONE),
client_crl_container_id='cont_id_crl' if client_crl_cert else '', client_crl_container_id='cont_id_crl' if client_crl_cert else '',
provisioning_status=provisioning_status, provisioning_status=provisioning_status,
tls_ciphers=tls_ciphers tls_ciphers=tls_ciphers,
tls_versions=tls_versions
) )
if recursive_nest: if recursive_nest:
listener.load_balancer.listeners.append(listener) listener.load_balancer.listeners.append(listener)

View File

@ -470,3 +470,17 @@ class TestValidations(base.TestCase):
self.assertRaises(exceptions.ValidationException, self.assertRaises(exceptions.ValidationException,
validate.check_default_ciphers_blacklist_conflict) validate.check_default_ciphers_blacklist_conflict)
def test_check_tls_version_list(self):
# Test valid list
validate.check_tls_version_list(['TLSv1.1', 'TLSv1.2', 'TLSv1.3'])
# Test invalid list
self.assertRaises(
exceptions.ValidationException,
validate.check_tls_version_list,
['SSLv3', 'TLSv1.0'])
# Test empty list
self.assertRaises(
exceptions.ValidationException,
validate.check_tls_version_list,
[])

View File

@ -0,0 +1,14 @@
---
features:
- |
HTTPS-terminated listeners can now be configured to use only specified
versions of TLS. Default TLS versions for new listeners can be set with
``default_listener_tls_versions`` in ``octavia.conf``. Existing listeners
will continue to use the old defaults.
upgrade:
- |
HTTPS-terminated listeners will now only allow TLS1.2 and TLS1.3 by
default. If no TLS versions are specified at listener create time, the
listener will only accept TLS1.2 and TLS1.3 connections. Previously TLS
listeners would accept any TLS version. Existing listeners will not be
changed.