diff --git a/etc/octavia.conf b/etc/octavia.conf index 678ff53b3b..4a8942f26c 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 9492264a51..66d186701b 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 e696b40a39..f1544b6a97 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( @@ -403,9 +405,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 c9aee0e3e4..3a53842e5c 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 @@ -875,6 +879,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.