Add client_ca_tls_container_ref to listener API
This patch add 'client_ca_tls_container_ref' into listener API for front client authentication. Story: 2002165 Task: 20018 Co-Authored-By: Michael Johnson <johnsomor@gmail.com> Change-Id: I8a96d6fdfe53a16d1abcfd09bc6afedd6c490de2changes/67/612267/20
parent
6e0bed1c54
commit
0cc546a7c7
|
@ -246,6 +246,26 @@ cert-expiration:
|
|||
in: body
|
||||
required: true
|
||||
type: string
|
||||
client_ca_tls_container_ref:
|
||||
description: |
|
||||
The ref of the `key manager service
|
||||
<https://docs.openstack.org/castellan/latest/>`__ secret containing a
|
||||
PEM format client CA certificate bundle for ``TERMINATED_TLS``
|
||||
listeners.
|
||||
in: body
|
||||
min_version: 2.8
|
||||
required: true
|
||||
type: string
|
||||
client_ca_tls_container_ref-optional:
|
||||
description: |
|
||||
The ref of the `key manager service
|
||||
<https://docs.openstack.org/castellan/latest/>`__ secret containing a
|
||||
PEM format client CA certificate bundle for ``TERMINATED_TLS``
|
||||
listeners.
|
||||
in: body
|
||||
min_version: 2.8
|
||||
required: false
|
||||
type: string
|
||||
compute-flavor:
|
||||
description: |
|
||||
The ID of the compute flavor used for the amphora.
|
||||
|
|
|
@ -1 +1 @@
|
|||
curl -X POST -H "Content-Type: application/json" -H "X-Auth-Token: <token>" -d '{"listener": {"protocol": "TERMINATED_HTTPS", "description": "A great TLS listener", "admin_state_up": true, "connection_limit": 200, "protocol_port": "443", "loadbalancer_id": "607226db-27ef-4d41-ae89-f2a800e9c2db", "name": "great_tls_listener", "insert_headers": {"X-Forwarded-For": "true", "X-Forwarded-Port": "true"}, "default_tls_container_ref": "http://198.51.100.10:9311/v1/containers/a570068c-d295-4780-91d4-3046a325db51", "sni_container_refs": ["http://198.51.100.10:9311/v1/containers/a570068c-d295-4780-91d4-3046a325db51", "http://198.51.100.10:9311/v1/containers/aaebb31e-7761-4826-8cb4-2b829caca3ee"], "timeout_client_data": 50000, "timeout_member_connect": 5000, "timeout_member_data": 50000, "timeout_tcp_inspect": 0, "tags": ["test_tag"]}}' http://198.51.100.10:9876/v2/lbaas/listeners
|
||||
curl -X POST -H "Content-Type: application/json" -H "X-Auth-Token: <token>" -d '{"listener": {"protocol": "TERMINATED_HTTPS", "description": "A great TLS listener", "admin_state_up": true, "connection_limit": 200, "protocol_port": "443", "loadbalancer_id": "607226db-27ef-4d41-ae89-f2a800e9c2db", "name": "great_tls_listener", "insert_headers": {"X-Forwarded-For": "true", "X-Forwarded-Port": "true"}, "default_tls_container_ref": "http://198.51.100.10:9311/v1/containers/a570068c-d295-4780-91d4-3046a325db51", "sni_container_refs": ["http://198.51.100.10:9311/v1/containers/a570068c-d295-4780-91d4-3046a325db51", "http://198.51.100.10:9311/v1/containers/aaebb31e-7761-4826-8cb4-2b829caca3ee"], "timeout_client_data": 50000, "timeout_member_connect": 5000, "timeout_member_data": 50000, "timeout_tcp_inspect": 0, "tags": ["test_tag"], "client_ca_tls_container_ref": "http://198.51.100.10:9311/v1/containers/35649991-49f3-4625-81ce-2465fe8932e5"}}' http://198.51.100.10:9876/v2/lbaas/listeners
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
"timeout_member_connect": 5000,
|
||||
"timeout_member_data": 50000,
|
||||
"timeout_tcp_inspect": 0,
|
||||
"tags": ["test_tag"]
|
||||
"tags": ["test_tag"],
|
||||
"client_ca_tls_container_ref": "http://198.51.100.10:9311/v1/containers/35649991-49f3-4625-81ce-2465fe8932e5"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
"timeout_member_connect": 5000,
|
||||
"timeout_member_data": 50000,
|
||||
"timeout_tcp_inspect": 0,
|
||||
"tags": ["test_tag"]
|
||||
"tags": ["test_tag"],
|
||||
"client_ca_tls_container_ref": "http://198.51.100.10:9311/v1/containers/35649991-49f3-4625-81ce-2465fe8932e5"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
"timeout_member_connect": 5000,
|
||||
"timeout_member_data": 50000,
|
||||
"timeout_tcp_inspect": 0,
|
||||
"tags": ["test_tag"]
|
||||
"tags": ["test_tag"],
|
||||
"client_ca_tls_container_ref": "http://198.51.100.10:9311/v1/containers/35649991-49f3-4625-81ce-2465fe8932e5"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
curl -X PUT -H "Content-Type: application/json" -H "X-Auth-Token: <token>" -d '{"listener": {"description": "An updated great TLS listener", "admin_state_up": true, "connection_limit": 200, "name": "great_updated_tls_listener", "insert_headers": {"X-Forwarded-For": "false", "X-Forwarded-Port": "true"}, "default_tls_container_ref": "http://198.51.100.10:9311/v1/containers/a570068c-d295-4780-91d4-3046a325db51", "sni_container_refs": ["http://198.51.100.10:9311/v1/containers/a570068c-d295-4780-91d4-3046a325db51", "http://198.51.100.10:9311/v1/containers/aaebb31e-7761-4826-8cb4-2b829caca3ee"], "timeout_client_data": 100000, "timeout_member_connect": 1000, "timeout_member_data": 100000, "timeout_tcp_inspect": 5, "tags": ["updated_tag"]}}' http://198.51.100.10:9876/v2/lbaas/listeners/023f2e34-7806-443b-bfae-16c324569a3d
|
||||
curl -X PUT -H "Content-Type: application/json" -H "X-Auth-Token: <token>" -d '{"listener": {"description": "An updated great TLS listener", "admin_state_up": true, "connection_limit": 200, "name": "great_updated_tls_listener", "insert_headers": {"X-Forwarded-For": "false", "X-Forwarded-Port": "true"}, "default_tls_container_ref": "http://198.51.100.10:9311/v1/containers/a570068c-d295-4780-91d4-3046a325db51", "sni_container_refs": ["http://198.51.100.10:9311/v1/containers/a570068c-d295-4780-91d4-3046a325db51", "http://198.51.100.10:9311/v1/containers/aaebb31e-7761-4826-8cb4-2b829caca3ee"], "timeout_client_data": 100000, "timeout_member_connect": 1000, "timeout_member_data": 100000, "timeout_tcp_inspect": 5, "tags": ["updated_tag"], "client_ca_tls_container_ref": null}}' http://198.51.100.10:9876/v2/lbaas/listeners/023f2e34-7806-443b-bfae-16c324569a3d
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
"timeout_member_connect": 1000,
|
||||
"timeout_member_data": 100000,
|
||||
"timeout_tcp_inspect": 5,
|
||||
"tags": ["updated_tag"]
|
||||
"tags": ["updated_tag"],
|
||||
"client_ca_tls_container_ref": null
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
"timeout_member_connect": 1000,
|
||||
"timeout_member_data": 100000,
|
||||
"timeout_tcp_inspect": 5,
|
||||
"tags": ["updated_tag"]
|
||||
"tags": ["updated_tag"],
|
||||
"client_ca_tls_container_ref": null
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,7 +37,8 @@
|
|||
"timeout_member_connect": 5000,
|
||||
"timeout_member_data": 50000,
|
||||
"timeout_tcp_inspect": 0,
|
||||
"tags": ["test_tag"]
|
||||
"tags": ["test_tag"],
|
||||
"client_ca_tls_container_ref": "http://198.51.100.10:9311/v1/containers/35649991-49f3-4625-81ce-2465fe8932e5"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -46,6 +46,7 @@ Response Parameters
|
|||
.. rest_parameters:: ../parameters.yaml
|
||||
|
||||
- admin_state_up: admin_state_up
|
||||
- client_ca_tls_container_ref: client_ca_tls_container_ref
|
||||
- connection_limit: connection_limit
|
||||
- created_at: created_at
|
||||
- default_pool_id: default_pool_id
|
||||
|
@ -136,6 +137,7 @@ Request
|
|||
.. rest_parameters:: ../parameters.yaml
|
||||
|
||||
- admin_state_up: admin_state_up-default-optional
|
||||
- client_ca_tls_container_ref: client_ca_tls_container_ref-optional
|
||||
- connection_limit: connection_limit-optional
|
||||
- default_pool: pool-optional
|
||||
- default_pool_id: default_pool_id-optional
|
||||
|
@ -204,6 +206,7 @@ Response Parameters
|
|||
.. rest_parameters:: ../parameters.yaml
|
||||
|
||||
- admin_state_up: admin_state_up
|
||||
- client_ca_tls_container_ref: client_ca_tls_container_ref
|
||||
- connection_limit: connection_limit
|
||||
- created_at: created_at
|
||||
- default_pool_id: default_pool_id
|
||||
|
@ -278,6 +281,7 @@ Response Parameters
|
|||
.. rest_parameters:: ../parameters.yaml
|
||||
|
||||
- admin_state_up: admin_state_up
|
||||
- client_ca_tls_container_ref: client_ca_tls_container_ref
|
||||
- connection_limit: connection_limit
|
||||
- created_at: created_at
|
||||
- default_pool_id: default_pool_id
|
||||
|
@ -342,6 +346,7 @@ Request
|
|||
.. rest_parameters:: ../parameters.yaml
|
||||
|
||||
- admin_state_up: admin_state_up-default-optional
|
||||
- client_ca_tls_container_ref: client_ca_tls_container_ref-optional
|
||||
- connection_limit: connection_limit-optional
|
||||
- default_pool_id: default_pool_id-optional
|
||||
- default_tls_container_ref: default_tls_container_ref-optional
|
||||
|
@ -374,6 +379,7 @@ Response Parameters
|
|||
.. rest_parameters:: ../parameters.yaml
|
||||
|
||||
- admin_state_up: admin_state_up
|
||||
- client_ca_tls_container_ref: client_ca_tls_container_ref
|
||||
- connection_limit: connection_limit
|
||||
- created_at: created_at
|
||||
- default_pool_id: default_pool_id
|
||||
|
|
|
@ -358,68 +358,73 @@ PEM format.
|
|||
As of the writing of this specification the create listener object may
|
||||
contain the following:
|
||||
|
||||
+----------------------------+--------+-------------------------------------+
|
||||
| Name | Type | Description |
|
||||
+============================+========+=====================================+
|
||||
| admin_state_up | bool | Admin state: True if up, False if |
|
||||
| | | down. |
|
||||
+----------------------------+--------+-------------------------------------+
|
||||
| connection_limit | int | The max number of connections |
|
||||
| | | permitted for this listener. Default|
|
||||
| | | is -1, which is infinite |
|
||||
| | | connections. |
|
||||
+----------------------------+--------+-------------------------------------+
|
||||
| default_pool | object | A `Pool object`_. |
|
||||
+----------------------------+--------+-------------------------------------+
|
||||
| default_pool_id | string | The ID of the pool used by the |
|
||||
| | | listener if no L7 policies match. |
|
||||
+----------------------------+--------+-------------------------------------+
|
||||
| default_tls_container_data | dict | A `TLS container`_ dict. |
|
||||
+----------------------------+--------+-------------------------------------+
|
||||
| default_tls_container_refs | string | The reference to the secrets |
|
||||
| | | container. |
|
||||
+----------------------------+--------+-------------------------------------+
|
||||
| description | string | A human-readable description for the|
|
||||
| | | listener. |
|
||||
+----------------------------+--------+-------------------------------------+
|
||||
| insert_headers | dict | A dictionary of optional headers to |
|
||||
| | | insert into the request before it is|
|
||||
| | | sent to the backend member. See |
|
||||
| | | `Supported HTTP Header Insertions`_.|
|
||||
| | | Keys and values are specified as |
|
||||
| | | strings. |
|
||||
+----------------------------+--------+-------------------------------------+
|
||||
| l7policies | list | A list of `L7policy objects`_. |
|
||||
+----------------------------+--------+-------------------------------------+
|
||||
| listener_id | string | ID of listener to create. |
|
||||
+----------------------------+--------+-------------------------------------+
|
||||
| loadbalancer_id | string | ID of load balancer. |
|
||||
+----------------------------+--------+-------------------------------------+
|
||||
| name | string | Human-readable name of the listener.|
|
||||
+----------------------------+--------+-------------------------------------+
|
||||
| protocol | string | Protocol type: One of HTTP, HTTPS, |
|
||||
| | | TCP, or TERMINATED_HTTPS. |
|
||||
+----------------------------+--------+-------------------------------------+
|
||||
| protocol_port | int | Protocol port number. |
|
||||
+----------------------------+--------+-------------------------------------+
|
||||
| sni_container_data | list | A list of `TLS container`_ dict. |
|
||||
+----------------------------+--------+-------------------------------------+
|
||||
| sni_container_refs | list | A list of references to the SNI |
|
||||
| | | secrets containers. |
|
||||
+----------------------------+--------+-------------------------------------+
|
||||
| timeout_client_data | int | Frontend client inactivity timeout |
|
||||
| | | in milliseconds. |
|
||||
+----------------------------+--------+-------------------------------------+
|
||||
| timeout_member_connect | int | Backend member connection timeout in|
|
||||
| | | milliseconds. |
|
||||
+----------------------------+--------+-------------------------------------+
|
||||
| timeout_member_data | int | Backend member inactivity timeout in|
|
||||
| | | milliseconds. |
|
||||
+----------------------------+--------+-------------------------------------+
|
||||
| timeout_tcp_inspect | int | Time, in milliseconds, to wait for |
|
||||
| | | additional TCP packets for content |
|
||||
| | | inspection. |
|
||||
+----------------------------+--------+-------------------------------------+
|
||||
+------------------------------+--------+-------------------------------------+
|
||||
| Name | Type | Description |
|
||||
+==============================+========+=====================================+
|
||||
| admin_state_up | bool | Admin state: True if up, False if |
|
||||
| | | down. |
|
||||
+------------------------------+--------+-------------------------------------+
|
||||
|client_ca_tls_container_data | string | A PEM encoded certificate. |
|
||||
+------------------------------+--------+-------------------------------------+
|
||||
|client_ca_tls_container_ref | string | The reference to the secrets |
|
||||
| | | container. |
|
||||
+------------------------------+--------+-------------------------------------+
|
||||
| connection_limit | int | The max number of connections |
|
||||
| | | permitted for this listener. Default|
|
||||
| | | is -1, which is infinite |
|
||||
| | | connections. |
|
||||
+------------------------------+--------+-------------------------------------+
|
||||
| default_pool | object | A `Pool object`_. |
|
||||
+------------------------------+--------+-------------------------------------+
|
||||
| default_pool_id | string | The ID of the pool used by the |
|
||||
| | | listener if no L7 policies match. |
|
||||
+------------------------------+--------+-------------------------------------+
|
||||
| default_tls_container_data | dict | A `TLS container`_ dict. |
|
||||
+------------------------------+--------+-------------------------------------+
|
||||
| default_tls_container_refs | string | The reference to the secrets |
|
||||
| | | container. |
|
||||
+------------------------------+--------+-------------------------------------+
|
||||
| description | string | A human-readable description for the|
|
||||
| | | listener. |
|
||||
+------------------------------+--------+-------------------------------------+
|
||||
| insert_headers | dict | A dictionary of optional headers to |
|
||||
| | | insert into the request before it is|
|
||||
| | | sent to the backend member. See |
|
||||
| | | `Supported HTTP Header Insertions`_.|
|
||||
| | | Keys and values are specified as |
|
||||
| | | strings. |
|
||||
+------------------------------+--------+-------------------------------------+
|
||||
| l7policies | list | A list of `L7policy objects`_. |
|
||||
+------------------------------+--------+-------------------------------------+
|
||||
| listener_id | string | ID of listener to create. |
|
||||
+------------------------------+--------+-------------------------------------+
|
||||
| loadbalancer_id | string | ID of load balancer. |
|
||||
+------------------------------+--------+-------------------------------------+
|
||||
| name | string | Human-readable name of the listener.|
|
||||
+------------------------------+--------+-------------------------------------+
|
||||
| protocol | string | Protocol type: One of HTTP, HTTPS, |
|
||||
| | | TCP, or TERMINATED_HTTPS. |
|
||||
+------------------------------+--------+-------------------------------------+
|
||||
| protocol_port | int | Protocol port number. |
|
||||
+------------------------------+--------+-------------------------------------+
|
||||
| sni_container_data | list | A list of `TLS container`_ dict. |
|
||||
+------------------------------+--------+-------------------------------------+
|
||||
| sni_container_refs | list | A list of references to the SNI |
|
||||
| | | secrets containers. |
|
||||
+------------------------------+--------+-------------------------------------+
|
||||
| timeout_client_data | int | Frontend client inactivity timeout |
|
||||
| | | in milliseconds. |
|
||||
+------------------------------+--------+-------------------------------------+
|
||||
| timeout_member_connect | int | Backend member connection timeout in|
|
||||
| | | milliseconds. |
|
||||
+------------------------------+--------+-------------------------------------+
|
||||
| timeout_member_data | int | Backend member inactivity timeout in|
|
||||
| | | milliseconds. |
|
||||
+------------------------------+--------+-------------------------------------+
|
||||
| timeout_tcp_inspect | int | Time, in milliseconds, to wait for |
|
||||
| | | additional TCP packets for content |
|
||||
| | | inspection. |
|
||||
+------------------------------+--------+-------------------------------------+
|
||||
|
||||
.. _TLS container:
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ import hashlib
|
|||
import time
|
||||
import warnings
|
||||
|
||||
from oslo_context import context as oslo_context
|
||||
from oslo_log import log as logging
|
||||
import requests
|
||||
import simplejson
|
||||
|
@ -115,11 +116,15 @@ class HaproxyAmphoraLoadBalancerDriver(
|
|||
timeout_dict=timeout_dict)
|
||||
else:
|
||||
certs = self._process_tls_certificates(listener)
|
||||
client_ca_filename = self._process_secret(
|
||||
listener, listener.client_ca_tls_certificate_id)
|
||||
|
||||
# Generate HaProxy configuration from listener object
|
||||
config = self.jinja.build_config(
|
||||
host_amphora=amp, listener=listener,
|
||||
tls_cert=certs['tls_cert'],
|
||||
haproxy_versions=haproxy_versions)
|
||||
haproxy_versions=haproxy_versions,
|
||||
client_ca_filename=client_ca_filename)
|
||||
self.client.upload_config(amp, listener.id, config,
|
||||
timeout_dict=timeout_dict)
|
||||
self.client.reload_listener(amp, listener.id,
|
||||
|
@ -149,6 +154,8 @@ class HaproxyAmphoraLoadBalancerDriver(
|
|||
|
||||
# Process listener certificate info
|
||||
certs = self._process_tls_certificates(listener)
|
||||
client_ca_filename = self._process_secret(
|
||||
listener, listener.client_ca_tls_certificate_id)
|
||||
|
||||
for amp in listener.load_balancer.amphorae:
|
||||
if amp.status != consts.DELETED:
|
||||
|
@ -159,7 +166,8 @@ class HaproxyAmphoraLoadBalancerDriver(
|
|||
config = self.jinja.build_config(
|
||||
host_amphora=amp, listener=listener,
|
||||
tls_cert=certs['tls_cert'],
|
||||
haproxy_versions=haproxy_versions)
|
||||
haproxy_versions=haproxy_versions,
|
||||
client_ca_filename=client_ca_filename)
|
||||
self.client.upload_config(amp, listener.id, config)
|
||||
self.client.reload_listener(amp, listener.id)
|
||||
|
||||
|
@ -278,6 +286,21 @@ class HaproxyAmphoraLoadBalancerDriver(
|
|||
|
||||
return {'tls_cert': tls_cert, 'sni_certs': sni_certs}
|
||||
|
||||
def _process_secret(self, listener, secret_ref):
|
||||
"""Get the secret from the cert manager and upload it to the amp.
|
||||
|
||||
:returns: The filename of the secret in the amp.
|
||||
"""
|
||||
if not secret_ref:
|
||||
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
|
||||
name = '{id}.pem'.format(id=id)
|
||||
self._apply(self._upload_cert, listener, None, secret, md5, name)
|
||||
return name
|
||||
|
||||
def _upload_cert(self, amp, listener_id, pem, md5, name):
|
||||
try:
|
||||
if self.client.get_cert_md5sum(
|
||||
|
|
|
@ -133,7 +133,8 @@ class Listener(BaseDataModel):
|
|||
protocol_port=Unset, sni_container_refs=Unset,
|
||||
sni_container_data=Unset, timeout_client_data=Unset,
|
||||
timeout_member_connect=Unset, timeout_member_data=Unset,
|
||||
timeout_tcp_inspect=Unset):
|
||||
timeout_tcp_inspect=Unset, client_ca_tls_container_ref=Unset,
|
||||
client_ca_tls_container_data=Unset):
|
||||
|
||||
self.admin_state_up = admin_state_up
|
||||
self.connection_limit = connection_limit
|
||||
|
@ -155,6 +156,8 @@ class Listener(BaseDataModel):
|
|||
self.timeout_member_connect = timeout_member_connect
|
||||
self.timeout_member_data = timeout_member_data
|
||||
self.timeout_tcp_inspect = timeout_tcp_inspect
|
||||
self.client_ca_tls_container_ref = client_ca_tls_container_ref
|
||||
self.client_ca_tls_container_data = client_ca_tls_container_data
|
||||
|
||||
|
||||
class Pool(BaseDataModel):
|
||||
|
|
|
@ -17,6 +17,7 @@ import copy
|
|||
import six
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_context import context as oslo_context
|
||||
from oslo_log import log as logging
|
||||
from stevedore import driver as stevedore_driver
|
||||
|
||||
|
@ -154,6 +155,15 @@ def db_listener_to_provider_listener(db_listener):
|
|||
return provider_listener
|
||||
|
||||
|
||||
def _get_secret_data(cert_manager, listener, secret_ref):
|
||||
"""Get the secret from the certificate manager and upload it to the amp.
|
||||
|
||||
:returns: The secret data.
|
||||
"""
|
||||
context = oslo_context.RequestContext(project_id=listener.project_id)
|
||||
return cert_manager.get_secret(context, secret_ref)
|
||||
|
||||
|
||||
def listener_dict_to_provider_dict(listener_dict):
|
||||
new_listener_dict = _base_to_provider_dict(listener_dict)
|
||||
new_listener_dict['listener_id'] = new_listener_dict.pop('id')
|
||||
|
@ -171,8 +181,13 @@ def listener_dict_to_provider_dict(listener_dict):
|
|||
if 'sni_container_refs' in listener_dict:
|
||||
listener_dict['sni_containers'] = listener_dict.pop(
|
||||
'sni_container_refs')
|
||||
if 'client_ca_tls_certificate_id' in new_listener_dict:
|
||||
new_listener_dict['client_ca_tls_container_ref'] = (
|
||||
new_listener_dict.pop('client_ca_tls_certificate_id'))
|
||||
|
||||
listener_obj = data_models.Listener(**listener_dict)
|
||||
if listener_obj.tls_certificate_id or listener_obj.sni_containers:
|
||||
if (listener_obj.tls_certificate_id or listener_obj.sni_containers or
|
||||
listener_obj.client_ca_tls_certificate_id):
|
||||
SNI_objs = []
|
||||
for sni in listener_obj.sni_containers:
|
||||
if isinstance(sni, dict):
|
||||
|
@ -192,15 +207,20 @@ def listener_dict_to_provider_dict(listener_dict):
|
|||
).driver
|
||||
cert_dict = cert_parser.load_certificates_data(cert_manager,
|
||||
listener_obj)
|
||||
if 'tls_cert' in cert_dict:
|
||||
if 'tls_cert' in cert_dict and cert_dict['tls_cert']:
|
||||
new_listener_dict['default_tls_container_data'] = (
|
||||
cert_dict['tls_cert'].to_dict())
|
||||
if 'sni_certs' in cert_dict:
|
||||
if 'sni_certs' in cert_dict and cert_dict['sni_certs']:
|
||||
sni_data_list = []
|
||||
for sni in cert_dict['sni_certs']:
|
||||
sni_data_list.append(sni.to_dict())
|
||||
new_listener_dict['sni_container_data'] = sni_data_list
|
||||
|
||||
if listener_obj.client_ca_tls_certificate_id:
|
||||
cert = _get_secret_data(cert_manager, listener_obj,
|
||||
listener_obj.client_ca_tls_certificate_id)
|
||||
new_listener_dict['client_ca_tls_container_data'] = cert
|
||||
|
||||
# Remove the DB back references
|
||||
if 'load_balancer' in new_listener_dict:
|
||||
del new_listener_dict['load_balancer']
|
||||
|
|
|
@ -89,6 +89,9 @@ class RootController(rest.RestController):
|
|||
self._add_a_version(versions, 'v2.6', 'v2', 'SUPPORTED',
|
||||
'2019-01-25T00:00:00Z', host_url)
|
||||
# Amphora Config update
|
||||
self._add_a_version(versions, 'v2.7', 'v2', 'CURRENT',
|
||||
self._add_a_version(versions, 'v2.7', 'v2', 'SUPPORTED',
|
||||
'2018-01-25T12:00:00Z', host_url)
|
||||
# TLS client authentication
|
||||
self._add_a_version(versions, 'v2.8', 'v2', 'CURRENT',
|
||||
'2019-02-12T00:00:00Z', host_url)
|
||||
return {'versions': versions}
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography import x509
|
||||
from oslo_config import cfg
|
||||
from oslo_db import exception as odb_exceptions
|
||||
from oslo_log import log as logging
|
||||
|
@ -140,11 +142,48 @@ class ListenersController(base.BaseController):
|
|||
if bad_refs:
|
||||
raise exceptions.CertificateRetrievalException(ref=bad_refs)
|
||||
|
||||
def _validate_client_ca_ref(self, client_ca_ref):
|
||||
context = pecan.request.context.get('octavia_context')
|
||||
bad_refs = []
|
||||
try:
|
||||
self.cert_manager.set_acls(context, client_ca_ref)
|
||||
ca_pem = self.cert_manager.get_secret(context, client_ca_ref)
|
||||
except Exception:
|
||||
bad_refs.append(client_ca_ref)
|
||||
|
||||
# This will be used in a later patch
|
||||
if bad_refs:
|
||||
raise exceptions.CertificateRetrievalException(ref=bad_refs)
|
||||
|
||||
try:
|
||||
# Test if it needs to be UTF-8 encoded
|
||||
try:
|
||||
ca_pem = ca_pem.encode('utf-8')
|
||||
except AttributeError:
|
||||
pass
|
||||
x509.load_pem_x509_certificate(ca_pem, default_backend())
|
||||
except Exception as e:
|
||||
raise exceptions.ValidationException(detail=_(
|
||||
"The client authentication CA certificate is invalid. "
|
||||
"It must be a valid x509 PEM format certificate. "
|
||||
"Error: %s") % str(e))
|
||||
|
||||
def _has_tls_container_refs(self, listener_dict):
|
||||
return (listener_dict.get('tls_certificate_id') or
|
||||
listener_dict.get('client_ca_tls_container_id') or
|
||||
listener_dict.get('sni_containers'))
|
||||
|
||||
def _is_tls_or_insert_header(self, listener_dict):
|
||||
return (self._has_tls_container_refs(listener_dict) or
|
||||
listener_dict.get('insert_headers'))
|
||||
|
||||
def _validate_create_listener(self, lock_session, listener_dict):
|
||||
"""Validate listener for wrong protocol or duplicate listeners
|
||||
|
||||
Update the load balancer db when provisioning status changes.
|
||||
"""
|
||||
listener_protocol = listener_dict.get('protocol')
|
||||
|
||||
if (listener_dict and
|
||||
listener_dict.get('insert_headers') and
|
||||
list(set(listener_dict['insert_headers'].keys()) -
|
||||
|
@ -153,12 +192,53 @@ class ListenersController(base.BaseController):
|
|||
value=listener_dict.get('insert_headers'),
|
||||
option='insert_headers')
|
||||
|
||||
# Check for UDP compatibility
|
||||
if (listener_protocol == constants.PROTOCOL_UDP and
|
||||
self._is_tls_or_insert_header(listener_dict)):
|
||||
raise exceptions.ValidationException(detail=_(
|
||||
"%s protocol listener does not support TLS or header "
|
||||
"insertion.") % constants.PROTOCOL_UDP)
|
||||
|
||||
# Check for TLS disabled
|
||||
if (not CONF.api_settings.allow_tls_terminated_listeners and
|
||||
listener_protocol == constants.PROTOCOL_TERMINATED_HTTPS):
|
||||
raise exceptions.DisabledOption(
|
||||
value=constants.PROTOCOL_TERMINATED_HTTPS, option='protocol')
|
||||
|
||||
# Check for certs when not TERMINATED_HTTPS
|
||||
if (listener_protocol != constants.PROTOCOL_TERMINATED_HTTPS and
|
||||
self._has_tls_container_refs(listener_dict)):
|
||||
raise exceptions.ValidationException(detail=_(
|
||||
"Certificate container references are only allowed on "
|
||||
"%s protocol listeners.") %
|
||||
constants.PROTOCOL_TERMINATED_HTTPS)
|
||||
|
||||
# Make sure a base certificate exists if specifying a client ca
|
||||
if (listener_dict.get('client_ca_tls_certificate_id') and
|
||||
not (listener_dict.get('tls_certificate_id') or
|
||||
listener_dict.get('sni_containers'))):
|
||||
raise exceptions.ValidationException(detail=_(
|
||||
"An SNI or default certificate container reference must "
|
||||
"be provided with a client CA container reference."))
|
||||
|
||||
# Make sure a certificate container is specified for TERMINATED_HTTPS
|
||||
if (listener_protocol == constants.PROTOCOL_TERMINATED_HTTPS and
|
||||
not (listener_dict.get('tls_certificate_id') or
|
||||
listener_dict.get('sni_containers'))):
|
||||
raise exceptions.ValidationException(detail=_(
|
||||
"An SNI or default certificate container reference must "
|
||||
"be provided for %s protocol listeners.") %
|
||||
constants.PROTOCOL_TERMINATED_HTTPS)
|
||||
|
||||
try:
|
||||
sni_containers = listener_dict.pop('sni_containers', [])
|
||||
tls_refs = [sni['tls_container_id'] for sni in sni_containers]
|
||||
if listener_dict.get('tls_certificate_id'):
|
||||
tls_refs.append(listener_dict.get('tls_certificate_id'))
|
||||
self._validate_tls_refs(tls_refs)
|
||||
if listener_dict.get('client_ca_tls_certificate_id'):
|
||||
self._validate_client_ca_ref(
|
||||
listener_dict.get('client_ca_tls_certificate_id'))
|
||||
db_listener = self.repositories.listener.create(
|
||||
lock_session, **listener_dict)
|
||||
if sni_containers:
|
||||
|
@ -183,10 +263,6 @@ class ListenersController(base.BaseController):
|
|||
raise exceptions.InvalidOption(value=listener_dict.get('protocol'),
|
||||
option='protocol')
|
||||
|
||||
def _is_tls_or_insert_header(self, listener):
|
||||
return (listener.default_tls_container_ref or
|
||||
listener.sni_container_refs or listener.insert_headers)
|
||||
|
||||
@wsme_pecan.wsexpose(listener_types.ListenerRootResponse,
|
||||
body=listener_types.ListenerRootPOST, status_code=201)
|
||||
def post(self, listener_):
|
||||
|
@ -200,15 +276,6 @@ class ListenersController(base.BaseController):
|
|||
|
||||
self._auth_validate_action(context, listener.project_id,
|
||||
constants.RBAC_POST)
|
||||
if (listener.protocol == constants.PROTOCOL_UDP and
|
||||
self._is_tls_or_insert_header(listener)):
|
||||
raise exceptions.ValidationException(detail=_(
|
||||
"%s protocol listener does not support TLS or header "
|
||||
"insertion.") % constants.PROTOCOL_UDP)
|
||||
if (not CONF.api_settings.allow_tls_terminated_listeners and
|
||||
listener.protocol == constants.PROTOCOL_TERMINATED_HTTPS):
|
||||
raise exceptions.DisabledOption(
|
||||
value=constants.PROTOCOL_TERMINATED_HTTPS, option='protocol')
|
||||
|
||||
# Load the driver early as it also provides validation
|
||||
driver = driver_factory.get_driver(provider)
|
||||
|
@ -293,6 +360,37 @@ class ListenersController(base.BaseController):
|
|||
db_listener.l7policies = new_l7ps
|
||||
return db_listener
|
||||
|
||||
def _validate_listener_PUT(self, listener, db_listener):
|
||||
# TODO(rm_work): Do we need something like this? What do we do on an
|
||||
# empty body for a PUT?
|
||||
if not listener:
|
||||
raise exceptions.ValidationException(
|
||||
detail='No listener object supplied.')
|
||||
|
||||
# Check for UDP compatibility
|
||||
if (db_listener.protocol == constants.PROTOCOL_UDP and
|
||||
self._is_tls_or_insert_header(listener.to_dict())):
|
||||
raise exceptions.ValidationException(detail=_(
|
||||
"%s protocol listener does not support TLS or header "
|
||||
"insertion.") % constants.PROTOCOL_UDP)
|
||||
|
||||
# Check for certs when not TERMINATED_HTTPS
|
||||
if (db_listener.protocol != constants.PROTOCOL_TERMINATED_HTTPS and
|
||||
self._has_tls_container_refs(listener.to_dict())):
|
||||
raise exceptions.ValidationException(detail=_(
|
||||
"Certificate container references are only allowed on "
|
||||
"%s protocol listeners.") %
|
||||
constants.PROTOCOL_TERMINATED_HTTPS)
|
||||
|
||||
# Make sure the refs are valid
|
||||
sni_containers = listener.sni_container_refs or []
|
||||
tls_refs = [sni for sni in sni_containers]
|
||||
if listener.default_tls_container_ref:
|
||||
tls_refs.append(listener.default_tls_container_ref)
|
||||
self._validate_tls_refs(tls_refs)
|
||||
if listener.client_ca_tls_container_ref:
|
||||
self._validate_client_ca_ref(listener.client_ca_tls_container_ref)
|
||||
|
||||
@wsme_pecan.wsexpose(listener_types.ListenerRootResponse, wtypes.text,
|
||||
body=listener_types.ListenerRootPUT, status_code=200)
|
||||
def put(self, id, listener_):
|
||||
|
@ -308,28 +406,12 @@ class ListenersController(base.BaseController):
|
|||
|
||||
self._auth_validate_action(context, project_id, constants.RBAC_PUT)
|
||||
|
||||
# TODO(rm_work): Do we need something like this? What do we do on an
|
||||
# empty body for a PUT?
|
||||
if not listener:
|
||||
raise exceptions.ValidationException(
|
||||
detail='No listener object supplied.')
|
||||
|
||||
if (db_listener.protocol == constants.PROTOCOL_UDP and
|
||||
self._is_tls_or_insert_header(listener)):
|
||||
raise exceptions.ValidationException(detail=_(
|
||||
"%s protocol listener does not support TLS or header "
|
||||
"insertion.") % constants.PROTOCOL_UDP)
|
||||
self._validate_listener_PUT(listener, db_listener)
|
||||
|
||||
if listener.default_pool_id:
|
||||
self._validate_pool(context.session, load_balancer_id,
|
||||
listener.default_pool_id, db_listener.protocol)
|
||||
|
||||
sni_containers = listener.sni_container_refs or []
|
||||
tls_refs = [sni for sni in sni_containers]
|
||||
if listener.default_tls_container_ref:
|
||||
tls_refs.append(listener.default_tls_container_ref)
|
||||
self._validate_tls_refs(tls_refs)
|
||||
|
||||
# Load the driver early as it also provides validation
|
||||
driver = driver_factory.get_driver(provider)
|
||||
|
||||
|
|
|
@ -25,8 +25,10 @@ CONF.import_group('haproxy_amphora', 'octavia.common.config')
|
|||
|
||||
|
||||
class BaseListenerType(types.BaseType):
|
||||
_type_to_model_map = {'admin_state_up': 'enabled',
|
||||
'default_tls_container_ref': 'tls_certificate_id'}
|
||||
_type_to_model_map = {
|
||||
'admin_state_up': 'enabled',
|
||||
'default_tls_container_ref': 'tls_certificate_id',
|
||||
'client_ca_tls_container_ref': 'client_ca_tls_certificate_id'}
|
||||
_child_map = {}
|
||||
|
||||
|
||||
|
@ -55,6 +57,7 @@ class ListenerResponse(BaseListenerType):
|
|||
timeout_member_data = wtypes.wsattr(wtypes.IntegerType())
|
||||
timeout_tcp_inspect = wtypes.wsattr(wtypes.IntegerType())
|
||||
tags = wtypes.wsattr(wtypes.ArrayType(wtypes.StringType()))
|
||||
client_ca_tls_container_ref = wtypes.StringType()
|
||||
|
||||
@classmethod
|
||||
def from_data_model(cls, data_model, children=False):
|
||||
|
@ -63,7 +66,6 @@ class ListenerResponse(BaseListenerType):
|
|||
|
||||
listener.sni_container_refs = [
|
||||
sni_c.tls_container_id for sni_c in data_model.sni_containers]
|
||||
|
||||
if cls._full_response():
|
||||
del listener.loadbalancers
|
||||
l7policy_type = l7policy.L7PolicyFullResponse
|
||||
|
@ -135,6 +137,7 @@ class ListenerPOST(BaseListenerType):
|
|||
maximum=constants.MAX_TIMEOUT),
|
||||
default=CONF.haproxy_amphora.timeout_tcp_inspect)
|
||||
tags = wtypes.wsattr(wtypes.ArrayType(wtypes.StringType(max_length=255)))
|
||||
client_ca_tls_container_ref = wtypes.StringType(max_length=255)
|
||||
|
||||
|
||||
class ListenerRootPOST(types.BaseType):
|
||||
|
@ -167,6 +170,7 @@ class ListenerPUT(BaseListenerType):
|
|||
wtypes.IntegerType(minimum=constants.MIN_TIMEOUT,
|
||||
maximum=constants.MAX_TIMEOUT))
|
||||
tags = wtypes.wsattr(wtypes.ArrayType(wtypes.StringType(max_length=255)))
|
||||
client_ca_tls_container_ref = wtypes.StringType(max_length=255)
|
||||
|
||||
|
||||
class ListenerRootPUT(types.BaseType):
|
||||
|
@ -210,6 +214,7 @@ class ListenerSingleCreate(BaseListenerType):
|
|||
maximum=constants.MAX_TIMEOUT),
|
||||
default=CONF.haproxy_amphora.timeout_tcp_inspect)
|
||||
tags = wtypes.wsattr(wtypes.ArrayType(wtypes.StringType(max_length=255)))
|
||||
client_ca_tls_container_ref = wtypes.StringType(max_length=255)
|
||||
|
||||
|
||||
class ListenerStatusResponse(BaseListenerType):
|
||||
|
|
|
@ -159,3 +159,24 @@ class BarbicanCertManager(cert_mgr.CertManager):
|
|||
# the legacy driver is complete.
|
||||
legacy_mgr = barbican_legacy.BarbicanCertManager(auth=self.auth)
|
||||
legacy_mgr.unset_acls(context, cert_ref)
|
||||
|
||||
def get_secret(self, context, secret_ref):
|
||||
"""Retrieves a secret payload by reference.
|
||||
|
||||
:param context: Oslo context of the request
|
||||
:param secret_ref: The secret reference ID
|
||||
|
||||
:return: The secret payload
|
||||
:raises CertificateStorageException: if retrieval fails
|
||||
"""
|
||||
connection = self.auth.get_barbican_client(context.project_id)
|
||||
|
||||
LOG.info('Loading secret %s from Barbican.', secret_ref)
|
||||
try:
|
||||
secret = connection.secrets.get(secret_ref=secret_ref)
|
||||
return secret.payload
|
||||
except Exception as e:
|
||||
LOG.error("Failed to access secret for %s due to: %s.",
|
||||
secret_ref, str(e))
|
||||
raise exceptions.CertificateStorageException(
|
||||
msg="Secret could not be accessed.")
|
||||
|
|
|
@ -226,3 +226,7 @@ class BarbicanCertManager(cert_mgr.CertManager):
|
|||
if cert_container.intermediates:
|
||||
self.auth.revoke_secret_access(
|
||||
context, cert_container.intermediates.secret_ref)
|
||||
|
||||
def get_secret(self, context, secret_ref):
|
||||
# The legacy driver doesn't need get_secret
|
||||
return None
|
||||
|
|
|
@ -71,3 +71,14 @@ class CastellanCertManager(cert_mgr.CertManager):
|
|||
# We don't manage ACL based access for things retrieved via Castellan
|
||||
# because we assume we have elevated access to the secret store.
|
||||
pass
|
||||
|
||||
def get_secret(self, context, secret_ref):
|
||||
try:
|
||||
certbag = self.manager.get(context, secret_ref)
|
||||
certbag_data = certbag.get_encoded()
|
||||
except Exception as e:
|
||||
LOG.error("Failed to access secret for %s due to: %s.",
|
||||
secret_ref, str(e))
|
||||
raise exceptions.CertificateStorageException(
|
||||
msg="Secret could not be accessed.")
|
||||
return certbag_data
|
||||
|
|
|
@ -72,3 +72,11 @@ class CertManager(object):
|
|||
If the specified cert does not exist or the removal of ACLs fails for
|
||||
any reason, a CertificateStorageException should be raised.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_secret(self, context, secret_ref):
|
||||
"""Retrieves a secret payload by reference.
|
||||
|
||||
If the specified secret does not exist, a CertificateStorageException
|
||||
should be raised.
|
||||
"""
|
||||
|
|
|
@ -168,3 +168,33 @@ class LocalCertManager(cert_mgr.CertManager):
|
|||
def unset_acls(self, context, cert_ref):
|
||||
# There is no security on this store, because it's really dumb
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def get_secret(context, secret_ref):
|
||||
"""Retrieves a secret payload by reference.
|
||||
|
||||
:param context: Ignored in this implementation
|
||||
:param secret_ref: The secret reference ID
|
||||
|
||||
:return: The secret payload
|
||||
:raises CertificateStorageException: if secret retrieval fails
|
||||
"""
|
||||
LOG.info("Loading secret %s from the local filesystem.", secret_ref)
|
||||
|
||||
filename_base = os.path.join(CONF.certificates.storage_path,
|
||||
secret_ref)
|
||||
|
||||
filename_secret = "{0}.pem".format(filename_base)
|
||||
|
||||
secret_data = None
|
||||
|
||||
flags = os.O_RDONLY
|
||||
try:
|
||||
with os.fdopen(os.open(filename_secret, flags)) as secret_file:
|
||||
secret_data = secret_file.read()
|
||||
except IOError:
|
||||
LOG.error("Failed to read secret for %s.", secret_ref)
|
||||
raise exceptions.CertificateStorageException(
|
||||
msg="secret could not be read.")
|
||||
|
||||
return secret_data
|
||||
|
|
|
@ -368,7 +368,7 @@ class Listener(BaseDataModel):
|
|||
created_at=None, updated_at=None,
|
||||
timeout_client_data=None, timeout_member_connect=None,
|
||||
timeout_member_data=None, timeout_tcp_inspect=None,
|
||||
tags=None):
|
||||
tags=None, client_ca_tls_certificate_id=None):
|
||||
self.id = id
|
||||
self.project_id = project_id
|
||||
self.name = name
|
||||
|
@ -397,6 +397,7 @@ class Listener(BaseDataModel):
|
|||
self.timeout_member_data = timeout_member_data
|
||||
self.timeout_tcp_inspect = timeout_tcp_inspect
|
||||
self.tags = tags
|
||||
self.client_ca_tls_certificate_id = client_ca_tls_certificate_id
|
||||
|
||||
def update(self, update_dict):
|
||||
for key, value in update_dict.items():
|
||||
|
|
|
@ -78,7 +78,8 @@ class JinjaTemplater(object):
|
|||
self.connection_logging = connection_logging
|
||||
|
||||
def build_config(self, host_amphora, listener, tls_cert,
|
||||
haproxy_versions, socket_path=None):
|
||||
haproxy_versions, socket_path=None,
|
||||
client_ca_filename=None):
|
||||
"""Convert a logical configuration to the HAProxy version
|
||||
|
||||
:param host_amphora: The Amphora this configuration is hosted on
|
||||
|
@ -99,7 +100,8 @@ class JinjaTemplater(object):
|
|||
|
||||
return self.render_loadbalancer_obj(
|
||||
host_amphora, listener, tls_cert=tls_cert, socket_path=socket_path,
|
||||
feature_compatibility=feature_compatibility)
|
||||
feature_compatibility=feature_compatibility,
|
||||
client_ca_filename=client_ca_filename)
|
||||
|
||||
def _get_template(self):
|
||||
"""Returns the specified Jinja configuration template."""
|
||||
|
@ -117,12 +119,14 @@ class JinjaTemplater(object):
|
|||
|
||||
def render_loadbalancer_obj(self, host_amphora, listener,
|
||||
tls_cert=None, socket_path=None,
|
||||
feature_compatibility=None):
|
||||
feature_compatibility=None,
|
||||
client_ca_filename=None):
|
||||
"""Renders a templated configuration from a load balancer object
|
||||
|
||||
:param host_amphora: The Amphora this configuration is hosted on
|
||||
:param listener: The listener configuration
|
||||
:param tls_cert: The TLS certificates for the listener
|
||||
:param client_ca_filename: The CA certificate for client authorization
|
||||
:param socket_path: The socket path for Haproxy process
|
||||
:return: Rendered configuration
|
||||
"""
|
||||
|
@ -132,7 +136,8 @@ class JinjaTemplater(object):
|
|||
listener.load_balancer,
|
||||
listener,
|
||||
tls_cert,
|
||||
feature_compatibility)
|
||||
feature_compatibility,
|
||||
client_ca_filename=client_ca_filename)
|
||||
if not socket_path:
|
||||
socket_path = '%s/%s.sock' % (self.base_amp_path, listener.id)
|
||||
return self._get_template().render(
|
||||
|
@ -144,13 +149,15 @@ class JinjaTemplater(object):
|
|||
constants=constants)
|
||||
|
||||
def _transform_loadbalancer(self, host_amphora, loadbalancer, listener,
|
||||
tls_cert, feature_compatibility):
|
||||
tls_cert, feature_compatibility,
|
||||
client_ca_filename=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)
|
||||
listener, tls_cert, feature_compatibility,
|
||||
client_ca_filename=client_ca_filename)
|
||||
ret_value = {
|
||||
'id': loadbalancer.id,
|
||||
'vip_address': loadbalancer.vip.ip_address,
|
||||
|
@ -189,7 +196,8 @@ class JinjaTemplater(object):
|
|||
'vrrp_priority': amphora.vrrp_priority
|
||||
}
|
||||
|
||||
def _transform_listener(self, listener, tls_cert, feature_compatibility):
|
||||
def _transform_listener(self, listener, tls_cert, feature_compatibility,
|
||||
client_ca_filename=None):
|
||||
"""Transforms a listener into an object that will
|
||||
|
||||
be processed by the templating system
|
||||
|
@ -227,6 +235,10 @@ class JinjaTemplater(object):
|
|||
tls_cert.id))
|
||||
if listener.sni_containers:
|
||||
ret_value['crt_dir'] = os.path.join(self.base_crt_dir, listener.id)
|
||||
if listener.client_ca_tls_certificate_id:
|
||||
ret_value['client_ca_tls_path'] = '%s' % (
|
||||
os.path.join(self.base_crt_dir, listener.id,
|
||||
client_ca_filename))
|
||||
if listener.default_pool:
|
||||
ret_value['default_pool'] = self._transform_pool(
|
||||
listener.default_pool, feature_compatibility)
|
||||
|
|
|
@ -38,8 +38,13 @@ peers {{ "%s_peers"|format(listener.id.replace("-", ""))|trim() }}
|
|||
{% else %}
|
||||
{% set crt_dir_opt = "" %}
|
||||
{% endif %}
|
||||
{% if listener.client_ca_tls_path %}
|
||||
{% set client_ca_opt = "ca-file %s"|format(listener.client_ca_tls_path)|trim() %}
|
||||
{% else %}
|
||||
{% set client_ca_opt = "" %}
|
||||
{% endif %}
|
||||
bind {{ lb_vip_address }}:{{ listener.protocol_port }} {{
|
||||
"%s %s"|format(def_crt_opt, crt_dir_opt)|trim() }}
|
||||
"%s %s %s"|format(def_crt_opt, crt_dir_opt, client_ca_opt)|trim() }}
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
|
|
|
@ -328,7 +328,9 @@ def build_pem(tls_container):
|
|||
:param tls_container: Object container TLS certificates
|
||||
:returns: Pem encoded certificate file
|
||||
"""
|
||||
pem = [tls_container.certificate, tls_container.private_key]
|
||||
pem = [tls_container.certificate]
|
||||
if tls_container.private_key:
|
||||
pem.append(tls_container.private_key)
|
||||
if tls_container.intermediates:
|
||||
pem.extend(tls_container.intermediates[:])
|
||||
return b'\n'.join(pem) + b'\n'
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
# 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.
|
||||
#
|
||||
|
||||
"""Add listener client_ca_tls_certificate_id column
|
||||
|
||||
Revision ID: 2ad093f6353f
|
||||
Revises: 11e4bb2bb8ef
|
||||
Create Date: 2019-02-13 08:32:43.009997
|
||||
|
||||
"""
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '2ad093f6353f'
|
||||
down_revision = '11e4bb2bb8ef'
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.add_column(
|
||||
u'listener',
|
||||
sa.Column(u'client_ca_tls_certificate_id', sa.String(255),
|
||||
nullable=True)
|
||||
)
|
|
@ -463,7 +463,7 @@ class Listener(base_models.BASE, base_models.IdMixin,
|
|||
sa.String(36),
|
||||
sa.ForeignKey("load_balancer.id", name="fk_listener_load_balancer_id"),
|
||||
nullable=True)
|
||||
tls_certificate_id = sa.Column(sa.String(36), nullable=True)
|
||||
tls_certificate_id = sa.Column(sa.String(255), nullable=True)
|
||||
default_pool_id = sa.Column(
|
||||
sa.String(36),
|
||||
sa.ForeignKey("pool.id", name="fk_listener_pool_id"),
|
||||
|
@ -498,6 +498,7 @@ class Listener(base_models.BASE, base_models.IdMixin,
|
|||
timeout_member_connect = sa.Column(sa.Integer, nullable=True)
|
||||
timeout_member_data = sa.Column(sa.Integer, nullable=True)
|
||||
timeout_tcp_inspect = sa.Column(sa.Integer, nullable=True)
|
||||
client_ca_tls_certificate_id = sa.Column(sa.String(255), nullable=True)
|
||||
|
||||
_tags = orm.relationship(
|
||||
'Tags',
|
||||
|
|
|
@ -90,10 +90,6 @@ def create_listener(listener_dict, lb_id):
|
|||
if 'tls_termination' in listener_dict:
|
||||
del listener_dict['tls_termination']
|
||||
|
||||
if 'default_tls_container_ref' in listener_dict:
|
||||
listener_dict['tls_certificate_id'] = (
|
||||
listener_dict.pop('default_tls_container_ref'))
|
||||
|
||||
if 'sni_containers' in listener_dict:
|
||||
sni_container_ids = listener_dict.pop('sni_containers') or []
|
||||
elif 'sni_container_refs' in listener_dict:
|
||||
|
@ -104,6 +100,7 @@ def create_listener(listener_dict, lb_id):
|
|||
'tls_container_id': sni_container_id}
|
||||
for sni_container_id in sni_container_ids]
|
||||
listener_dict['sni_containers'] = sni_containers
|
||||
|
||||
return listener_dict
|
||||
|
||||
|
||||
|
|
|
@ -46,7 +46,7 @@ class TestRootController(base_db_test.OctaviaDBTestBase):
|
|||
versions = self._get_versions_with_config(
|
||||
api_v1_enabled=True, api_v2_enabled=True)
|
||||
version_ids = tuple(v.get('id') for v in versions)
|
||||
self.assertEqual(9, len(version_ids))
|
||||
self.assertEqual(10, len(version_ids))
|
||||
self.assertIn('v1', version_ids)
|
||||
self.assertIn('v2.0', version_ids)
|
||||
self.assertIn('v2.1', version_ids)
|
||||
|
@ -56,6 +56,7 @@ class TestRootController(base_db_test.OctaviaDBTestBase):
|
|||
self.assertIn('v2.5', version_ids)
|
||||
self.assertIn('v2.6', version_ids)
|
||||
self.assertIn('v2.7', version_ids)
|
||||
self.assertIn('v2.8', version_ids)
|
||||
|
||||
# Each version should have a 'self' 'href' to the API version URL
|
||||
# [{u'rel': u'self', u'href': u'http://localhost/v2'}]
|
||||
|
@ -75,7 +76,7 @@ class TestRootController(base_db_test.OctaviaDBTestBase):
|
|||
def test_api_v1_disabled(self):
|
||||
versions = self._get_versions_with_config(
|
||||
api_v1_enabled=False, api_v2_enabled=True)
|
||||
self.assertEqual(8, len(versions))
|
||||
self.assertEqual(9, len(versions))
|
||||
self.assertEqual('v2.0', versions[0].get('id'))
|
||||
self.assertEqual('v2.1', versions[1].get('id'))
|
||||
self.assertEqual('v2.2', versions[2].get('id'))
|
||||
|
@ -84,6 +85,7 @@ class TestRootController(base_db_test.OctaviaDBTestBase):
|
|||
self.assertEqual('v2.5', versions[5].get('id'))
|
||||
self.assertEqual('v2.6', versions[6].get('id'))
|
||||
self.assertEqual('v2.7', versions[7].get('id'))
|
||||
self.assertEqual('v2.8', versions[8].get('id'))
|
||||
|
||||
def test_api_v2_disabled(self):
|
||||
versions = self._get_versions_with_config(
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import copy
|
||||
import random
|
||||
|
||||
import mock
|
||||
|
@ -25,6 +26,7 @@ import octavia.common.context
|
|||
from octavia.common import data_models
|
||||
from octavia.common import exceptions
|
||||
from octavia.tests.functional.api.v2 import base
|
||||
from octavia.tests.unit.common.sample_configs import sample_certs
|
||||
|
||||
|
||||
class TestListener(base.BaseAPITest):
|
||||
|
@ -564,7 +566,7 @@ class TestListener(base.BaseAPITest):
|
|||
lb_listener = {'name': 'listener1', 'default_pool_id': None,
|
||||
'description': 'desc1',
|
||||
'admin_state_up': False,
|
||||
'protocol': constants.PROTOCOL_HTTP,
|
||||
'protocol': constants.PROTOCOL_TERMINATED_HTTPS,
|
||||
'protocol_port': 80, 'connection_limit': 10,
|
||||
'default_tls_container_ref': uuidutils.generate_uuid(),
|
||||
'sni_container_refs': [sni1, sni2],
|
||||
|
@ -724,6 +726,47 @@ class TestListener(base.BaseAPITest):
|
|||
self.assertNotIn(sni2, response['faultstring'])
|
||||
self.assertIn(tls_ref, response['faultstring'])
|
||||
|
||||
def test_create_with_certs_not_terminated_https(self):
|
||||
optionals = {
|
||||
'default_tls_container_ref': uuidutils.generate_uuid(),
|
||||
'protocol': constants.PROTOCOL_TCP
|
||||
}
|
||||
resp = self.test_create(response_status=400, **optionals).json
|
||||
fault = resp.get('faultstring')
|
||||
self.assertIn(
|
||||
'Certificate container references are only allowed on ', fault)
|
||||
self.assertIn(
|
||||
'{} protocol listeners.'.format(
|
||||
constants.PROTOCOL_TERMINATED_HTTPS), fault)
|
||||
|
||||
def test_create_without_certs_if_terminated_https(self):
|
||||
optionals = {
|
||||
'default_tls_container_ref': None,
|
||||
'sni_container_refs': None,
|
||||
'protocol': constants.PROTOCOL_TERMINATED_HTTPS
|
||||
}
|
||||
resp = self.test_create(response_status=400, **optionals).json
|
||||
fault = resp.get('faultstring')
|
||||
self.assertIn(
|
||||
'An SNI or default certificate container reference must ', fault)
|
||||
self.assertIn(
|
||||
'be provided for {} protocol listeners.'.format(
|
||||
constants.PROTOCOL_TERMINATED_HTTPS), fault)
|
||||
|
||||
def test_create_client_ca_cert_without_tls_cert(self):
|
||||
optionals = {
|
||||
'default_tls_container_ref': None,
|
||||
'sni_container_refs': None,
|
||||
'client_ca_tls_container_ref': uuidutils.generate_uuid(),
|
||||
'protocol': constants.PROTOCOL_TERMINATED_HTTPS
|
||||
}
|
||||
resp = self.test_create(response_status=400, **optionals).json
|
||||
fault = resp.get('faultstring')
|
||||
self.assertIn(
|
||||
'An SNI or default certificate container reference must ', fault)
|
||||
self.assertIn(
|
||||
'be provided with a client CA container reference.', fault)
|
||||
|
||||
def test_create_with_default_pool_id(self):
|
||||
lb_listener = {'name': 'listener1',
|
||||
'default_pool_id': self.pool_id,
|
||||
|
@ -824,23 +867,14 @@ class TestListener(base.BaseAPITest):
|
|||
self.assertIn('Provider \'bad_driver\' reports error: broken',
|
||||
response.json.get('faultstring'))
|
||||
|
||||
# TODO(johnsom) Fix this when there is a noop certificate manager
|
||||
@mock.patch('octavia.common.tls_utils.cert_parser.load_certificates_data')
|
||||
def test_create_authorized(self, mock_cert_data, **optionals):
|
||||
cert1 = data_models.TLSContainer(certificate='cert 1')
|
||||
cert2 = data_models.TLSContainer(certificate='cert 2')
|
||||
cert3 = data_models.TLSContainer(certificate='cert 3')
|
||||
mock_cert_data.return_value = {'tls_cert': cert1,
|
||||
'sni_certs': [cert2, cert3]}
|
||||
sni1 = uuidutils.generate_uuid()
|
||||
sni2 = uuidutils.generate_uuid()
|
||||
def test_create_authorized(self, **optionals):
|
||||
lb_listener = {'name': 'listener1', 'default_pool_id': None,
|
||||
'description': 'desc1',
|
||||
'admin_state_up': False,
|
||||
'protocol': constants.PROTOCOL_HTTP,
|
||||
'protocol_port': 80, 'connection_limit': 10,
|
||||
'default_tls_container_ref': uuidutils.generate_uuid(),
|
||||
'sni_container_refs': [sni1, sni2],
|
||||
'default_tls_container_ref': None,
|
||||
'sni_container_refs': None,
|
||||
'insert_headers': {},
|
||||
'project_id': self.project_id,
|
||||
'loadbalancer_id': self.lb_id}
|
||||
|
@ -881,12 +915,6 @@ class TestListener(base.BaseAPITest):
|
|||
for key, value in optionals.items():
|
||||
self.assertEqual(value, lb_listener.get(key))
|
||||
lb_listener['id'] = listener_api.get('id')
|
||||
lb_listener.pop('sni_container_refs')
|
||||
sni_ex = [sni1, sni2]
|
||||
sni_resp = listener_api.pop('sni_container_refs')
|
||||
self.assertEqual(2, len(sni_resp))
|
||||
for sni in sni_resp:
|
||||
self.assertIn(sni, sni_ex)
|
||||
self.assertIsNotNone(listener_api.pop('created_at'))
|
||||
self.assertIsNone(listener_api.pop('updated_at'))
|
||||
self.assertNotEqual(lb_listener, listener_api)
|
||||
|
@ -895,15 +923,13 @@ class TestListener(base.BaseAPITest):
|
|||
self.assert_final_listener_statuses(self.lb_id, listener_api.get('id'))
|
||||
|
||||
def test_create_not_authorized(self, **optionals):
|
||||
sni1 = uuidutils.generate_uuid()
|
||||
sni2 = uuidutils.generate_uuid()
|
||||
lb_listener = {'name': 'listener1', 'default_pool_id': None,
|
||||
'description': 'desc1',
|
||||
'admin_state_up': False,
|
||||
'protocol': constants.PROTOCOL_HTTP,
|
||||
'protocol_port': 80, 'connection_limit': 10,
|
||||
'default_tls_container_ref': uuidutils.generate_uuid(),
|
||||
'sni_container_refs': [sni1, sni2],
|
||||
'default_tls_container_ref': None,
|
||||
'sni_container_refs': None,
|
||||
'insert_headers': {},
|
||||
'project_id': self.project_id,
|
||||
'loadbalancer_id': self.lb_id}
|
||||
|
@ -921,6 +947,63 @@ class TestListener(base.BaseAPITest):
|
|||
self.conf.config(group='api_settings', auth_strategy=auth_strategy)
|
||||
self.assertEqual(self.NOT_AUTHORIZED_BODY, response.json)
|
||||
|
||||
def test_create_with_ca_cert(self):
|
||||
self.cert_manager_mock().get_secret.return_value = (
|
||||
sample_certs.X509_CA_CERT)
|
||||
|
||||
optionals = {
|
||||
'client_ca_tls_container_ref': uuidutils.generate_uuid()
|
||||
}
|
||||
listener_api = self.test_create(**optionals)
|
||||
self.assertEqual(optionals['client_ca_tls_container_ref'],
|
||||
listener_api.get('client_ca_tls_container_ref'))
|
||||
|
||||