From 270b973bf96c7a873f495ac6977b8188cf63bf72 Mon Sep 17 00:00:00 2001 From: Dawson Coleman Date: Wed, 22 Apr 2020 14:02:00 -0500 Subject: [PATCH] Add minimum TLS version option in octavia.conf Add new configuration option "minimum_tls_versions" to octavia.conf. Listeners, pools, or the default values for either will be blocked from using lower versions. Change-Id: Ifa0d695c2227772d6b37987a7857fe58ca660dc8 Story: 2006733 Task: 37171 Depends-On: I480b7fb9756d98ba9dbcdfd1d4b193ce6868e291 --- etc/octavia.conf | 4 ++ octavia/api/v2/controllers/listener.py | 8 +++- octavia/api/v2/controllers/pool.py | 8 +++- octavia/common/config.py | 7 +++- octavia/common/validate.py | 37 +++++++++++++++++++ octavia/tests/unit/common/test_validations.py | 28 ++++++++++++++ .../min-tls-version-8e2856fb055ece2c.yaml | 6 +++ 7 files changed, 93 insertions(+), 5 deletions(-) create mode 100644 releasenotes/notes/min-tls-version-8e2856fb055ece2c.yaml diff --git a/etc/octavia.conf b/etc/octavia.conf index 906198913d..9fa9fc6249 100644 --- a/etc/octavia.conf +++ b/etc/octavia.conf @@ -80,6 +80,10 @@ # pools. Available versions: SSLv3, TLSv1, TLSv1.1, TLSv1.2, TLSv1.3 # default_pool_tls_versions = TLSv1.2, TLSv1.3 +# Minimum TLS version to allow for listeners, pools, or the defaults for +# either. Available versions: SSLv3, TLSv1, TLSv1.1, TLSv1.2, TLSv1.3 +# minimum_tls_version = + [database] # This line MUST be changed to actually run the plugin. # Example: diff --git a/octavia/api/v2/controllers/listener.py b/octavia/api/v2/controllers/listener.py index ee13bf490c..0cc9bf2922 100644 --- a/octavia/api/v2/controllers/listener.py +++ b/octavia/api/v2/controllers/listener.py @@ -288,9 +288,11 @@ class ListenersController(base.BaseController): vip_address = vip_db.ip_address self._validate_cidr_compatible_with_vip(vip_address, allowed_cidrs) - # Validate TLS version list if listener_protocol == constants.PROTOCOL_TERMINATED_HTTPS: + # Validate TLS version list validate.check_tls_version_list(listener_dict['tls_versions']) + # Validate TLS versions against minimum + validate.check_tls_version_min(listener_dict['tls_versions']) try: db_listener = self.repositories.listener.create( @@ -498,9 +500,11 @@ class ListenersController(base.BaseController): 'The following ciphers have been blacklisted by an ' 'administrator: ' + ', '.join(rejected_ciphers))) - # Validate TLS version list if listener.tls_versions is not wtypes.Unset: + # Validate TLS version list validate.check_tls_version_list(listener.tls_versions) + # Validate TLS versions against minimum + validate.check_tls_version_min(listener.tls_versions) def _set_default_on_none(self, listener): """Reset settings to their default values if None/null was passed in diff --git a/octavia/api/v2/controllers/pool.py b/octavia/api/v2/controllers/pool.py index dd209b50c4..e1696815a9 100644 --- a/octavia/api/v2/controllers/pool.py +++ b/octavia/api/v2/controllers/pool.py @@ -131,9 +131,11 @@ class PoolsController(base.BaseController): 'The following ciphers have been blacklisted by an ' 'administrator: ' + ', '.join(rejected_ciphers))) - # Validate TLS version list if pool_dict['tls_enabled']: + # Validate TLS version list validate.check_tls_version_list(pool_dict['tls_versions']) + # Validate TLS versions against minimum + validate.check_tls_version_min(pool_dict['tls_versions']) try: return self.repositories.create_pool_on_load_balancer( @@ -399,9 +401,11 @@ class PoolsController(base.BaseController): "The following ciphers have been blacklisted by an " "administrator: " + ', '.join(rejected_ciphers))) - # Validate TLS version list if pool.tls_versions is not wtypes.Unset: + # Validate TLS version list validate.check_tls_version_list(pool.tls_versions) + # Validate TLS version against minimum + validate.check_tls_version_min(pool.tls_versions) @wsme_pecan.wsexpose(pool_types.PoolRootResponse, wtypes.text, body=pool_types.PoolRootPut, status_code=200) diff --git a/octavia/common/config.py b/octavia/common/config.py index a0c6a1c4e9..20c03d274b 100644 --- a/octavia/common/config.py +++ b/octavia/common/config.py @@ -123,7 +123,11 @@ api_opts = [ cfg.ListOpt('default_pool_tls_versions', default=constants.TLS_VERSIONS_OWASP_SUITE_B, help=_('List of TLS versions to use for new TLS-enabled ' - 'pools.')) + 'pools.')), + cfg.StrOpt('minimum_tls_version', + default=None, + choices=constants.TLS_ALL_VERSIONS + [None], + help=_('Minimum allowed TLS version for listeners and pools.')) ] # Options only used by the amphora agent @@ -833,6 +837,7 @@ def init(args, **kwargs): version='%%prog %s' % version.version_info.release_string(), **kwargs) handle_deprecation_compatibility() + validate.check_default_tls_versions_min_conflict() setup_remote_debugger() validate.check_default_ciphers_blacklist_conflict() diff --git a/octavia/common/validate.py b/octavia/common/validate.py index 2dd83f24d1..75fa1f1be9 100644 --- a/octavia/common/validate.py +++ b/octavia/common/validate.py @@ -471,3 +471,40 @@ def check_tls_version_list(versions): if invalid_versions: raise exceptions.ValidationException( detail=_('Invalid TLS versions: ' + ', '.join(invalid_versions))) + + +def check_tls_version_min(versions, message=None): + """Checks a TLS version string against the configured minimum.""" + + if not CONF.api_settings.minimum_tls_version: + return + + if not message: + message = _("Requested TLS versions are less than the minimum: ") + + min_ver_index = constants.TLS_ALL_VERSIONS.index( + CONF.api_settings.minimum_tls_version) + + rejected = [] + for ver in versions: + if constants.TLS_ALL_VERSIONS.index(ver) < min_ver_index: + rejected.append(ver) + if rejected: + raise exceptions.ValidationException(detail=( + message + ', '.join(rejected) + " < " + + CONF.api_settings.minimum_tls_version)) + + +def check_default_tls_versions_min_conflict(): + if not CONF.api_settings.minimum_tls_version: + return + + listener_message = _("Default listener TLS versions are less than the " + "minimum: ") + pool_message = _("Default pool TLS versions are less than the minimum: ") + + check_tls_version_min(CONF.api_settings.default_listener_tls_versions, + message=listener_message) + + check_tls_version_min(CONF.api_settings.default_pool_tls_versions, + message=pool_message) diff --git a/octavia/tests/unit/common/test_validations.py b/octavia/tests/unit/common/test_validations.py index 383014743c..7196b6d689 100644 --- a/octavia/tests/unit/common/test_validations.py +++ b/octavia/tests/unit/common/test_validations.py @@ -484,3 +484,31 @@ class TestValidations(base.TestCase): exceptions.ValidationException, validate.check_tls_version_list, []) + + def test_check_tls_version_min(self): + self.conf.config(group="api_settings", minimum_tls_version='TLSv1.2') + + # Test valid list + validate.check_tls_version_min(['TLSv1.2', 'TLSv1.3']) + + # Test invalid list + self.assertRaises(exceptions.ValidationException, + validate.check_tls_version_min, + ['TLSv1', 'TLSv1.1', 'TLSv1.2']) + + def test_check_default_tls_versions_min_conflict(self): + self.conf.config(group="api_settings", minimum_tls_version='TLSv1.2') + + # Test conflict in listener default + self.conf.config(group="api_settings", default_listener_tls_versions=[ + 'SSLv3', 'TLSv1.2']) + self.assertRaises(exceptions.ValidationException, + validate.check_default_tls_versions_min_conflict) + + # Test conflict in pool default + self.conf.config(group="api_settings", default_listener_tls_versions=[ + 'TLSv1.2']) + self.conf.config(group="api_settings", default_pool_tls_versions=[ + 'TLSv1', 'TLSv1.3']) + self.assertRaises(exceptions.ValidationException, + validate.check_default_tls_versions_min_conflict) diff --git a/releasenotes/notes/min-tls-version-8e2856fb055ece2c.yaml b/releasenotes/notes/min-tls-version-8e2856fb055ece2c.yaml new file mode 100644 index 0000000000..2a45179f60 --- /dev/null +++ b/releasenotes/notes/min-tls-version-8e2856fb055ece2c.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Added ``minimum_tls_version`` to ``octavia.conf``. Listeners, pools, and + the defaults for either will be blocked from using any lower TLS versions. + By default, there is no minumum version.