Pool support sni cert for backend re-encryption
Add 1 fields like Listener does, which is 'tls_container_ref', this field is introduced into Pool for storage the pool client certificate to the backend servers, when the traffic willing to bring a cert to the servers and check for tls connection. Story: 2003859 Task: 26685 Change-Id: I29b7c7116e6087c942179ed9efdead494ef277a3
This commit is contained in:
parent
f77d7d0220
commit
aa7ac7ab73
@ -1320,6 +1320,26 @@ timeout_tcp_inspect-optional:
|
||||
min_version: 2.1
|
||||
required: false
|
||||
type: integer
|
||||
tls_container_ref:
|
||||
description: |
|
||||
The reference to the `key manager service
|
||||
<https://docs.openstack.org/castellan/latest/>`__ secret containing a
|
||||
PKCS12 format certificate/key bundle for ``tls_enabled`` pools for
|
||||
TLS client authentication to the member servers.
|
||||
in: body
|
||||
min_version: 2.8
|
||||
required: true
|
||||
type: string
|
||||
tls_container_ref-optional:
|
||||
description: |
|
||||
The reference to the `key manager service
|
||||
<https://docs.openstack.org/castellan/latest/>`__ secret containing a
|
||||
PKCS12 format certificate/key bundle for ``tls_enabled`` pools for
|
||||
TLS client authentication to the member servers.
|
||||
in: body
|
||||
min_version: 2.8
|
||||
required: false
|
||||
type: string
|
||||
total_connections:
|
||||
description: |
|
||||
The total connections handled.
|
||||
|
@ -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"]}}' 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"}}' http://198.51.100.10:9876/v2/lbaas/pools
|
||||
|
@ -10,6 +10,7 @@
|
||||
},
|
||||
"listener_id": "023f2e34-7806-443b-bfae-16c324569a3d",
|
||||
"name": "super-pool",
|
||||
"tags": ["test_tag"]
|
||||
"tags": ["test_tag"],
|
||||
"tls_container_ref": "http://198.51.100.10:9311/v1/containers/4073846f-1d5e-42e1-a4cf-a7046419d0e6"
|
||||
}
|
||||
}
|
||||
|
@ -27,6 +27,7 @@
|
||||
"id": "4029d267-3983-4224-a3d0-afb3fe16a2cd",
|
||||
"operating_status": "ONLINE",
|
||||
"name": "super-pool",
|
||||
"tags": ["test_tag"]
|
||||
"tags": ["test_tag"],
|
||||
"tls_container_ref": "http://198.51.100.10:9311/v1/containers/4073846f-1d5e-42e1-a4cf-a7046419d0e6"
|
||||
}
|
||||
}
|
||||
|
@ -27,6 +27,7 @@
|
||||
"id": "4029d267-3983-4224-a3d0-afb3fe16a2cd",
|
||||
"operating_status": "ONLINE",
|
||||
"name": "super-pool",
|
||||
"tags": ["test_tag"]
|
||||
"tags": ["test_tag"],
|
||||
"tls_container_ref": "http://198.51.100.10:9311/v1/containers/4073846f-1d5e-42e1-a4cf-a7046419d0e6"
|
||||
}
|
||||
}
|
||||
|
@ -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"]}}' 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"}}' http://198.51.100.10:9876/v2/lbaas/pools/4029d267-3983-4224-a3d0-afb3fe16a2cd
|
||||
|
@ -6,6 +6,7 @@
|
||||
},
|
||||
"description": "Super Least Connections Pool",
|
||||
"name": "super-least-conn-pool",
|
||||
"tags": ["updated_tag"]
|
||||
"tags": ["updated_tag"],
|
||||
"tls_container_ref": "http://198.51.100.10:9311/v1/containers/c1cd501d-3cf9-4873-a11b-a74bebcde929"
|
||||
}
|
||||
}
|
||||
|
@ -27,6 +27,7 @@
|
||||
"id": "4029d267-3983-4224-a3d0-afb3fe16a2cd",
|
||||
"operating_status": "ONLINE",
|
||||
"name": "super-least-conn-pool",
|
||||
"tags": ["updated_tag"]
|
||||
"tags": ["updated_tag"],
|
||||
"tls_container_ref": "http://198.51.100.10:9311/v1/containers/c1cd501d-3cf9-4873-a11b-a74bebcde929"
|
||||
}
|
||||
}
|
||||
|
@ -33,7 +33,8 @@
|
||||
"id": "ddb2b28f-89e9-45d3-a329-a359c3e39e4a",
|
||||
"operating_status": "ONLINE",
|
||||
"name": "round_robin_pool",
|
||||
"tags": ["test_tag"]
|
||||
"tags": ["test_tag"],
|
||||
"tls_container_ref": "http://198.51.100.10:9311/v1/containers/4073846f-1d5e-42e1-a4cf-a7046419d0e6"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -61,6 +61,7 @@ Response Parameters
|
||||
- provisioning_status: provisioning_status
|
||||
- session_persistence: session_persistence
|
||||
- tags: tags
|
||||
- tls_container_ref: tls_container_ref
|
||||
- updated_at: updated_at
|
||||
|
||||
Response Example
|
||||
@ -169,6 +170,7 @@ Request
|
||||
- protocol: protocol-pools
|
||||
- session_persistence: session_persistence-optional
|
||||
- tags: tags-optional
|
||||
- tls_container_ref: tls_container_ref-optional
|
||||
|
||||
.. _session_persistence:
|
||||
|
||||
@ -246,6 +248,7 @@ Response Parameters
|
||||
- provisioning_status: provisioning_status
|
||||
- session_persistence: session_persistence
|
||||
- tags: tags
|
||||
- tls_container_ref: tls_container_ref
|
||||
- updated_at: updated_at
|
||||
|
||||
Response Example
|
||||
@ -313,6 +316,7 @@ Response Parameters
|
||||
- provisioning_status: provisioning_status
|
||||
- session_persistence: session_persistence
|
||||
- tags: tags
|
||||
- tls_container_ref: tls_container_ref
|
||||
- updated_at: updated_at
|
||||
|
||||
Response Example
|
||||
@ -361,6 +365,7 @@ Request
|
||||
- pool_id: path-pool-id
|
||||
- session_persistence: session_persistence-optional
|
||||
- tags: tags-optional
|
||||
- tls_container_ref: tls_container_ref-optional
|
||||
|
||||
Request Example
|
||||
---------------
|
||||
@ -395,6 +400,7 @@ Response Parameters
|
||||
- provisioning_status: provisioning_status
|
||||
- session_persistence: session_persistence
|
||||
- tags: tags
|
||||
- tls_container_ref: tls_container_ref
|
||||
- updated_at: updated_at
|
||||
|
||||
Response Example
|
||||
|
@ -676,6 +676,11 @@ contain the following:
|
||||
| | | {'type': 'APP_COOKIE', |
|
||||
| | | 'cookie_name': <cookie_name>} |
|
||||
+-----------------------+--------+------------------------------------------+
|
||||
| tls_container_data | dict | A `TLS container`_ dict. |
|
||||
+-----------------------+--------+------------------------------------------+
|
||||
| tls_container_ref | string | The reference to the secrets |
|
||||
| | | container. |
|
||||
+-----------------------+--------+------------------------------------------+
|
||||
|
||||
Delete
|
||||
^^^^^^
|
||||
@ -724,6 +729,11 @@ contain the following:
|
||||
| | | {'type': 'APP_COOKIE', |
|
||||
| | | 'cookie_name': <cookie_name>} |
|
||||
+-----------------------+--------+------------------------------------------+
|
||||
| tls_container_data | dict | A `TLS container`_ dict. |
|
||||
+-----------------------+--------+------------------------------------------+
|
||||
| tls_container_ref | string | The reference to the secrets |
|
||||
| | | container. |
|
||||
+-----------------------+--------+------------------------------------------+
|
||||
|
||||
The pool will be in the ``PENDING_UPDATE`` provisioning_status when it is
|
||||
passed to the driver. The driver will update the provisioning_status of the
|
||||
|
@ -14,6 +14,7 @@
|
||||
# under the License.
|
||||
import functools
|
||||
import hashlib
|
||||
import os
|
||||
import time
|
||||
import warnings
|
||||
|
||||
@ -120,6 +121,7 @@ class HaproxyAmphoraLoadBalancerDriver(
|
||||
listener, listener.client_ca_tls_certificate_id)
|
||||
crl_filename = self._process_secret(
|
||||
listener, listener.client_crl_container_id)
|
||||
pool_tls_certs = self._process_listener_pool_certs(listener)
|
||||
|
||||
# Generate HaProxy configuration from listener object
|
||||
config = self.jinja.build_config(
|
||||
@ -127,7 +129,8 @@ class HaproxyAmphoraLoadBalancerDriver(
|
||||
tls_cert=certs['tls_cert'],
|
||||
haproxy_versions=haproxy_versions,
|
||||
client_ca_filename=client_ca_filename,
|
||||
client_crl=crl_filename)
|
||||
client_crl=crl_filename,
|
||||
pool_tls_certs=pool_tls_certs)
|
||||
self.client.upload_config(amp, listener.id, config,
|
||||
timeout_dict=timeout_dict)
|
||||
self.client.reload_listener(amp, listener.id,
|
||||
@ -161,6 +164,7 @@ class HaproxyAmphoraLoadBalancerDriver(
|
||||
listener, listener.client_ca_tls_certificate_id)
|
||||
crl_filename = self._process_secret(
|
||||
listener, listener.client_crl_container_id)
|
||||
pool_tls_certs = self._process_listener_pool_certs(listener)
|
||||
|
||||
for amp in listener.load_balancer.amphorae:
|
||||
if amp.status != consts.DELETED:
|
||||
@ -173,7 +177,8 @@ class HaproxyAmphoraLoadBalancerDriver(
|
||||
tls_cert=certs['tls_cert'],
|
||||
haproxy_versions=haproxy_versions,
|
||||
client_ca_filename=client_ca_filename,
|
||||
client_crl=crl_filename)
|
||||
client_crl=crl_filename,
|
||||
pool_tls_certs=pool_tls_certs)
|
||||
self.client.upload_config(amp, listener.id, config)
|
||||
self.client.reload_listener(amp, listener.id)
|
||||
|
||||
@ -301,12 +306,51 @@ class HaproxyAmphoraLoadBalancerDriver(
|
||||
return None
|
||||
context = oslo_context.RequestContext(project_id=listener.project_id)
|
||||
secret = self.cert_manager.get_secret(context, secret_ref)
|
||||
md5 = hashlib.md5(secret.encode('utf-8')).hexdigest() # nosec
|
||||
id = hashlib.sha1(secret.encode('utf-8')).hexdigest() # nosec
|
||||
try:
|
||||
secret = secret.encode('utf-8')
|
||||
except AttributeError:
|
||||
pass
|
||||
md5 = hashlib.md5(secret).hexdigest() # nosec
|
||||
id = hashlib.sha1(secret).hexdigest() # nosec
|
||||
name = '{id}.pem'.format(id=id)
|
||||
self._apply(self._upload_cert, listener, None, secret, md5, name)
|
||||
return name
|
||||
|
||||
def _process_listener_pool_certs(self, listener):
|
||||
# {'POOL-ID': {
|
||||
# 'client_cert': client_full_filename,
|
||||
# 'ca_cert': ca_cert_full_filename,
|
||||
# 'crl': crl_full_filename}}
|
||||
pool_certs_dict = dict()
|
||||
for pool in listener.pools:
|
||||
if pool.id not in pool_certs_dict:
|
||||
pool_certs_dict[pool.id] = self._process_pool_certs(listener,
|
||||
pool)
|
||||
for l7policy in listener.l7policies:
|
||||
if (l7policy.redirect_pool and
|
||||
l7policy.redirect_pool.id not in pool_certs_dict):
|
||||
pool_certs_dict[l7policy.redirect_pool.id] = (
|
||||
self._process_pool_certs(listener, l7policy.redirect_pool))
|
||||
return pool_certs_dict
|
||||
|
||||
def _process_pool_certs(self, listener, pool):
|
||||
pool_cert_dict = dict()
|
||||
|
||||
# Handle the cleint cert(s) and key
|
||||
if pool.tls_certificate_id:
|
||||
data = cert_parser.load_certificates_data(self.cert_manager, pool)
|
||||
pem = cert_parser.build_pem(data)
|
||||
try:
|
||||
pem = pem.encode('utf-8')
|
||||
except AttributeError:
|
||||
pass
|
||||
md5 = hashlib.md5(pem).hexdigest() # nosec
|
||||
name = '{id}.pem'.format(id=data.id)
|
||||
self._apply(self._upload_cert, listener, None, pem, md5, name)
|
||||
pool_cert_dict['client_cert'] = os.path.join(
|
||||
CONF.haproxy_amphora.base_cert_dir, listener.id, name)
|
||||
return pool_cert_dict
|
||||
|
||||
def _upload_cert(self, amp, listener_id, pem, md5, name):
|
||||
try:
|
||||
if self.client.get_cert_md5sum(
|
||||
@ -315,8 +359,7 @@ class HaproxyAmphoraLoadBalancerDriver(
|
||||
except exc.NotFound:
|
||||
pass
|
||||
|
||||
self.client.upload_cert_pem(
|
||||
amp, listener_id, name, pem)
|
||||
self.client.upload_cert_pem(amp, listener_id, name, pem)
|
||||
|
||||
def update_amphora_agent_config(self, amphora, agent_config,
|
||||
timeout_dict=None):
|
||||
@ -488,24 +531,29 @@ class AmphoraAPIClient(object):
|
||||
|
||||
def upload_cert_pem(self, amp, listener_id, pem_filename, pem_file):
|
||||
r = self.put(
|
||||
amp,
|
||||
'listeners/{listener_id}/certificates/{filename}'.format(
|
||||
amp, 'listeners/{listener_id}/certificates/{filename}'.format(
|
||||
listener_id=listener_id, filename=pem_filename),
|
||||
data=pem_file)
|
||||
return exc.check_exception(r)
|
||||
|
||||
def update_cert_for_rotation(self, amp, pem_file):
|
||||
r = self.put(amp, 'certificate', data=pem_file)
|
||||
return exc.check_exception(r)
|
||||
|
||||
def get_cert_md5sum(self, amp, listener_id, pem_filename, ignore=tuple()):
|
||||
r = self.get(amp,
|
||||
'listeners/{listener_id}/certificates/{filename}'.format(
|
||||
listener_id=listener_id, filename=pem_filename))
|
||||
r = self.get(
|
||||
amp, 'listeners/{listener_id}/certificates/{filename}'.format(
|
||||
listener_id=listener_id, filename=pem_filename))
|
||||
if exc.check_exception(r, ignore):
|
||||
return r.json().get("md5sum")
|
||||
return None
|
||||
|
||||
def delete_cert_pem(self, amp, listener_id, pem_filename):
|
||||
r = self.delete(
|
||||
amp, 'listeners/{listener_id}/certificates/{filename}'.format(
|
||||
listener_id=listener_id, filename=pem_filename))
|
||||
return exc.check_exception(r, (404,))
|
||||
|
||||
def update_cert_for_rotation(self, amp, pem_file):
|
||||
r = self.put(amp, 'certificate', data=pem_file)
|
||||
return exc.check_exception(r)
|
||||
|
||||
def delete_listener(self, amp, listener_id):
|
||||
r = self.delete(
|
||||
amp, 'listeners/{listener_id}'.format(listener_id=listener_id))
|
||||
@ -529,13 +577,6 @@ class AmphoraAPIClient(object):
|
||||
return r.json()
|
||||
return None
|
||||
|
||||
def delete_cert_pem(self, amp, listener_id, pem_filename):
|
||||
r = self.delete(
|
||||
amp,
|
||||
'listeners/{listener_id}/certificates/{filename}'.format(
|
||||
listener_id=listener_id, filename=pem_filename))
|
||||
return exc.check_exception(r, (404,))
|
||||
|
||||
def plug_network(self, amp, port):
|
||||
r = self.post(amp, 'plug/network',
|
||||
json=port)
|
||||
|
@ -135,7 +135,9 @@ class AmphoraProviderDriver(driver_base.ProviderDriver):
|
||||
if 'admin_state_up' in pool_dict:
|
||||
pool_dict['enabled'] = pool_dict.pop('admin_state_up')
|
||||
pool_id = pool_dict.pop('pool_id')
|
||||
|
||||
if 'tls_container_ref' in pool_dict:
|
||||
pool_dict['tls_container_id'] = pool_dict.pop('tls_container_ref')
|
||||
pool_dict.pop('tls_container_data', None)
|
||||
payload = {consts.POOL_ID: pool_id,
|
||||
consts.POOL_UPDATES: pool_dict}
|
||||
self.client.cast({}, 'update_pool', **payload)
|
||||
|
@ -170,7 +170,8 @@ class Pool(BaseDataModel):
|
||||
healthmonitor=Unset, lb_algorithm=Unset,
|
||||
loadbalancer_id=Unset, members=Unset, name=Unset,
|
||||
pool_id=Unset, listener_id=Unset, protocol=Unset,
|
||||
session_persistence=Unset):
|
||||
session_persistence=Unset, tls_container_ref=Unset,
|
||||
tls_container_data=Unset):
|
||||
|
||||
self.admin_state_up = admin_state_up
|
||||
self.description = description
|
||||
@ -183,6 +184,8 @@ class Pool(BaseDataModel):
|
||||
self.listener_id = listener_id
|
||||
self.protocol = protocol
|
||||
self.session_persistence = session_persistence
|
||||
self.tls_container_ref = tls_container_ref
|
||||
self.tls_container_data = tls_container_data
|
||||
|
||||
|
||||
class Member(BaseDataModel):
|
||||
|
@ -281,6 +281,25 @@ def db_pool_to_provider_pool(db_pool):
|
||||
def pool_dict_to_provider_dict(pool_dict):
|
||||
new_pool_dict = _base_to_provider_dict(pool_dict)
|
||||
new_pool_dict['pool_id'] = new_pool_dict.pop('id')
|
||||
|
||||
# Pull the certs out of the certificate manager to pass to the provider
|
||||
if 'tls_certificate_id' in new_pool_dict:
|
||||
new_pool_dict['tls_container_ref'] = new_pool_dict.pop(
|
||||
'tls_certificate_id')
|
||||
|
||||
pool_obj = data_models.Pool(**pool_dict)
|
||||
if pool_obj.tls_certificate_id:
|
||||
cert_manager = stevedore_driver.DriverManager(
|
||||
namespace='octavia.cert_manager',
|
||||
name=CONF.certificates.cert_manager,
|
||||
invoke_on_load=True,
|
||||
).driver
|
||||
cert_dict = cert_parser.load_certificates_data(cert_manager,
|
||||
pool_obj)
|
||||
if 'tls_cert' in cert_dict and cert_dict['tls_cert']:
|
||||
new_pool_dict['tls_container_data'] = (
|
||||
cert_dict['tls_cert'].to_dict())
|
||||
|
||||
# Remove the DB back references
|
||||
if ('session_persistence' in new_pool_dict and
|
||||
new_pool_dict['session_persistence']):
|
||||
|
@ -18,6 +18,7 @@ from oslo_db import exception as odb_exceptions
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import excutils
|
||||
import pecan
|
||||
from stevedore import driver as stevedore_driver
|
||||
from wsme import types as wtypes
|
||||
from wsmeext import pecan as wsme_pecan
|
||||
|
||||
@ -46,6 +47,11 @@ class PoolsController(base.BaseController):
|
||||
|
||||
def __init__(self):
|
||||
super(PoolsController, self).__init__()
|
||||
self.cert_manager = stevedore_driver.DriverManager(
|
||||
namespace='octavia.cert_manager',
|
||||
name=CONF.certificates.cert_manager,
|
||||
invoke_on_load=True,
|
||||
).driver
|
||||
|
||||
@wsme_pecan.wsexpose(pool_types.PoolRootResponse, wtypes.text,
|
||||
[wtypes.text], ignore_extra_args=True)
|
||||
@ -98,6 +104,19 @@ class PoolsController(base.BaseController):
|
||||
raise exceptions.ImmutableObject(resource=_('Load Balancer'),
|
||||
id=lb_id)
|
||||
|
||||
def _validate_tls_refs(self, tls_refs):
|
||||
context = pecan.request.context.get('octavia_context')
|
||||
bad_refs = []
|
||||
for ref in tls_refs:
|
||||
try:
|
||||
self.cert_manager.set_acls(context, ref)
|
||||
self.cert_manager.get_cert(context, ref, check_only=True)
|
||||
except Exception:
|
||||
bad_refs.append(ref)
|
||||
|
||||
if bad_refs:
|
||||
raise exceptions.CertificateRetrievalException(ref=bad_refs)
|
||||
|
||||
def _validate_create_pool(self, lock_session, pool_dict, listener_id=None):
|
||||
"""Validate creating pool on load balancer.
|
||||
|
||||
@ -105,6 +124,9 @@ class PoolsController(base.BaseController):
|
||||
provisioning status.
|
||||
"""
|
||||
try:
|
||||
tls_certificate_id = pool_dict.get('tls_certificate_id', None)
|
||||
tls_refs = [tls_certificate_id] if tls_certificate_id else []
|
||||
self._validate_tls_refs(tls_refs)
|
||||
return self.repositories.create_pool_on_load_balancer(
|
||||
lock_session, pool_dict,
|
||||
listener_id=listener_id)
|
||||
@ -327,6 +349,9 @@ class PoolsController(base.BaseController):
|
||||
sp_dict = pool.session_persistence.to_dict(render_unsets=False)
|
||||
validate.check_session_persistence(sp_dict)
|
||||
|
||||
if pool.tls_container_ref:
|
||||
self._validate_tls_refs([pool.tls_container_ref])
|
||||
|
||||
# Load the driver early as it also provides validation
|
||||
driver = driver_factory.get_driver(provider)
|
||||
|
||||
|
@ -52,7 +52,8 @@ class SessionPersistencePUT(types.BaseType):
|
||||
class BasePoolType(types.BaseType):
|
||||
_type_to_model_map = {'admin_state_up': 'enabled',
|
||||
'healthmonitor': 'health_monitor',
|
||||
'healthmonitor_id': 'health_monitor.id'}
|
||||
'healthmonitor_id': 'health_monitor.id',
|
||||
'tls_container_ref': 'tls_certificate_id'}
|
||||
|
||||
_child_map = {'health_monitor': {'id': 'healthmonitor_id'}}
|
||||
|
||||
@ -76,6 +77,7 @@ class PoolResponse(BasePoolType):
|
||||
healthmonitor_id = wtypes.wsattr(wtypes.UuidType())
|
||||
members = wtypes.wsattr([types.IdOnlyType])
|
||||
tags = wtypes.wsattr(wtypes.ArrayType(wtypes.StringType()))
|
||||
tls_container_ref = wtypes.wsattr(wtypes.StringType())
|
||||
|
||||
@classmethod
|
||||
def from_data_model(cls, data_model, children=False):
|
||||
@ -147,6 +149,8 @@ class PoolPOST(BasePoolType):
|
||||
healthmonitor = wtypes.wsattr(health_monitor.HealthMonitorSingleCreate)
|
||||
members = wtypes.wsattr([member.MemberSingleCreate])
|
||||
tags = wtypes.wsattr(wtypes.ArrayType(wtypes.StringType(max_length=255)))
|
||||
tls_container_ref = wtypes.wsattr(
|
||||
wtypes.StringType(max_length=255))
|
||||
|
||||
|
||||
class PoolRootPOST(types.BaseType):
|
||||
@ -162,6 +166,7 @@ class PoolPUT(BasePoolType):
|
||||
wtypes.Enum(str, *constants.SUPPORTED_LB_ALGORITHMS))
|
||||
session_persistence = wtypes.wsattr(SessionPersistencePUT)
|
||||
tags = wtypes.wsattr(wtypes.ArrayType(wtypes.StringType(max_length=255)))
|
||||
tls_container_ref = wtypes.wsattr(wtypes.StringType(max_length=255))
|
||||
|
||||
|
||||
class PoolRootPut(types.BaseType):
|
||||
@ -180,6 +185,7 @@ class PoolSingleCreate(BasePoolType):
|
||||
healthmonitor = wtypes.wsattr(health_monitor.HealthMonitorSingleCreate)
|
||||
members = wtypes.wsattr([member.MemberSingleCreate])
|
||||
tags = wtypes.wsattr(wtypes.ArrayType(wtypes.StringType(max_length=255)))
|
||||
tls_container_ref = wtypes.wsattr(wtypes.StringType(max_length=255))
|
||||
|
||||
|
||||
class PoolStatusResponse(BasePoolType):
|
||||
|
@ -263,7 +263,7 @@ class Pool(BaseDataModel):
|
||||
session_persistence=None, load_balancer_id=None,
|
||||
load_balancer=None, listeners=None, l7policies=None,
|
||||
created_at=None, updated_at=None, provisioning_status=None,
|
||||
tags=None):
|
||||
tags=None, tls_certificate_id=None):
|
||||
self.id = id
|
||||
self.project_id = project_id
|
||||
self.name = name
|
||||
@ -283,6 +283,7 @@ class Pool(BaseDataModel):
|
||||
self.updated_at = updated_at
|
||||
self.provisioning_status = provisioning_status
|
||||
self.tags = tags
|
||||
self.tls_certificate_id = tls_certificate_id
|
||||
|
||||
def update(self, update_dict):
|
||||
for key, value in update_dict.items():
|
||||
|
@ -83,7 +83,8 @@ class JinjaTemplater(object):
|
||||
|
||||
def build_config(self, host_amphora, listener, tls_cert,
|
||||
haproxy_versions, socket_path=None,
|
||||
client_ca_filename=None, client_crl=None):
|
||||
client_ca_filename=None, client_crl=None,
|
||||
pool_tls_certs=None):
|
||||
"""Convert a logical configuration to the HAProxy version
|
||||
|
||||
:param host_amphora: The Amphora this configuration is hosted on
|
||||
@ -105,7 +106,8 @@ class JinjaTemplater(object):
|
||||
return self.render_loadbalancer_obj(
|
||||
host_amphora, listener, tls_cert=tls_cert, socket_path=socket_path,
|
||||
feature_compatibility=feature_compatibility,
|
||||
client_ca_filename=client_ca_filename, client_crl=client_crl)
|
||||
client_ca_filename=client_ca_filename, client_crl=client_crl,
|
||||
pool_tls_certs=pool_tls_certs)
|
||||
|
||||
def _get_template(self):
|
||||
"""Returns the specified Jinja configuration template."""
|
||||
@ -124,7 +126,8 @@ class JinjaTemplater(object):
|
||||
def render_loadbalancer_obj(self, host_amphora, listener,
|
||||
tls_cert=None, socket_path=None,
|
||||
feature_compatibility=None,
|
||||
client_ca_filename=None, client_crl=None):
|
||||
client_ca_filename=None, client_crl=None,
|
||||
pool_tls_certs=None):
|
||||
"""Renders a templated configuration from a load balancer object
|
||||
|
||||
:param host_amphora: The Amphora this configuration is hosted on
|
||||
@ -142,7 +145,8 @@ class JinjaTemplater(object):
|
||||
tls_cert,
|
||||
feature_compatibility,
|
||||
client_ca_filename=client_ca_filename,
|
||||
client_crl=client_crl)
|
||||
client_crl=client_crl,
|
||||
pool_tls_certs=pool_tls_certs)
|
||||
if not socket_path:
|
||||
socket_path = '%s/%s.sock' % (self.base_amp_path, listener.id)
|
||||
return self._get_template().render(
|
||||
@ -155,14 +159,16 @@ class JinjaTemplater(object):
|
||||
|
||||
def _transform_loadbalancer(self, host_amphora, loadbalancer, listener,
|
||||
tls_cert, feature_compatibility,
|
||||
client_ca_filename=None, client_crl=None):
|
||||
client_ca_filename=None, client_crl=None,
|
||||
pool_tls_certs=None):
|
||||
"""Transforms a load balancer into an object that will
|
||||
|
||||
be processed by the templating system
|
||||
"""
|
||||
t_listener = self._transform_listener(
|
||||
listener, tls_cert, feature_compatibility,
|
||||
client_ca_filename=client_ca_filename, client_crl=client_crl)
|
||||
client_ca_filename=client_ca_filename, client_crl=client_crl,
|
||||
pool_tls_certs=pool_tls_certs)
|
||||
ret_value = {
|
||||
'id': loadbalancer.id,
|
||||
'vip_address': loadbalancer.vip.ip_address,
|
||||
@ -202,7 +208,8 @@ class JinjaTemplater(object):
|
||||
}
|
||||
|
||||
def _transform_listener(self, listener, tls_cert, feature_compatibility,
|
||||
client_ca_filename=None, client_crl=None):
|
||||
client_ca_filename=None, client_crl=None,
|
||||
pool_tls_certs=None):
|
||||
"""Transforms a listener into an object that will
|
||||
|
||||
be processed by the templating system
|
||||
@ -251,17 +258,28 @@ class JinjaTemplater(object):
|
||||
os.path.join(self.base_crt_dir, listener.id, client_crl))
|
||||
|
||||
if listener.default_pool:
|
||||
kwargs = {}
|
||||
if pool_tls_certs and pool_tls_certs.get(listener.default_pool.id):
|
||||
kwargs = {'pool_tls_certs': pool_tls_certs.get(
|
||||
listener.default_pool.id)}
|
||||
ret_value['default_pool'] = self._transform_pool(
|
||||
listener.default_pool, feature_compatibility)
|
||||
pools = [self._transform_pool(x, feature_compatibility)
|
||||
for x in listener.pools]
|
||||
listener.default_pool, feature_compatibility, **kwargs)
|
||||
pools = []
|
||||
for x in listener.pools:
|
||||
kwargs = {}
|
||||
if pool_tls_certs and pool_tls_certs.get(x.id):
|
||||
kwargs = {'pool_tls_certs': pool_tls_certs.get(x.id)}
|
||||
pools.append(self._transform_pool(
|
||||
x, feature_compatibility, **kwargs))
|
||||
ret_value['pools'] = pools
|
||||
l7policies = [self._transform_l7policy(x, feature_compatibility)
|
||||
l7policies = [self._transform_l7policy(
|
||||
x, feature_compatibility, pool_tls_certs)
|
||||
for x in listener.l7policies]
|
||||
ret_value['l7policies'] = l7policies
|
||||
return ret_value
|
||||
|
||||
def _transform_pool(self, pool, feature_compatibility):
|
||||
def _transform_pool(self, pool, feature_compatibility,
|
||||
pool_tls_certs=None):
|
||||
"""Transforms a pool into an object that will
|
||||
|
||||
be processed by the templating system
|
||||
@ -289,6 +307,10 @@ class JinjaTemplater(object):
|
||||
ret_value[
|
||||
'session_persistence'] = self._transform_session_persistence(
|
||||
pool.session_persistence, feature_compatibility)
|
||||
if (pool.tls_certificate_id and pool_tls_certs and
|
||||
pool_tls_certs.get('client_cert')):
|
||||
ret_value['client_cert'] = pool_tls_certs.get('client_cert')
|
||||
|
||||
return ret_value
|
||||
|
||||
@staticmethod
|
||||
@ -343,7 +365,8 @@ class JinjaTemplater(object):
|
||||
'enabled': monitor.enabled,
|
||||
}
|
||||
|
||||
def _transform_l7policy(self, l7policy, feature_compatibility):
|
||||
def _transform_l7policy(self, l7policy, feature_compatibility,
|
||||
pool_tls_certs=None):
|
||||
"""Transforms an L7 policy into an object that will
|
||||
|
||||
be processed by the templating system
|
||||
@ -356,8 +379,13 @@ class JinjaTemplater(object):
|
||||
'enabled': l7policy.enabled
|
||||
}
|
||||
if l7policy.redirect_pool:
|
||||
kwargs = {}
|
||||
if pool_tls_certs and pool_tls_certs.get(
|
||||
l7policy.redirect_pool.id):
|
||||
kwargs = {'pool_tls_certs':
|
||||
pool_tls_certs.get(l7policy.redirect_pool.id)}
|
||||
ret_value['redirect_pool'] = self._transform_pool(
|
||||
l7policy.redirect_pool, feature_compatibility)
|
||||
l7policy.redirect_pool, feature_compatibility, **kwargs)
|
||||
else:
|
||||
ret_value['redirect_pool'] = None
|
||||
l7rules = [self._transform_l7rule(x, feature_compatibility)
|
||||
|
@ -210,10 +210,19 @@ frontend {{ listener.id }}
|
||||
{% else %}
|
||||
{% set member_enabled_opt = " disabled" %}
|
||||
{% endif %}
|
||||
{{ "server %s %s:%d weight %s%s%s%s%s%s"|e|format(
|
||||
{% if pool.client_cert %}
|
||||
{% set def_opt_prefix = " ssl" %}
|
||||
{% set def_crt_opt = " crt %s"|format(pool.client_cert) %}
|
||||
{% set def_verify_opt = " verify none" %}
|
||||
{% else %}
|
||||
{% set def_opt_prefix = "" %}
|
||||
{% set def_crt_opt = "" %}
|
||||
{% set def_verify_opt = "" %}
|
||||
{% endif %}
|
||||
{{ "server %s %s:%d weight %s%s%s%s%s%s%s%s%s"|e|format(
|
||||
member.id, member.address, member.protocol_port, member.weight,
|
||||
hm_opt, persistence_opt, proxy_protocol_opt, member_backup_opt,
|
||||
member_enabled_opt)|trim() }}
|
||||
member_enabled_opt, def_opt_prefix, def_crt_opt, def_verify_opt)|trim() }}
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
|
@ -336,23 +336,23 @@ def build_pem(tls_container):
|
||||
return b'\n'.join(pem) + b'\n'
|
||||
|
||||
|
||||
def load_certificates_data(cert_mngr, listener, context=None):
|
||||
"""Load TLS certificate data from the listener.
|
||||
def load_certificates_data(cert_mngr, obj, context=None):
|
||||
"""Load TLS certificate data from the listener/pool.
|
||||
|
||||
return TLS_CERT and SNI_CERTS
|
||||
"""
|
||||
tls_cert = None
|
||||
sni_certs = []
|
||||
if not context:
|
||||
context = oslo_context.RequestContext(project_id=listener.project_id)
|
||||
context = oslo_context.RequestContext(project_id=obj.project_id)
|
||||
|
||||
if listener.tls_certificate_id:
|
||||
if obj.tls_certificate_id:
|
||||
tls_cert = _map_cert_tls_container(
|
||||
cert_mngr.get_cert(context,
|
||||
listener.tls_certificate_id,
|
||||
obj.tls_certificate_id,
|
||||
check_only=True))
|
||||
if listener.sni_containers:
|
||||
for sni_cont in listener.sni_containers:
|
||||
if hasattr(obj, 'sni_containers') and obj.sni_containers:
|
||||
for sni_cont in obj.sni_containers:
|
||||
cert_container = _map_cert_tls_container(
|
||||
cert_mngr.get_cert(context,
|
||||
sni_cont.tls_container_id,
|
||||
|
@ -0,0 +1,35 @@
|
||||
# Copyright 2018 Huawei
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
"""Extend pool for support backend re-encryption
|
||||
|
||||
Revision ID: a1f689aecc1d
|
||||
Revises: 1afc932f1ca2
|
||||
Create Date: 2018-10-23 20:47:52.405865
|
||||
|
||||
"""
|
||||
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'a1f689aecc1d'
|
||||
down_revision = '1afc932f1ca2'
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.add_column(u'pool', sa.Column(u'tls_certificate_id', sa.String(255),
|
||||
nullable=True))
|
@ -328,6 +328,7 @@ class Pool(base_models.BASE, base_models.IdMixin, base_models.ProjectMixin,
|
||||
cascade='all,delete-orphan',
|
||||
primaryjoin='and_(foreign(Tags.resource_id)==Pool.id)'
|
||||
)
|
||||
tls_certificate_id = sa.Column(sa.String(255), nullable=True)
|
||||
|
||||
# This property should be a unique list of any listeners that reference
|
||||
# this pool as its default_pool and any listeners referenced by enabled
|
||||
@ -544,7 +545,6 @@ class SNI(base_models.BASE):
|
||||
__table_args__ = (
|
||||
sa.PrimaryKeyConstraint('listener_id', 'tls_container_id'),
|
||||
)
|
||||
|
||||
listener_id = sa.Column(
|
||||
sa.String(36),
|
||||
sa.ForeignKey("listener.id", name="fk_sni_listener_id"),
|
||||
|
@ -858,6 +858,43 @@ class TestPool(base.BaseAPITest):
|
||||
pool_prov_status=constants.PENDING_CREATE,
|
||||
pool_op_status=constants.OFFLINE)
|
||||
|
||||
@mock.patch('octavia.common.tls_utils.cert_parser.load_certificates_data')
|
||||
def test_create_with_tls_container_ref(self, mock_cert_data):
|
||||
tls_container_ref = uuidutils.generate_uuid()
|
||||
pool_cert = data_models.TLSContainer(certificate='pool cert')
|
||||
mock_cert_data.return_value = {'tls_cert': pool_cert,
|
||||
'sni_certs': [],
|
||||
'client_ca_cert': None}
|
||||
api_pool = self.create_pool(
|
||||
self.lb_id,
|
||||
constants.PROTOCOL_HTTP,
|
||||
constants.LB_ALGORITHM_ROUND_ROBIN,
|
||||
listener_id=self.listener_id,
|
||||
tls_container_ref=tls_container_ref).get(self.root_tag)
|
||||
self.assert_correct_status(
|
||||
lb_id=self.lb_id, listener_id=self.listener_id,
|
||||
pool_id=api_pool.get('id'),
|
||||
lb_prov_status=constants.PENDING_UPDATE,
|
||||
listener_prov_status=constants.PENDING_UPDATE,
|
||||
pool_prov_status=constants.PENDING_CREATE,
|
||||
pool_op_status=constants.OFFLINE)
|
||||
self.set_lb_status(self.lb_id)
|
||||
self.assertEqual(tls_container_ref, api_pool.get('tls_container_ref'))
|
||||
self.assert_correct_status(
|
||||
lb_id=self.lb_id, listener_id=self.listener_id,
|
||||
pool_id=api_pool.get('id'))
|
||||
|
||||
def test_create_with_bad_tls_container_ref(self):
|
||||
tls_container_ref = uuidutils.generate_uuid()
|
||||
self.cert_manager_mock().get_cert.side_effect = [Exception(
|
||||
"bad secret")]
|
||||
api_pool = self.create_pool(
|
||||
self.lb_id, constants.PROTOCOL_HTTP,
|
||||
constants.LB_ALGORITHM_ROUND_ROBIN,
|
||||
listener_id=self.listener_id,
|
||||
tls_container_ref=tls_container_ref, status=400)
|
||||
self.assertIn(tls_container_ref, api_pool['faultstring'])
|
||||
|
||||
def test_negative_create_udp_case(self):
|
||||
# Error create pool with udp protocol but non-udp-type
|
||||
sp = {"type": constants.SESSION_PERSISTENCE_HTTP_COOKIE,
|
||||
@ -1205,6 +1242,39 @@ class TestPool(base.BaseAPITest):
|
||||
self.assert_correct_status(
|
||||
lb_id=self.udp_lb_id, listener_id=self.udp_listener_id)
|
||||
|
||||
@mock.patch(
|
||||
'octavia.common.tls_utils.cert_parser.load_certificates_data')
|
||||
def test_update_with_tls_container_ref(self, mock_cert_data):
|
||||
tls_container_ref = uuidutils.generate_uuid()
|
||||
api_pool = self.create_pool(
|
||||
self.lb_id,
|
||||
constants.PROTOCOL_HTTP,
|
||||
constants.LB_ALGORITHM_ROUND_ROBIN,
|
||||
listener_id=self.listener_id).get(self.root_tag)
|
||||
self.set_lb_status(lb_id=self.lb_id)
|
||||
new_pool = {'tls_container_ref': tls_container_ref}
|
||||
pool_cert = data_models.TLSContainer(certificate='pool cert')
|
||||
mock_cert_data.return_value = {'tls_cert': pool_cert,
|
||||
'sni_certs': [],
|
||||
'client_ca_cert': None}
|
||||
self.put(self.POOL_PATH.format(pool_id=api_pool.get('id')),
|
||||
self._build_body(new_pool))
|
||||
self.assert_correct_status(
|
||||
lb_id=self.lb_id, listener_id=self.listener_id,
|
||||
pool_id=api_pool.get('id'),
|
||||
lb_prov_status=constants.PENDING_UPDATE,
|
||||
listener_prov_status=constants.PENDING_UPDATE,
|
||||
pool_prov_status=constants.PENDING_UPDATE)
|
||||
self.set_lb_status(self.lb_id)
|
||||
response = self.get(self.POOL_PATH.format(
|
||||
pool_id=api_pool.get('id'))).json.get(self.root_tag)
|
||||
self.assertEqual(tls_container_ref, response.get('tls_container_ref'))
|
||||
self.assertIsNotNone(response.get('created_at'))
|
||||
self.assertIsNotNone(response.get('updated_at'))
|
||||
self.assert_correct_status(
|
||||
lb_id=self.lb_id, listener_id=self.listener_id,
|
||||
pool_id=response.get('id'))
|
||||
|
||||
def test_bad_update(self):
|
||||
api_pool = self.create_pool(
|
||||
self.lb_id,
|
||||
@ -1256,6 +1326,22 @@ class TestPool(base.BaseAPITest):
|
||||
self.assert_correct_status(
|
||||
lb_id=self.udp_lb_id, listener_id=self.udp_listener_id)
|
||||
|
||||
def test_update_with_bad_tls_container_ref(self):
|
||||
api_pool = self.create_pool(
|
||||
self.lb_id,
|
||||
constants.PROTOCOL_HTTP,
|
||||
constants.LB_ALGORITHM_ROUND_ROBIN,
|
||||
listener_id=self.listener_id).get(self.root_tag)
|
||||
self.set_lb_status(lb_id=self.lb_id)
|
||||
tls_container_ref = uuidutils.generate_uuid()
|
||||
new_pool = {'tls_container_ref': tls_container_ref}
|
||||
|
||||
self.cert_manager_mock().get_cert.side_effect = [Exception(
|
||||
"bad secret")]
|
||||
resp = self.put(self.POOL_PATH.format(pool_id=api_pool.get('id')),
|
||||
self._build_body(new_pool), status=400).json
|
||||
self.assertIn(tls_container_ref, resp['faultstring'])
|
||||
|
||||
def test_delete(self):
|
||||
api_pool = self.create_pool(
|
||||
self.lb_id,
|
||||
|
@ -179,7 +179,8 @@ class AllRepositoriesTest(base.OctaviaDBTestBase):
|
||||
'project_id': uuidutils.generate_uuid(),
|
||||
'id': uuidutils.generate_uuid(),
|
||||
'provisioning_status': constants.ACTIVE,
|
||||
'tags': ['test_tag']}
|
||||
'tags': ['test_tag'],
|
||||
'tls_certificate_id': uuidutils.generate_uuid()}
|
||||
pool_dm = self.repos.create_pool_on_load_balancer(
|
||||
self.session, pool, listener_id=self.listener.id)
|
||||
pool_dm_dict = pool_dm.to_dict()
|
||||
@ -205,7 +206,8 @@ class AllRepositoriesTest(base.OctaviaDBTestBase):
|
||||
'project_id': uuidutils.generate_uuid(),
|
||||
'id': uuidutils.generate_uuid(),
|
||||
'provisioning_status': constants.ACTIVE,
|
||||
'tags': ['test_tag']}
|
||||
'tags': ['test_tag'],
|
||||
'tls_certificate_id': uuidutils.generate_uuid()}
|
||||
sp = {'type': constants.SESSION_PERSISTENCE_HTTP_COOKIE,
|
||||
'cookie_name': 'cookie_monster',
|
||||
'pool_id': pool['id'],
|
||||
@ -261,6 +263,7 @@ class AllRepositoriesTest(base.OctaviaDBTestBase):
|
||||
del pool_dm_dict['created_at']
|
||||
del pool_dm_dict['updated_at']
|
||||
pool.update(update_pool)
|
||||
pool['tls_certificate_id'] = None
|
||||
self.assertEqual(pool, pool_dm_dict)
|
||||
self.assertIsNone(new_pool_dm.session_persistence)
|
||||
|
||||
@ -272,7 +275,8 @@ class AllRepositoriesTest(base.OctaviaDBTestBase):
|
||||
'project_id': uuidutils.generate_uuid(),
|
||||
'id': uuidutils.generate_uuid(),
|
||||
'provisioning_status': constants.ACTIVE,
|
||||
'tags': ['test_tag']}
|
||||
'tags': ['test_tag'],
|
||||
'tls_certificate_id': uuidutils.generate_uuid()}
|
||||
sp = {'type': constants.SESSION_PERSISTENCE_HTTP_COOKIE,
|
||||
'cookie_name': 'cookie_monster',
|
||||
'pool_id': pool['id'],
|
||||
@ -364,6 +368,33 @@ class AllRepositoriesTest(base.OctaviaDBTestBase):
|
||||
self.session, pool_dm.id, update_pool)
|
||||
self.assertIsNone(new_pool_dm.session_persistence)
|
||||
|
||||
def test_update_pool_with_cert(self):
|
||||
pool = {'protocol': constants.PROTOCOL_HTTP, 'name': 'pool1',
|
||||
'description': 'desc1',
|
||||
'lb_algorithm': constants.LB_ALGORITHM_ROUND_ROBIN,
|
||||
'enabled': True, 'operating_status': constants.ONLINE,
|
||||
'project_id': uuidutils.generate_uuid(),
|
||||
'id': uuidutils.generate_uuid(),
|
||||
'provisioning_status': constants.ACTIVE}
|
||||
pool_dm = self.repos.create_pool_on_load_balancer(
|
||||
self.session, pool, listener_id=self.listener.id)
|
||||
update_pool = {'tls_certificate_id': uuidutils.generate_uuid()}
|
||||
new_pool_dm = self.repos.update_pool_and_sp(
|
||||
self.session, pool_dm.id, update_pool)
|
||||
pool_dm_dict = new_pool_dm.to_dict()
|
||||
del pool_dm_dict['members']
|
||||
del pool_dm_dict['health_monitor']
|
||||
del pool_dm_dict['session_persistence']
|
||||
del pool_dm_dict['listeners']
|
||||
del pool_dm_dict['load_balancer']
|
||||
del pool_dm_dict['load_balancer_id']
|
||||
del pool_dm_dict['l7policies']
|
||||
del pool_dm_dict['created_at']
|
||||
del pool_dm_dict['updated_at']
|
||||
del pool_dm_dict['tags']
|
||||
pool.update(update_pool)
|
||||
self.assertEqual(pool, pool_dm_dict)
|
||||
|
||||
def test_create_load_balancer_tree(self):
|
||||
project_id = uuidutils.generate_uuid()
|
||||
member = {'project_id': project_id, 'ip_address': '11.0.0.1',
|
||||
|
@ -73,6 +73,8 @@ class TestHaproxyAmphoraLoadBalancerDriverTest(base.TestCase):
|
||||
persistence_timeout=33,
|
||||
persistence_granularity='255.255.0.0',
|
||||
monitor_proto=constants.HEALTH_MONITOR_UDP_CONNECT)
|
||||
self.pool_has_cert = sample_configs.sample_pool_tuple(
|
||||
pool_cert=True, full_store=True)
|
||||
self.amp = self.sl.load_balancer.amphorae[0]
|
||||
self.sv = sample_configs.sample_vip_tuple()
|
||||
self.lb = self.sl.load_balancer
|
||||
@ -152,11 +154,9 @@ class TestHaproxyAmphoraLoadBalancerDriverTest(base.TestCase):
|
||||
sconts = []
|
||||
for sni_container in self.sl.sni_containers:
|
||||
sconts.append(sni_container.tls_container)
|
||||
mock_load_crt.return_value = {
|
||||
'tls_cert': self.sl.default_tls_container,
|
||||
'sni_certs': sconts,
|
||||
'client_ca_cert': self.sl.client_ca_tls_certificate
|
||||
}
|
||||
mock_load_crt.side_effect = [{
|
||||
'tls_cert': self.sl.default_tls_container, 'sni_certs': sconts},
|
||||
{'tls_cert': None, 'sni_certs': []}]
|
||||
self.driver.client.get_cert_md5sum.side_effect = [
|
||||
exc.NotFound, 'Fake_MD5', 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
|
||||
'CA_CERT_MD5']
|
||||
@ -243,8 +243,7 @@ class TestHaproxyAmphoraLoadBalancerDriverTest(base.TestCase):
|
||||
sconts.append(sni_container.tls_container)
|
||||
mock_load_crt.return_value = {
|
||||
'tls_cert': self.sl.default_tls_container,
|
||||
'sni_certs': sconts,
|
||||
'client_ca_cert': None
|
||||
'sni_certs': sconts
|
||||
}
|
||||
self.driver.client.get_cert_md5sum.side_effect = [
|
||||
exc.NotFound, 'Fake_MD5', 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa']
|
||||
@ -298,12 +297,12 @@ class TestHaproxyAmphoraLoadBalancerDriverTest(base.TestCase):
|
||||
sample_listener = sample_configs.sample_listener_tuple(
|
||||
tls=True, sni=True, client_ca_cert=True)
|
||||
fake_context = 'fake context'
|
||||
fake_secret = 'fake cert'
|
||||
fake_secret = b'fake cert'
|
||||
mock_oslo.return_value = fake_context
|
||||
self.driver.cert_manager.get_secret.reset_mock()
|
||||
self.driver.cert_manager.get_secret.return_value = fake_secret
|
||||
ref_md5 = hashlib.md5(fake_secret.encode('utf-8')).hexdigest() # nosec
|
||||
ref_id = hashlib.sha1(fake_secret.encode('utf-8')).hexdigest() # nosec
|
||||
ref_md5 = hashlib.md5(fake_secret).hexdigest() # nosec
|
||||
ref_id = hashlib.sha1(fake_secret).hexdigest() # nosec
|
||||
ref_name = '{id}.pem'.format(id=ref_id)
|
||||
|
||||
result = self.driver._process_secret(
|
||||
@ -318,6 +317,60 @@ class TestHaproxyAmphoraLoadBalancerDriverTest(base.TestCase):
|
||||
ref_md5, ref_name)
|
||||
self.assertEqual(ref_name, result)
|
||||
|
||||
@mock.patch('octavia.amphorae.drivers.haproxy.rest_api_driver.'
|
||||
'HaproxyAmphoraLoadBalancerDriver._process_pool_certs')
|
||||
def test__process_listener_pool_certs(self, mock_pool_cert):
|
||||
sample_listener = sample_configs.sample_listener_tuple(l7=True)
|
||||
|
||||
ref_pool_cert_1 = {'client_cert': '/some/fake/cert-1.pem'}
|
||||
ref_pool_cert_2 = {'client_cert': '/some/fake/cert-2.pem'}
|
||||
|
||||
mock_pool_cert.side_effect = [ref_pool_cert_1, ref_pool_cert_2]
|
||||
|
||||
ref_cert_dict = {'sample_pool_id_1': ref_pool_cert_1,
|
||||
'sample_pool_id_2': ref_pool_cert_2}
|
||||
|
||||
result = self.driver._process_listener_pool_certs(sample_listener)
|
||||
|
||||
pool_certs_calls = [
|
||||
mock.call(sample_listener, sample_listener.default_pool),
|
||||
mock.call(sample_listener, sample_listener.pools[1])
|
||||
]
|
||||
|
||||
mock_pool_cert.assert_has_calls(pool_certs_calls, any_order=True)
|
||||
|
||||
self.assertEqual(ref_cert_dict, result)
|
||||
|
||||
@mock.patch('octavia.amphorae.drivers.haproxy.rest_api_driver.'
|
||||
'HaproxyAmphoraLoadBalancerDriver._apply')
|
||||
@mock.patch('octavia.common.tls_utils.cert_parser.build_pem')
|
||||
@mock.patch('octavia.common.tls_utils.cert_parser.load_certificates_data')
|
||||
def test__process_pool_certs(self, mock_load_certs, mock_build_pem,
|
||||
mock_apply):
|
||||
fake_cert_dir = '/fake/cert/dir'
|
||||
conf = oslo_fixture.Config(cfg.CONF)
|
||||
conf.config(group="haproxy_amphora", base_cert_dir=fake_cert_dir)
|
||||
sample_listener = sample_configs.sample_listener_tuple(pool_cert=True)
|
||||
cert_data_mock = mock.MagicMock()
|
||||
cert_data_mock.id = uuidutils.generate_uuid()
|
||||
mock_load_certs.return_value = cert_data_mock
|
||||
fake_pem = b'fake pem'
|
||||
mock_build_pem.return_value = fake_pem
|
||||
ref_md5 = hashlib.md5(fake_pem).hexdigest() # nosec
|
||||
ref_name = '{id}.pem'.format(id=cert_data_mock.id)
|
||||
ref_path = '{cert_dir}/{list_id}/{name}'.format(
|
||||
cert_dir=fake_cert_dir, list_id=sample_listener.id, name=ref_name)
|
||||
ref_result = {'client_cert': ref_path}
|
||||
|
||||
result = self.driver._process_pool_certs(sample_listener,
|
||||
sample_listener.default_pool)
|
||||
|
||||
mock_build_pem.assert_called_once_with(cert_data_mock)
|
||||
mock_apply.assert_called_once_with(
|
||||
self.driver._upload_cert, sample_listener, None, fake_pem,
|
||||
ref_md5, ref_name)
|
||||
self.assertEqual(ref_result, result)
|
||||
|
||||
def test_stop(self):
|
||||
self.driver.client.stop_listener.__name__ = 'stop_listener'
|
||||
# Execute driver method
|
||||
@ -340,6 +393,7 @@ class TestHaproxyAmphoraLoadBalancerDriverTest(base.TestCase):
|
||||
listener.id = uuidutils.generate_uuid()
|
||||
listener.load_balancer.amphorae = [amp1, amp2]
|
||||
listener.protocol = 'listener_protocol'
|
||||
del listener.listeners
|
||||
self.driver.client.start_listener.__name__ = 'start_listener'
|
||||
# Execute driver method
|
||||
self.driver.start(listener, self.sv)
|
||||
|
@ -39,6 +39,7 @@ class SampleDriverDataModels(object):
|
||||
self.sni_container_ref_2 = uuidutils.generate_uuid()
|
||||
self.client_ca_tls_certificate_ref = uuidutils.generate_uuid()
|
||||
self.client_crl_container_ref = uuidutils.generate_uuid()
|
||||
self.pool_sni_container_ref = uuidutils.generate_uuid()
|
||||
|
||||
self.pool1_id = uuidutils.generate_uuid()
|
||||
self.pool2_id = uuidutils.generate_uuid()
|
||||
@ -205,7 +206,9 @@ class SampleDriverDataModels(object):
|
||||
'health_monitor': self.test_hm1_dict,
|
||||
'session_persistence': {'type': 'SOURCE'},
|
||||
'listeners': [],
|
||||
'l7policies': []}
|
||||
'l7policies': [],
|
||||
'tls_certificate_id':
|
||||
self.pool_sni_container_ref}
|
||||
|
||||
self.test_pool1_dict.update(self._common_test_dict)
|
||||
|
||||
@ -214,6 +217,7 @@ class SampleDriverDataModels(object):
|
||||
self.test_pool2_dict['name'] = 'pool2'
|
||||
self.test_pool2_dict['description'] = 'Pool 2'
|
||||
self.test_pool2_dict['members'] = self.test_pool2_members_dict
|
||||
del self.test_pool2_dict['tls_certificate_id']
|
||||
|
||||
self.test_pools = [self.test_pool1_dict, self.test_pool2_dict]
|
||||
|
||||
@ -225,6 +229,7 @@ class SampleDriverDataModels(object):
|
||||
self.db_pool2.members = self.db_pool2_members
|
||||
|
||||
self.test_db_pools = [self.db_pool1, self.db_pool2]
|
||||
pool_cert = data_models.TLSContainer(certificate='pool cert')
|
||||
|
||||
self.provider_pool1_dict = {
|
||||
'admin_state_up': True,
|
||||
@ -236,7 +241,9 @@ class SampleDriverDataModels(object):
|
||||
'name': 'pool1',
|
||||
'pool_id': self.pool1_id,
|
||||
'protocol': 'avian',
|
||||
'session_persistence': {'type': 'SOURCE'}}
|
||||
'session_persistence': {'type': 'SOURCE'},
|
||||
'tls_container_ref': self.pool_sni_container_ref,
|
||||
'tls_container_data': pool_cert.to_dict()}
|
||||
|
||||
self.provider_pool2_dict = copy.deepcopy(self.provider_pool1_dict)
|
||||
self.provider_pool2_dict['pool_id'] = self.pool2_id
|
||||
@ -244,6 +251,8 @@ class SampleDriverDataModels(object):
|
||||
self.provider_pool2_dict['description'] = 'Pool 2'
|
||||
self.provider_pool2_dict['members'] = self.provider_pool2_members_dict
|
||||
self.provider_pool2_dict['healthmonitor'] = self.provider_hm2_dict
|
||||
self.provider_pool2_dict['tls_container_ref'] = None
|
||||
del self.provider_pool2_dict['tls_container_data']
|
||||
|
||||
self.provider_pool1 = driver_dm.Pool(**self.provider_pool1_dict)
|
||||
self.provider_pool1.members = self.provider_pool1_members
|
||||
|
@ -91,8 +91,12 @@ class TestUtils(base.TestCase):
|
||||
cert2 = data_models.TLSContainer(certificate='cert 2')
|
||||
cert3 = data_models.TLSContainer(certificate='cert 3')
|
||||
mock_secret.side_effect = ['ca cert', 'X509 CRL FILE']
|
||||
mock_load_cert.return_value = {'tls_cert': cert1,
|
||||
'sni_certs': [cert2, cert3]}
|
||||
listener_certs = {'tls_cert': cert1, 'sni_certs': [cert2, cert3]}
|
||||
pool_cert = data_models.TLSContainer(certificate='pool cert')
|
||||
pool_certs = {'tls_cert': pool_cert, 'sni_certs': []}
|
||||
mock_load_cert.side_effect = [pool_certs, listener_certs,
|
||||
listener_certs, listener_certs,
|
||||
listener_certs]
|
||||
test_lb_dict = {'name': 'lb1',
|
||||
'project_id': self.sample_data.project_id,
|
||||
'vip_subnet_id': self.sample_data.subnet_id,
|
||||
@ -180,8 +184,10 @@ class TestUtils(base.TestCase):
|
||||
cert1 = data_models.TLSContainer(certificate='cert 1')
|
||||