Add TLS cipher blacklist to octavia.conf

Add new configuration option "tls_cipher_blacklist" to octavia.conf.
Blacklisted ciphers are blocked from being used in listeners, pools, or
default cipher strings.

Change-Id: I44fd4da1b47faee9cc01b9426898a28b6f13f223
Story: 2006627
Task: 37168
This commit is contained in:
Dawson Coleman 2020-04-15 22:32:37 -05:00
parent c9e1551550
commit 85f5b8181b
7 changed files with 88 additions and 0 deletions

View File

@ -68,6 +68,10 @@
# see https://cheatsheetseries.owasp.org/cheatsheets/TLS_Cipher_String_Cheat_Sheet.html # see https://cheatsheetseries.owasp.org/cheatsheets/TLS_Cipher_String_Cheat_Sheet.html
# default_pool_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 # default_pool_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
# Colon-separated list of disallowed ciphers. Ciphers specified here will not be
# allowed on listeners, pools, or the default values for either.
# tls_cipher_blacklist =
[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

@ -33,6 +33,7 @@ from octavia.common import data_models
from octavia.common import exceptions from octavia.common import exceptions
from octavia.common import stats from octavia.common import stats
from octavia.common import utils as common_utils from octavia.common import utils as common_utils
from octavia.common import validate
from octavia.db import api as db_api from octavia.db import api as db_api
from octavia.db import prepare as db_prepare from octavia.db import prepare as db_prepare
from octavia.i18n import _ from octavia.i18n import _
@ -223,6 +224,15 @@ class ListenersController(base.BaseController):
"A client authentication CA reference is required to " "A client authentication CA reference is required to "
"specify a client authentication revocation list.")) "specify a client authentication revocation list."))
# Check TLS cipher blacklist
if 'tls_ciphers' in listener_dict and listener_dict['tls_ciphers']:
rejected_ciphers = validate.check_cipher_blacklist(
listener_dict['tls_ciphers'])
if rejected_ciphers:
raise exceptions.ValidationException(detail=_(
'The following ciphers have been blacklisted by an '
'administrator: ' + ', '.join(rejected_ciphers)))
# Validate the TLS containers # Validate the TLS containers
sni_containers = listener_dict.pop('sni_containers', []) sni_containers = listener_dict.pop('sni_containers', [])
tls_refs = [sni['tls_container_id'] for sni in sni_containers] tls_refs = [sni['tls_container_id'] for sni in sni_containers]
@ -475,6 +485,15 @@ class ListenersController(base.BaseController):
self._validate_cidr_compatible_with_vip( self._validate_cidr_compatible_with_vip(
vip_address, listener.allowed_cidrs) vip_address, listener.allowed_cidrs)
# Check TLS cipher blacklist
if listener.tls_ciphers:
rejected_ciphers = validate.check_cipher_blacklist(
listener.tls_ciphers)
if rejected_ciphers:
raise exceptions.ValidationException(detail=_(
'The following ciphers have been blacklisted by an '
'administrator: ' + ', '.join(rejected_ciphers)))
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

@ -122,6 +122,15 @@ class PoolsController(base.BaseController):
pool_dict.get('ca_tls_certificate_id'), pool_dict.get('ca_tls_certificate_id'),
pool_dict.get('crl_container_id', None)) pool_dict.get('crl_container_id', None))
# Check TLS cipher blacklist
if 'tls_ciphers' in pool_dict and pool_dict['tls_ciphers']:
rejected_ciphers = validate.check_cipher_blacklist(
pool_dict['tls_ciphers'])
if rejected_ciphers:
raise exceptions.ValidationException(detail=_(
'The following ciphers have been blacklisted by an '
'administrator: ' + ', '.join(rejected_ciphers)))
try: try:
return self.repositories.create_pool_on_load_balancer( return self.repositories.create_pool_on_load_balancer(
lock_session, pool_dict, lock_session, pool_dict,
@ -368,6 +377,15 @@ class PoolsController(base.BaseController):
if ca_ref: if ca_ref:
self._validate_client_ca_and_crl_refs(ca_ref, crl_ref) self._validate_client_ca_and_crl_refs(ca_ref, crl_ref)
# Check TLS cipher blacklist
if pool.tls_ciphers:
rejected_ciphers = validate.check_cipher_blacklist(
pool.tls_ciphers)
if rejected_ciphers:
raise exceptions.ValidationException(detail=_(
"The following ciphers have been blacklisted by an "
"administrator: " + ', '.join(rejected_ciphers)))
@wsme_pecan.wsexpose(pool_types.PoolRootResponse, wtypes.text, @wsme_pecan.wsexpose(pool_types.PoolRootResponse, wtypes.text,
body=pool_types.PoolRootPut, status_code=200) body=pool_types.PoolRootPut, status_code=200)
def put(self, id, pool_): def put(self, id, pool_):

View File

@ -31,6 +31,7 @@ import oslo_messaging as messaging
from octavia.certificates.common import local from octavia.certificates.common import local
from octavia.common import constants from octavia.common import constants
from octavia.common import utils from octavia.common import utils
from octavia.common import validate
from octavia.i18n import _ from octavia.i18n import _
from octavia import version from octavia import version
@ -112,6 +113,9 @@ api_opts = [
default=constants.CIPHERS_OWASP_SUITE_B, default=constants.CIPHERS_OWASP_SUITE_B,
help=_("Default OpenSSL cipher string (colon-separated) for " help=_("Default OpenSSL cipher string (colon-separated) for "
"new TLS-enabled pools.")), "new TLS-enabled pools.")),
cfg.StrOpt('tls_cipher_blacklist', default='',
help=_("Colon separated list of OpenSSL ciphers. "
"Usage of these ciphers will be blocked."))
] ]
# Options only used by the amphora agent # Options only used by the amphora agent
@ -816,6 +820,7 @@ def init(args, **kwargs):
**kwargs) **kwargs)
handle_deprecation_compatibility() handle_deprecation_compatibility()
setup_remote_debugger() setup_remote_debugger()
validate.check_default_ciphers_blacklist_conflict()
def setup_logging(conf): def setup_logging(conf):

View File

@ -432,3 +432,29 @@ def is_flavor_spares_compatible(flavor):
if flavor.get(constants.COMPUTE_FLAVOR, None): if flavor.get(constants.COMPUTE_FLAVOR, None):
return False return False
return True return True
def check_cipher_blacklist(cipherstring):
ciphers = cipherstring.split(':')
blacklist = CONF.api_settings.tls_cipher_blacklist.split(':')
rejected = []
for cipher in ciphers:
if cipher in blacklist:
rejected.append(cipher)
return rejected
def check_default_ciphers_blacklist_conflict():
listener_rejected = check_cipher_blacklist(
CONF.api_settings.default_listener_ciphers)
if listener_rejected:
raise exceptions.ValidationException(
detail=_('Default listener ciphers conflict with blacklist. '
'Conflicting ciphers: ' + ', '.join(listener_rejected)))
pool_rejected = check_cipher_blacklist(
CONF.api_settings.default_pool_ciphers)
if pool_rejected:
raise exceptions.ValidationException(
detail=_('Default pool ciphers conflict with blacklist. '
'Conflicting ciphers: ' + ', '.join(pool_rejected)))

View File

@ -460,3 +460,13 @@ class TestValidations(base.TestCase):
self.assertTrue(validate.is_flavor_spares_compatible(compat_flavor)) self.assertTrue(validate.is_flavor_spares_compatible(compat_flavor))
self.assertFalse( self.assertFalse(
validate.is_flavor_spares_compatible(not_compat_flavor)) validate.is_flavor_spares_compatible(not_compat_flavor))
def test_check_default_ciphers_blacklist_conflict(self):
self.conf.config(group='api_settings',
tls_cipher_blacklist='PSK-AES128-CBC-SHA')
self.conf.config(group='api_settings',
default_listener_ciphers='ECDHE-ECDSA-AES256-SHA:'
'PSK-AES128-CBC-SHA:TLS_AES_256_GCM_SHA384')
self.assertRaises(exceptions.ValidationException,
validate.check_default_ciphers_blacklist_conflict)

View File

@ -0,0 +1,6 @@
---
features:
- |
Added ``tls_cipher_blacklist`` to ``octavia.conf``. Listeners, pools, and
the default values for either will be blocked from using any of these ciphers.
By default, no ciphers are blacklisted.