Add ability to specify TLS cipher list for pools

Pools can now be each be assigned an OpenSSL cipher string with the
field tls_ciphers.  A new configuration option, default_pool_ciphers,
specifies what cipher string to use for new tls-enabled pools
if one is not explicitly specified at time of creation.

Change-Id: Iedb7774bfb8d70ea307d6a513248e1fe2389fa34
Depends-On: I77da6f14063877af0077f2c12df1aab5d5ead187
Story: 2006627
Task: 37172
This commit is contained in:
Dawson Coleman 2020-04-01 10:30:55 -05:00
parent 78863cf7ee
commit d47f164a60
24 changed files with 150 additions and 31 deletions

View File

@ -1 +1 @@
curl -X POST -H "Content-Type: application/json" -H "X-Auth-Token: <token>" -d '{"pool":{"lb_algorithm":"ROUND_ROBIN","protocol":"HTTP","description":"Super Round Robin Pool","admin_state_up":true,"session_persistence":{"cookie_name":"ChocolateChip","type":"APP_COOKIE"},"listener_id":"023f2e34-7806-443b-bfae-16c324569a3d","name":"super-pool","tags":["test_tag"],"tls_container_ref":"http://198.51.100.10:9311/v1/containers/4073846f-1d5e-42e1-a4cf-a7046419d0e6","ca_tls_container_ref":"http://198.51.100.10:9311/v1/containers/5f0d5540-fae6-4646-85d6-8a84883807fb","crl_container_ref":"http://198.51.100.10:9311/v1/containers/6faf0a01-6892-454c-aaac-650282820c0b","tls_enabled":true}}' http://198.51.100.10:9876/v2/lbaas/pools curl -X POST -H "Content-Type: application/json" -H "X-Auth-Token: <token>" -d '{"pool":{"lb_algorithm":"ROUND_ROBIN","protocol":"HTTP","description":"Super Round Robin Pool","admin_state_up":true,"session_persistence":{"cookie_name":"ChocolateChip","type":"APP_COOKIE"},"listener_id":"023f2e34-7806-443b-bfae-16c324569a3d","name":"super-pool","tags":["test_tag"],"tls_container_ref":"http://198.51.100.10:9311/v1/containers/4073846f-1d5e-42e1-a4cf-a7046419d0e6","ca_tls_container_ref":"http://198.51.100.10:9311/v1/containers/5f0d5540-fae6-4646-85d6-8a84883807fb","crl_container_ref":"http://198.51.100.10:9311/v1/containers/6faf0a01-6892-454c-aaac-650282820c0b","tls_enabled":true,"tls_ciphers":"ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256"}}' http://198.51.100.10:9876/v2/lbaas/pools

View File

@ -14,6 +14,7 @@
"tls_container_ref": "http://198.51.100.10:9311/v1/containers/4073846f-1d5e-42e1-a4cf-a7046419d0e6", "tls_container_ref": "http://198.51.100.10:9311/v1/containers/4073846f-1d5e-42e1-a4cf-a7046419d0e6",
"ca_tls_container_ref": "http://198.51.100.10:9311/v1/containers/5f0d5540-fae6-4646-85d6-8a84883807fb", "ca_tls_container_ref": "http://198.51.100.10:9311/v1/containers/5f0d5540-fae6-4646-85d6-8a84883807fb",
"crl_container_ref": "http://198.51.100.10:9311/v1/containers/6faf0a01-6892-454c-aaac-650282820c0b", "crl_container_ref": "http://198.51.100.10:9311/v1/containers/6faf0a01-6892-454c-aaac-650282820c0b",
"tls_enabled": true "tls_enabled": true,
"tls_ciphers": "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256"
} }
} }

View File

@ -31,6 +31,7 @@
"tls_container_ref": "http://198.51.100.10:9311/v1/containers/4073846f-1d5e-42e1-a4cf-a7046419d0e6", "tls_container_ref": "http://198.51.100.10:9311/v1/containers/4073846f-1d5e-42e1-a4cf-a7046419d0e6",
"ca_tls_container_ref": "http://198.51.100.10:9311/v1/containers/5f0d5540-fae6-4646-85d6-8a84883807fb", "ca_tls_container_ref": "http://198.51.100.10:9311/v1/containers/5f0d5540-fae6-4646-85d6-8a84883807fb",
"crl_container_ref": "http://198.51.100.10:9311/v1/containers/6faf0a01-6892-454c-aaac-650282820c0b", "crl_container_ref": "http://198.51.100.10:9311/v1/containers/6faf0a01-6892-454c-aaac-650282820c0b",
"tls_enabled": true "tls_enabled": true,
"tls_ciphers": "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256"
} }
} }

View File

@ -31,6 +31,7 @@
"tls_container_ref": "http://198.51.100.10:9311/v1/containers/4073846f-1d5e-42e1-a4cf-a7046419d0e6", "tls_container_ref": "http://198.51.100.10:9311/v1/containers/4073846f-1d5e-42e1-a4cf-a7046419d0e6",
"ca_tls_container_ref": "http://198.51.100.10:9311/v1/containers/5f0d5540-fae6-4646-85d6-8a84883807fb", "ca_tls_container_ref": "http://198.51.100.10:9311/v1/containers/5f0d5540-fae6-4646-85d6-8a84883807fb",
"crl_container_ref": "http://198.51.100.10:9311/v1/containers/6faf0a01-6892-454c-aaac-650282820c0b", "crl_container_ref": "http://198.51.100.10:9311/v1/containers/6faf0a01-6892-454c-aaac-650282820c0b",
"tls_enabled": false "tls_enabled": false,
"tls_ciphers": "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256"
} }
} }

View File

@ -1 +1 @@
curl -X PUT -H "Content-Type: application/json" -H "X-Auth-Token: <token>" -d '{"pool":{"lb_algorithm":"LEAST_CONNECTIONS","session_persistence":{"type":"SOURCE_IP"},"description":"second description","name":"second_name","tags":["updated_tag"],"tls_container_ref":"http://198.51.100.10:9311/v1/containers/c1cd501d-3cf9-4873-a11b-a74bebcde929","ca_tls_container_ref":null,"crl_container_ref":null,"tls_enabled":false}}' http://198.51.100.10:9876/v2/lbaas/pools/4029d267-3983-4224-a3d0-afb3fe16a2cd curl -X PUT -H "Content-Type: application/json" -H "X-Auth-Token: <token>" -d '{"pool":{"lb_algorithm":"LEAST_CONNECTIONS","session_persistence":{"type":"SOURCE_IP"},"description":"second description","name":"second_name","tags":["updated_tag"],"tls_container_ref":"http://198.51.100.10:9311/v1/containers/c1cd501d-3cf9-4873-a11b-a74bebcde929","ca_tls_container_ref":null,"crl_container_ref":null,"tls_enabled":false,"tls_ciphers":"ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256"}}' http://198.51.100.10:9876/v2/lbaas/pools/4029d267-3983-4224-a3d0-afb3fe16a2cd

View File

@ -10,6 +10,7 @@
"tls_container_ref": "http://198.51.100.10:9311/v1/containers/c1cd501d-3cf9-4873-a11b-a74bebcde929", "tls_container_ref": "http://198.51.100.10:9311/v1/containers/c1cd501d-3cf9-4873-a11b-a74bebcde929",
"ca_tls_container_ref": null, "ca_tls_container_ref": null,
"crl_container_ref": null, "crl_container_ref": null,
"tls_enabled": false "tls_enabled": false,
"tls_ciphers": "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256"
} }
} }

View File

@ -31,6 +31,7 @@
"tls_container_ref": "http://198.51.100.10:9311/v1/containers/c1cd501d-3cf9-4873-a11b-a74bebcde929", "tls_container_ref": "http://198.51.100.10:9311/v1/containers/c1cd501d-3cf9-4873-a11b-a74bebcde929",
"ca_tls_container_ref": null, "ca_tls_container_ref": null,
"crl_container_ref": null, "crl_container_ref": null,
"tls_enabled": false "tls_enabled": false,
"tls_ciphers": "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256"
} }
} }

View File

@ -37,7 +37,8 @@
"tls_container_ref": "http://198.51.100.10:9311/v1/containers/4073846f-1d5e-42e1-a4cf-a7046419d0e6", "tls_container_ref": "http://198.51.100.10:9311/v1/containers/4073846f-1d5e-42e1-a4cf-a7046419d0e6",
"ca_tls_container_ref": "http://198.51.100.10:9311/v1/containers/5f0d5540-fae6-4646-85d6-8a84883807fb", "ca_tls_container_ref": "http://198.51.100.10:9311/v1/containers/5f0d5540-fae6-4646-85d6-8a84883807fb",
"crl_container_ref": "http://198.51.100.10:9311/v1/containers/6faf0a01-6892-454c-aaac-650282820c0b", "crl_container_ref": "http://198.51.100.10:9311/v1/containers/6faf0a01-6892-454c-aaac-650282820c0b",
"tls_enabled": true "tls_enabled": true,
"tls_ciphers": "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256"
} }
] ]
} }

View File

@ -63,6 +63,7 @@ Response Parameters
- provisioning_status: provisioning_status - provisioning_status: provisioning_status
- session_persistence: session_persistence - session_persistence: session_persistence
- tags: tags - tags: tags
- tls_ciphers: tls_ciphers
- tls_container_ref: tls_container_ref - tls_container_ref: tls_container_ref
- tls_enabled: tls_enabled - tls_enabled: tls_enabled
- updated_at: updated_at - updated_at: updated_at
@ -179,6 +180,7 @@ Request
- session_persistence: session_persistence-optional - session_persistence: session_persistence-optional
- tags: tags-optional - tags: tags-optional
- tls_enabled: tls_enabled-optional - tls_enabled: tls_enabled-optional
- tls_ciphers: tls_ciphers-optional
- tls_container_ref: tls_container_ref-optional - tls_container_ref: tls_container_ref-optional
.. _session_persistence: .. _session_persistence:
@ -260,6 +262,7 @@ Response Parameters
- session_persistence: session_persistence - session_persistence: session_persistence
- tags: tags - tags: tags
- tls_enabled: tls_enabled - tls_enabled: tls_enabled
- tls_ciphers: tls_ciphers
- tls_container_ref: tls_container_ref - tls_container_ref: tls_container_ref
- updated_at: updated_at - updated_at: updated_at
@ -331,6 +334,7 @@ Response Parameters
- session_persistence: session_persistence - session_persistence: session_persistence
- tags: tags - tags: tags
- tls_enabled: tls_enabled - tls_enabled: tls_enabled
- tls_ciphers: tls_ciphers
- tls_container_ref: tls_container_ref - tls_container_ref: tls_container_ref
- updated_at: updated_at - updated_at: updated_at
@ -383,6 +387,7 @@ Request
- session_persistence: session_persistence-optional - session_persistence: session_persistence-optional
- tags: tags-optional - tags: tags-optional
- tls_enabled: tls_enabled-optional - tls_enabled: tls_enabled-optional
- tls_ciphers: tls_ciphers-optional
- tls_container_ref: tls_container_ref-optional - tls_container_ref: tls_container_ref-optional
Request Example Request Example
@ -421,6 +426,7 @@ Response Parameters
- session_persistence: session_persistence - session_persistence: session_persistence
- tags: tags - tags: tags
- tls_enabled: tls_enabled - tls_enabled: tls_enabled
- tls_ciphers: tls_ciphers
- tls_container_ref: tls_container_ref - tls_container_ref: tls_container_ref
- updated_at: updated_at - updated_at: updated_at

View File

@ -62,6 +62,12 @@
# 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_listener_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_listener_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 cipher string for new TLS-enabled pools, in OpenSSL format.
# Cipher strings are in OpenSSL format, see https://www.openssl.org/docs/man1.1.1/man1/ciphers.html
# This example is the "Broad Compatibility" cipher string from OWASP,
# 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
[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

@ -83,6 +83,7 @@ class PoolResponse(BasePoolType):
ca_tls_container_ref = wtypes.wsattr(wtypes.StringType()) ca_tls_container_ref = wtypes.wsattr(wtypes.StringType())
crl_container_ref = wtypes.wsattr(wtypes.StringType()) crl_container_ref = wtypes.wsattr(wtypes.StringType())
tls_enabled = wtypes.wsattr(bool) tls_enabled = wtypes.wsattr(bool)
tls_ciphers = wtypes.StringType()
@classmethod @classmethod
def from_data_model(cls, data_model, children=False): def from_data_model(cls, data_model, children=False):
@ -158,6 +159,7 @@ class PoolPOST(BasePoolType):
ca_tls_container_ref = wtypes.wsattr(wtypes.StringType(max_length=255)) ca_tls_container_ref = wtypes.wsattr(wtypes.StringType(max_length=255))
crl_container_ref = wtypes.wsattr(wtypes.StringType(max_length=255)) crl_container_ref = wtypes.wsattr(wtypes.StringType(max_length=255))
tls_enabled = wtypes.wsattr(bool, default=False) tls_enabled = wtypes.wsattr(bool, default=False)
tls_ciphers = wtypes.StringType(max_length=2048)
class PoolRootPOST(types.BaseType): class PoolRootPOST(types.BaseType):
@ -177,6 +179,7 @@ class PoolPUT(BasePoolType):
ca_tls_container_ref = wtypes.wsattr(wtypes.StringType(max_length=255)) ca_tls_container_ref = wtypes.wsattr(wtypes.StringType(max_length=255))
crl_container_ref = wtypes.wsattr(wtypes.StringType(max_length=255)) crl_container_ref = wtypes.wsattr(wtypes.StringType(max_length=255))
tls_enabled = wtypes.wsattr(bool) tls_enabled = wtypes.wsattr(bool)
tls_ciphers = wtypes.StringType(max_length=2048)
class PoolRootPut(types.BaseType): class PoolRootPut(types.BaseType):
@ -199,6 +202,7 @@ class PoolSingleCreate(BasePoolType):
ca_tls_container_ref = wtypes.wsattr(wtypes.StringType(max_length=255)) ca_tls_container_ref = wtypes.wsattr(wtypes.StringType(max_length=255))
crl_container_ref = wtypes.wsattr(wtypes.StringType(max_length=255)) crl_container_ref = wtypes.wsattr(wtypes.StringType(max_length=255))
tls_enabled = wtypes.wsattr(bool, default=False) tls_enabled = wtypes.wsattr(bool, default=False)
tls_ciphers = wtypes.StringType(max_length=2048)
class PoolStatusResponse(BasePoolType): class PoolStatusResponse(BasePoolType):

View File

@ -107,6 +107,10 @@ 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 listeners.")), "new TLS-enabled listeners.")),
cfg.StrOpt('default_pool_ciphers',
default=constants.CIPHERS_OWASP_SUITE_B,
help=_("Default OpenSSL cipher string (colon-separated) for "
"new TLS-enabled pools.")),
] ]
# Options only used by the amphora agent # Options only used by the amphora agent

View File

@ -273,7 +273,7 @@ class Pool(BaseDataModel):
created_at=None, updated_at=None, provisioning_status=None, created_at=None, updated_at=None, provisioning_status=None,
tags=None, tls_certificate_id=None, tags=None, tls_certificate_id=None,
ca_tls_certificate_id=None, crl_container_id=None, ca_tls_certificate_id=None, crl_container_id=None,
tls_enabled=None): tls_enabled=None, tls_ciphers=None):
self.id = id self.id = id
self.project_id = project_id self.project_id = project_id
self.name = name self.name = name
@ -297,6 +297,7 @@ class Pool(BaseDataModel):
self.ca_tls_certificate_id = ca_tls_certificate_id self.ca_tls_certificate_id = ca_tls_certificate_id
self.crl_container_id = crl_container_id self.crl_container_id = crl_container_id
self.tls_enabled = tls_enabled self.tls_enabled = tls_enabled
self.tls_ciphers = tls_ciphers
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

@ -350,6 +350,8 @@ class JinjaTemplater(object):
if (pool.tls_certificate_id and pool_tls_certs and if (pool.tls_certificate_id and pool_tls_certs and
pool_tls_certs.get('client_cert')): pool_tls_certs.get('client_cert')):
ret_value['client_cert'] = pool_tls_certs.get('client_cert') ret_value['client_cert'] = pool_tls_certs.get('client_cert')
if pool.tls_enabled is True and pool.tls_ciphers is not None:
ret_value['tls_ciphers'] = pool.tls_ciphers
if (pool.ca_tls_certificate_id and pool_tls_certs and if (pool.ca_tls_certificate_id and pool_tls_certs and
pool_tls_certs.get('ca_cert')): pool_tls_certs.get('ca_cert')):
ret_value['ca_cert'] = pool_tls_certs.get('ca_cert') ret_value['ca_cert'] = pool_tls_certs.get('ca_cert')

View File

@ -231,11 +231,16 @@ frontend {{ listener.id }}
{% elif pool.tls_enabled %} {% elif pool.tls_enabled %}
{% set def_verify_opt = " verify none" %} {% set def_verify_opt = " verify none" %}
{% endif %} {% endif %}
{{ "server %s %s:%d weight %s%s%s%s%s%s%s%s%s%s%s%s"|e|format( {% if pool.tls_ciphers is defined %}
{% set ciphers_opt = " ciphers %s"|format(pool.tls_ciphers) %}
{% else %}
{% set ciphers_opt = "" %}
{% endif %}
{{ "server %s %s:%d weight %s%s%s%s%s%s%s%s%s%s%s%s%s"|e|format(
member.id, member.address, member.protocol_port, member.weight, member.id, member.address, member.protocol_port, member.weight,
hm_opt, persistence_opt, proxy_protocol_opt, member_backup_opt, hm_opt, persistence_opt, proxy_protocol_opt, member_backup_opt,
member_enabled_opt, def_opt_prefix, def_crt_opt, ca_opt, crl_opt, member_enabled_opt, def_opt_prefix, def_crt_opt, ca_opt, crl_opt,
def_verify_opt, def_sni_opt)|trim() }} def_verify_opt, def_sni_opt, ciphers_opt)|trim() }}
{% endmacro %} {% endmacro %}

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 pool ciphers column
Revision ID: fbd705961c3a
Revises: 7c36b277bfb0
Create Date: 2020-03-31 14:19:25.280946
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'fbd705961c3a'
down_revision = '7c36b277bfb0'
def upgrade():
op.add_column(
'pool',
sa.Column('tls_ciphers', sa.String(2048), nullable=True)
)

View File

@ -336,6 +336,7 @@ class Pool(base_models.BASE, base_models.IdMixin, base_models.ProjectMixin,
ca_tls_certificate_id = sa.Column(sa.String(255), nullable=True) ca_tls_certificate_id = sa.Column(sa.String(255), nullable=True)
crl_container_id = sa.Column(sa.String(255), nullable=True) crl_container_id = sa.Column(sa.String(255), nullable=True)
tls_enabled = sa.Column(sa.Boolean, default=False, nullable=False) tls_enabled = sa.Column(sa.Boolean, default=False, nullable=False)
tls_ciphers = sa.Column(sa.String(2048), nullable=True)
# This property should be a unique list of any listeners that reference # This property should be a unique list of any listeners that reference
# this pool as its default_pool and any listeners referenced by enabled # this pool as its default_pool and any listeners referenced by enabled

View File

@ -169,6 +169,8 @@ def create_pool(pool_dict, lb_id=None):
prepped_members = [] prepped_members = []
for member_dict in pool_dict.get('members'): for member_dict in pool_dict.get('members'):
prepped_members.append(create_member(member_dict, pool_dict['id'])) prepped_members.append(create_member(member_dict, pool_dict['id']))
if pool_dict['tls_enabled'] is True and pool_dict['tls_ciphers'] is None:
pool_dict['tls_ciphers'] = CONF.api_settings.default_pool_ciphers
pool_dict[constants.PROVISIONING_STATUS] = constants.PENDING_CREATE pool_dict[constants.PROVISIONING_STATUS] = constants.PENDING_CREATE
pool_dict[constants.OPERATING_STATUS] = constants.OFFLINE pool_dict[constants.OPERATING_STATUS] = constants.OFFLINE
return pool_dict return pool_dict

View File

@ -247,7 +247,8 @@ class SampleDriverDataModels(object):
constants.TLS_CERTIFICATE_ID: self.pool_sni_container_ref, constants.TLS_CERTIFICATE_ID: self.pool_sni_container_ref,
constants.CA_TLS_CERTIFICATE_ID: self.pool_ca_container_ref, constants.CA_TLS_CERTIFICATE_ID: self.pool_ca_container_ref,
constants.CRL_CONTAINER_ID: self.pool_crl_container_ref, constants.CRL_CONTAINER_ID: self.pool_crl_container_ref,
lib_consts.TLS_ENABLED: True} lib_consts.TLS_ENABLED: True,
lib_consts.TLS_CIPHERS: None}
self.test_pool1_dict.update(self._common_test_dict) self.test_pool1_dict.update(self._common_test_dict)

View File

@ -185,7 +185,7 @@ class AllRepositoriesTest(base.OctaviaDBTestBase):
'provisioning_status': constants.ACTIVE, 'provisioning_status': constants.ACTIVE,
'tags': ['test_tag'], 'tags': ['test_tag'],
'tls_certificate_id': uuidutils.generate_uuid(), 'tls_certificate_id': uuidutils.generate_uuid(),
'tls_enabled': False} 'tls_enabled': False, 'tls_ciphers': None}
pool_dm = self.repos.create_pool_on_load_balancer( pool_dm = self.repos.create_pool_on_load_balancer(
self.session, pool, listener_id=self.listener.id) self.session, pool, listener_id=self.listener.id)
pool_dm_dict = pool_dm.to_dict() pool_dm_dict = pool_dm.to_dict()
@ -217,7 +217,8 @@ class AllRepositoriesTest(base.OctaviaDBTestBase):
'provisioning_status': constants.ACTIVE, 'provisioning_status': constants.ACTIVE,
'tags': ['test_tag'], 'tags': ['test_tag'],
'tls_certificate_id': uuidutils.generate_uuid(), 'tls_certificate_id': uuidutils.generate_uuid(),
'tls_enabled': False} 'tls_enabled': False,
'tls_ciphers': None}
sp = {'type': constants.SESSION_PERSISTENCE_HTTP_COOKIE, sp = {'type': constants.SESSION_PERSISTENCE_HTTP_COOKIE,
'cookie_name': 'cookie_monster', 'cookie_name': 'cookie_monster',
'pool_id': pool['id'], 'pool_id': pool['id'],
@ -260,7 +261,8 @@ class AllRepositoriesTest(base.OctaviaDBTestBase):
'project_id': uuidutils.generate_uuid(), 'project_id': uuidutils.generate_uuid(),
'id': uuidutils.generate_uuid(), 'id': uuidutils.generate_uuid(),
'provisioning_status': constants.ACTIVE, 'provisioning_status': constants.ACTIVE,
'tags': ['test_tag'], 'tls_enabled': False} 'tags': ['test_tag'], 'tls_enabled': False,
'tls_ciphers': None}
pool_dm = self.repos.create_pool_on_load_balancer( pool_dm = self.repos.create_pool_on_load_balancer(
self.session, pool, listener_id=self.listener.id) self.session, pool, listener_id=self.listener.id)
update_pool = {'protocol': constants.PROTOCOL_TCP, 'name': 'up_pool'} update_pool = {'protocol': constants.PROTOCOL_TCP, 'name': 'up_pool'}
@ -295,7 +297,7 @@ class AllRepositoriesTest(base.OctaviaDBTestBase):
'provisioning_status': constants.ACTIVE, 'provisioning_status': constants.ACTIVE,
'tags': ['test_tag'], 'tags': ['test_tag'],
'tls_certificate_id': uuidutils.generate_uuid(), 'tls_certificate_id': uuidutils.generate_uuid(),
'tls_enabled': False} 'tls_enabled': False, 'tls_ciphers': None}
sp = {'type': constants.SESSION_PERSISTENCE_HTTP_COOKIE, sp = {'type': constants.SESSION_PERSISTENCE_HTTP_COOKIE,
'cookie_name': 'cookie_monster', 'cookie_name': 'cookie_monster',
'pool_id': pool['id'], 'pool_id': pool['id'],
@ -399,7 +401,7 @@ class AllRepositoriesTest(base.OctaviaDBTestBase):
'project_id': uuidutils.generate_uuid(), 'project_id': uuidutils.generate_uuid(),
'id': uuidutils.generate_uuid(), 'id': uuidutils.generate_uuid(),
'provisioning_status': constants.ACTIVE, 'provisioning_status': constants.ACTIVE,
'tls_enabled': False} 'tls_enabled': False, 'tls_ciphers': None}
pool_dm = self.repos.create_pool_on_load_balancer( pool_dm = self.repos.create_pool_on_load_balancer(
self.session, pool, listener_id=self.listener.id) self.session, pool, listener_id=self.listener.id)
update_pool = {'tls_certificate_id': uuidutils.generate_uuid()} update_pool = {'tls_certificate_id': uuidutils.generate_uuid()}

View File

@ -146,7 +146,6 @@ class TestUtils(base.TestCase):
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)
delattr(pool, lib_constants.TLS_CIPHERS)
ref_prov_lb_dict = { ref_prov_lb_dict = {
'vip_address': self.sample_data.ip_address, 'vip_address': self.sample_data.ip_address,
'admin_state_up': True, 'admin_state_up': True,
@ -263,7 +262,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_pool_prov.pop(lib_constants.TLS_CIPHERS)
expect_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(
@ -300,7 +298,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_pool_prov.pop(lib_constants.TLS_CIPHERS)
expect_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']
@ -340,7 +337,6 @@ class TestUtils(base.TestCase):
# TODO(johnsom) Remove when versions and ciphers are implemented # TODO(johnsom) Remove when versions and ciphers are implemented
expect_prov_pool = copy.deepcopy(self.sample_data.provider_pool1) expect_prov_pool = copy.deepcopy(self.sample_data.provider_pool1)
delattr(expect_prov_pool, lib_constants.TLS_VERSIONS) delattr(expect_prov_pool, lib_constants.TLS_VERSIONS)
delattr(expect_prov_pool, lib_constants.TLS_CIPHERS)
self.assertEqual(expect_prov_pool, provider_pool) self.assertEqual(expect_prov_pool, provider_pool)
@mock.patch('octavia.api.drivers.utils._get_secret_data') @mock.patch('octavia.api.drivers.utils._get_secret_data')
@ -359,7 +355,6 @@ class TestUtils(base.TestCase):
# TODO(johnsom) Remove when versions and ciphers are implemented # TODO(johnsom) Remove when versions and ciphers are implemented
expect_prov_pool = copy.deepcopy(self.sample_data.provider_pool1) expect_prov_pool = copy.deepcopy(self.sample_data.provider_pool1)
delattr(expect_prov_pool, lib_constants.TLS_VERSIONS) delattr(expect_prov_pool, lib_constants.TLS_VERSIONS)
delattr(expect_prov_pool, lib_constants.TLS_CIPHERS)
self.assertEqual(expect_prov_pool, provider_pool) self.assertEqual(expect_prov_pool, provider_pool)
@mock.patch('octavia.api.drivers.utils._get_secret_data') @mock.patch('octavia.api.drivers.utils._get_secret_data')
@ -377,7 +372,6 @@ class TestUtils(base.TestCase):
expect_prov_pools = copy.deepcopy(self.sample_data.provider_pools) expect_prov_pools = copy.deepcopy(self.sample_data.provider_pools)
for prov_pool in expect_prov_pools: for prov_pool in expect_prov_pools:
delattr(prov_pool, lib_constants.TLS_VERSIONS) delattr(prov_pool, lib_constants.TLS_VERSIONS)
delattr(prov_pool, lib_constants.TLS_CIPHERS)
self.assertEqual(expect_prov_pools, provider_pools) self.assertEqual(expect_prov_pools, provider_pools)
@mock.patch('octavia.api.drivers.utils._get_secret_data') @mock.patch('octavia.api.drivers.utils._get_secret_data')
@ -396,7 +390,6 @@ class TestUtils(base.TestCase):
provider_pool_dict.pop('crl_container_ref') provider_pool_dict.pop('crl_container_ref')
# TODO(johnsom) Remove when versions and ciphers are implemented # TODO(johnsom) Remove when versions and ciphers are implemented
expect_prov.pop(lib_constants.TLS_VERSIONS) expect_prov.pop(lib_constants.TLS_VERSIONS)
expect_prov.pop(lib_constants.TLS_CIPHERS)
self.assertEqual(expect_prov, provider_pool_dict) self.assertEqual(expect_prov, provider_pool_dict)
@mock.patch('octavia.api.drivers.utils._get_secret_data') @mock.patch('octavia.api.drivers.utils._get_secret_data')
@ -430,7 +423,6 @@ class TestUtils(base.TestCase):
provider_pool_dict.pop('crl_container_ref') provider_pool_dict.pop('crl_container_ref')
# TODO(johnsom) Remove when versions and ciphers are implemented # TODO(johnsom) Remove when versions and ciphers are implemented
expect_prov.pop(lib_constants.TLS_VERSIONS) expect_prov.pop(lib_constants.TLS_VERSIONS)
expect_prov.pop(lib_constants.TLS_CIPHERS)
self.assertEqual(expect_prov, provider_pool_dict) self.assertEqual(expect_prov, provider_pool_dict)
def test_db_HM_to_provider_HM(self): def test_db_HM_to_provider_HM(self):

View File

@ -865,6 +865,42 @@ class TestHaproxyCfg(base.TestCase):
rendered_obj) rendered_obj)
def test_render_template_pool_cert(self): def test_render_template_pool_cert(self):
cert_file_path = os.path.join(self.jinja_cfg.base_crt_dir,
'sample_listener_id_1', 'fake path')
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 "
"{opts}\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 "
"{opts}\n\n").format(
maxconn=constants.HAPROXY_MAX_MAXCONN,
opts="ssl crt %s verify none sni ssl_fc_sni" % cert_file_path +
" ciphers " + constants.CIPHERS_OWASP_SUITE_B)
rendered_obj = self.jinja_cfg.render_loadbalancer_obj(
sample_configs_combined.sample_amphora_tuple(),
[sample_configs_combined.sample_listener_tuple(
pool_cert=True, tls_enabled=True,
backend_tls_ciphers=constants.CIPHERS_OWASP_SUITE_B)],
tls_certs={
'sample_pool_id_1':
{'client_cert': cert_file_path,
'ca_cert': None, 'crl': None}})
self.assertEqual(
sample_configs_combined.sample_base_expected_config(backend=be),
rendered_obj)
def test_render_template_pool_cert_no_ciphers(self):
cert_file_path = os.path.join(self.jinja_cfg.base_crt_dir, cert_file_path = os.path.join(self.jinja_cfg.base_crt_dir,
'sample_listener_id_1', 'fake path') 'sample_listener_id_1', 'fake path')
be = ("backend sample_pool_id_1:sample_listener_id_1\n" be = ("backend sample_pool_id_1:sample_listener_id_1\n"

View File

@ -600,7 +600,8 @@ def sample_listener_tuple(proto=None, monitor=True, alloc_default_pool=True,
tls_enabled=False, hm_host_http_check=False, tls_enabled=False, hm_host_http_check=False,
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):
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
@ -631,7 +632,8 @@ def sample_listener_tuple(proto=None, monitor=True, alloc_default_pool=True,
pool_cert=pool_cert, pool_ca_cert=pool_ca_cert, pool_cert=pool_cert, pool_ca_cert=pool_ca_cert,
pool_crl=pool_crl, tls_enabled=tls_enabled, pool_crl=pool_crl, tls_enabled=tls_enabled,
hm_host_http_check=hm_host_http_check, hm_host_http_check=hm_host_http_check,
listener_id='sample_listener_id_1'), listener_id='sample_listener_id_1',
tls_ciphers=backend_tls_ciphers),
sample_pool_tuple( sample_pool_tuple(
proto=be_proto, monitor=monitor, persistence=persistence, proto=be_proto, monitor=monitor, persistence=persistence,
persistence_type=persistence_type, persistence_type=persistence_type,
@ -640,7 +642,8 @@ def sample_listener_tuple(proto=None, monitor=True, alloc_default_pool=True,
pool_cert=pool_cert, pool_ca_cert=pool_ca_cert, pool_cert=pool_cert, pool_ca_cert=pool_ca_cert,
pool_crl=pool_crl, tls_enabled=tls_enabled, pool_crl=pool_crl, tls_enabled=tls_enabled,
hm_host_http_check=hm_host_http_check, hm_host_http_check=hm_host_http_check,
listener_id='sample_listener_id_1')] listener_id='sample_listener_id_1',
tls_ciphers=backend_tls_ciphers)]
l7policies = [ l7policies = [
sample_l7policy_tuple('sample_l7policy_id_1', sample_policy=1), sample_l7policy_tuple('sample_l7policy_id_1', sample_policy=1),
sample_l7policy_tuple('sample_l7policy_id_2', sample_policy=2), sample_l7policy_tuple('sample_l7policy_id_2', sample_policy=2),
@ -663,7 +666,8 @@ def sample_listener_tuple(proto=None, monitor=True, alloc_default_pool=True,
pool_cert=pool_cert, pool_ca_cert=pool_ca_cert, pool_cert=pool_cert, pool_ca_cert=pool_ca_cert,
pool_crl=pool_crl, tls_enabled=tls_enabled, pool_crl=pool_crl, tls_enabled=tls_enabled,
hm_host_http_check=hm_host_http_check, hm_host_http_check=hm_host_http_check,
listener_id='sample_listener_id_1')] listener_id='sample_listener_id_1',
tls_ciphers=backend_tls_ciphers)]
l7policies = [] l7policies = []
listener = in_listener( listener = in_listener(
id=id, id=id,
@ -769,14 +773,18 @@ def sample_pool_tuple(listener_id=None, proto=None, monitor=True,
has_http_reuse=True, pool_cert=False, pool_ca_cert=False, has_http_reuse=True, pool_cert=False, pool_ca_cert=False,
pool_crl=False, tls_enabled=False, pool_crl=False, tls_enabled=False,
hm_host_http_check=False, hm_host_http_check=False,
provisioning_status=constants.ACTIVE): provisioning_status=constants.ACTIVE,
tls_ciphers=constants.CIPHERS_OWASP_SUITE_B):
proto = 'HTTP' if proto is None else proto proto = 'HTTP' if proto is None else proto
if not tls_enabled:
tls_ciphers = None
monitor_proto = proto if monitor_proto is None else monitor_proto monitor_proto = proto if monitor_proto is None else monitor_proto
in_pool = collections.namedtuple( in_pool = collections.namedtuple(
'pool', 'id, protocol, lb_algorithm, members, health_monitor, ' 'pool', 'id, protocol, lb_algorithm, members, health_monitor, '
'session_persistence, enabled, operating_status, ' 'session_persistence, enabled, operating_status, '
'tls_certificate_id, ca_tls_certificate_id, ' 'tls_certificate_id, ca_tls_certificate_id, '
'crl_container_id, tls_enabled, provisioning_status, ' + 'crl_container_id, tls_enabled, tls_ciphers, '
'provisioning_status, ' +
constants.HTTP_REUSE) constants.HTTP_REUSE)
if (proto == constants.PROTOCOL_UDP and if (proto == constants.PROTOCOL_UDP and
persistence_type == constants.SESSION_PERSISTENCE_SOURCE_IP): persistence_type == constants.SESSION_PERSISTENCE_SOURCE_IP):
@ -820,6 +828,7 @@ def sample_pool_tuple(listener_id=None, proto=None, monitor=True,
ca_tls_certificate_id='pool_ca_1' if pool_ca_cert else None, ca_tls_certificate_id='pool_ca_1' if pool_ca_cert else None,
crl_container_id='pool_crl' if pool_crl else None, crl_container_id='pool_crl' if pool_crl else None,
tls_enabled=tls_enabled, tls_enabled=tls_enabled,
tls_ciphers=tls_ciphers,
provisioning_status=provisioning_status) provisioning_status=provisioning_status)

View File

@ -0,0 +1,7 @@
---
features:
- |
TLS-enabled pools can now be individually configured with an OpenSSL cipher string.
The default cipher for new pools can be specified with ``default_pools_ciphers``
in ``octavia.conf``. The built-in default is OWASP's "Suite B" recommendation. (https://cheatsheetseries.owasp.org/cheatsheets/TLS_Cipher_String_Cheat_Sheet.html)
Existing pools will be unaffected.