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:
parent
c9e1551550
commit
85f5b8181b
@ -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:
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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_):
|
||||||
|
@ -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):
|
||||||
|
@ -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)))
|
||||||
|
@ -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)
|
||||||
|
@ -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.
|
Loading…
Reference in New Issue
Block a user