diff --git a/api-ref/source/parameters.yaml b/api-ref/source/parameters.yaml
index 50e7eec28f..2804f3c2b9 100644
--- a/api-ref/source/parameters.yaml
+++ b/api-ref/source/parameters.yaml
@@ -245,17 +245,21 @@ default_pool_id-optional:
type: string
default_tls_container_ref:
description: |
- The URI to the `key manager service
- `__ secrets container
- containing the certificate and key for ``TERMINATED_TLS`` listeners.
+ The URI of the `key manager service
+ `__ secret containing a
+ PKCS12 format certificate/key bundle for ``TERMINATED_TLS`` listeners.
+ DEPRECATED: A secret container of type "certificate" containing the
+ certificate and key for ``TERMINATED_TLS`` listeners.
in: body
required: true
type: string
default_tls_container_ref-optional:
description: |
- The URI to the `key manager service
- `__ secrets container
- containing the certificate and key for ``TERMINATED_TLS`` listeners.
+ The URI of the `key manager service
+ `__ secret containing a
+ PKCS12 format certificate/key bundle for ``TERMINATED_TLS`` listeners.
+ DEPRECATED: A secret container of type "certificate" containing the
+ certificate and key for ``TERMINATED_TLS`` listeners.
in: body
required: false
type: string
@@ -963,18 +967,20 @@ session_persistence_type:
sni_container_refs:
description: |
A list of URIs to the `key manager service
- `__ secrets containers
- containing the certificates and keys for ``TERMINATED_TLS`` the listener
- using Server Name Indication.
+ `__ secrets containing
+ PKCS12 format certificate/key bundles for ``TERMINATED_TLS`` listeners.
+ (DEPRECATED) Secret containers of type "certificate" containing the
+ certificates and keys for ``TERMINATED_TLS`` listeners.
in: body
required: true
type: array
sni_container_refs-optional:
description: |
A list of URIs to the `key manager service
- `__ secrets containers
- containing the certificates and keys for ``TERMINATED_TLS`` the listener
- using Server Name Indication.
+ `__ secrets containing
+ PKCS12 format certificate/key bundles for ``TERMINATED_TLS`` listeners.
+ (DEPRECATED) Secret containers of type "certificate" containing the
+ certificates and keys for ``TERMINATED_TLS`` listeners.
in: body
required: false
type: array
diff --git a/api-ref/source/v1/octaviaapi.rst b/api-ref/source/v1/octaviaapi.rst
index d089d5d365..617ea3f7fe 100644
--- a/api-ref/source/v1/octaviaapi.rst
+++ b/api-ref/source/v1/octaviaapi.rst
@@ -1,5 +1,5 @@
-Octavia API v1 (SUPORTED)
-=========================
+Octavia API v1 (SUPPORTED)
+==========================
Authentication
--------------
diff --git a/doc/source/user/guides/basic-cookbook.rst b/doc/source/user/guides/basic-cookbook.rst
index 4af90db7aa..a566f580c2 100644
--- a/doc/source/user/guides/basic-cookbook.rst
+++ b/doc/source/user/guides/basic-cookbook.rst
@@ -365,34 +365,30 @@ balancer features, like Layer 7 features and header manipulation.
* Back-end servers 192.0.2.10 and 192.0.2.11 on subnet *private-subnet* have
been configured with regular HTTP application on TCP port 80.
-* These back-end servers have been configured with a health check at the URL
- path "/healthcheck". See :ref:`http-heath-monitors` below.
* Subnet *public-subnet* is a shared external subnet created by the cloud
operator which is reachable from the internet.
* A TLS certificate, key, and intermediate certificate chain for
www.example.com have been obtained from an external certificate authority.
- These now exist in the files server.crt, server.key, and ca-chain.p7b in the
+ These now exist in the files server.crt, server.key, and ca-chain.crt in the
current directory. The key and certificate are PEM-encoded, and the
- intermediate certificate chain is PKCS7 PEM encoded. The key is not encrypted
- with a passphrase.
+ intermediate certificate chain is multiple PEM-encoded certs concatenated
+ together. The key is not encrypted with a passphrase.
* The *admin* user on this cloud installation has keystone ID *admin_id*
* We want to configure a TLS-terminated HTTPS load balancer that is accessible
from the internet using the key and certificate mentioned above, which
distributes requests to the back-end servers over the non-encrypted HTTP
protocol.
+* Octavia is configured to use barbican for key management.
**Solution**:
-1. Create barbican *secret* resources for the certificate, key, and
- intermediate certificate chain. We will call these *cert1*, *key1*, and
- *intermediates1* respectively.
-2. Create a *secret container* resource combining all of the above. We will
- call this *tls_container1*.
-3. Grant the *admin* user access to all the *secret* and *secret container*
- barbican resources above.
+1. Combine the individual cert/key/intermediates to a single PKCS12 file.
+2. Create a barbican *secret* resource for the PKCS12 file. We will call
+ this *tls_secret1*.
+3. Grant the *admin* user access to the *tls_secret1* barbican resource.
4. Create load balancer *lb1* on subnet *public-subnet*.
5. Create listener *listener1* as a TERMINATED_HTTPS listener referencing
- *tls_container1* as its default TLS container.
+ *tls_secret1* as its default TLS container.
6. Create pool *pool1* as *listener1*'s default pool.
7. Add members 192.0.2.10 and 192.0.2.11 on *private-subnet* to *pool1*.
@@ -400,21 +396,16 @@ balancer features, like Layer 7 features and header manipulation.
::
- openstack secret store --name='cert1' --payload-content-type='text/plain' --payload="$(cat server.crt)"
- openstack secret store --name='key1' --payload-content-type='text/plain' --payload="$(cat server.key)"
- openstack secret store --name='intermediates1' --payload-content-type='text/plain' --payload="$(cat ca-chain.p7b)"
- openstack secret container create --name='tls_container1' --type='certificate' --secret="certificate=$(openstack secret list | awk '/ cert1 / {print $2}')" --secret="private_key=$(openstack secret list | awk '/ key1 / {print $2}')" --secret="intermediates=$(openstack secret list | awk '/ intermediates1 / {print $2}')"
- openstack acl user add -u admin_id $(openstack secret list | awk '/ cert1 / {print $2}')
- openstack acl user add -u admin_id $(openstack secret list | awk '/ key1 / {print $2}')
- openstack acl user add -u admin_id $(openstack secret list | awk '/ intermediates1 / {print $2}')
- openstack acl user add -u admin_id $(openstack secret list | awk '/ tls_container1 / {print $2}')
- neutron lbaas-loadbalancer-create --name lb1 public-subnet
+ openssl pkcs12 -export -inkey server.key -in server.crt -certfile ca-chain.crt -passout pass: -out server.p12
+ openstack secret store --name='tls_secret1' -t 'application/octet-stream' -e 'base64' --payload="$(base64 < server.p12)"
+ openstack acl user add -u admin_id $(openstack secret list | awk '/ tls_secret1 / {print $2}')
+ openstack loadbalancer create --name lb1 --vip-subnet-id public-subnet
# Re-run the following until lb1 shows ACTIVE and ONLINE statuses:
- neutron lbaas-loadbalancer-show lb1
- neutron lbaas-listener-create --loadbalancer lb1 --protocol-port 443 --protocol TERMINATED_HTTPS --name listener1 --default-tls-container=$(openstack secret container list | awk '/ tls_container1 / {print $2}')
- neutron lbaas-pool-create --name pool1 --lb-algorithm ROUND_ROBIN --listener listener1 --protocol HTTP
- neutron lbaas-member-create --subnet private-subnet --address 192.0.2.10 --protocol-port 80 pool1
- neutron lbaas-member-create --subnet private-subnet --address 192.0.2.11 --protocol-port 80 pool1
+ openstack loadbalancer show lb1
+ openstack loadbalancer listener create --protocol-port 443 --protocol TERMINATED_HTTPS --name listener1 --default-tls-container=$(openstack secret list | awk '/ tls_secret1 / {print $2}' lb1
+ openstack loadbalancer pool create --name pool1 --lb-algorithm ROUND_ROBIN --listener listener1 --protocol HTTP
+ openstack loadbalancer member create --subnet-id private-subnet --address 192.0.2.10 --protocol-port 80 pool1
+ openstack loadbalancer member create --subnet-id private-subnet --address 192.0.2.11 --protocol-port 80 pool1
Deploy a TLS-terminated HTTPS load balancer with SNI
@@ -427,18 +418,15 @@ listener using Server Name Indication (SNI) technology.
* Back-end servers 192.0.2.10 and 192.0.2.11 on subnet *private-subnet* have
been configured with regular HTTP application on TCP port 80.
-* These back-end servers have been configured with a health check at the URL
- path "/healthcheck". See :ref:`http-heath-monitors` below.
* Subnet *public-subnet* is a shared external subnet created by the cloud
operator which is reachable from the internet.
* TLS certificates, keys, and intermediate certificate chains for
www.example.com and www2.example.com have been obtained from an external
certificate authority. These now exist in the files server.crt, server.key,
- ca-chain.p7b, server2.crt, server2-encrypted.key, and ca-chain2.p7b in the
+ ca-chain.crt, server2.crt, server2.key, and ca-chain2.crt in the
current directory. The keys and certificates are PEM-encoded, and the
- intermediate certificate chains are PKCS7 PEM encoded.
-* The key for www.example.com is not encrypted with a passphrase.
-* The key for www2.example.com is encrypted with the passphrase "abc123".
+ intermediate certificate chains are multiple certs PEM-encoded and
+ concatenated together. Neither key is encrypted with a passphrase.
* The *admin* user on this cloud installation has keystone ID *admin_id*
* We want to configure a TLS-terminated HTTPS load balancer that is accessible
from the internet using the keys and certificates mentioned above, which
@@ -449,50 +437,34 @@ listener using Server Name Indication (SNI) technology.
**Solution**:
-1. Create barbican *secret* resources for the certificates, keys, and
- intermediate certificate chains. We will call these *cert1*, *key1*,
- *intermediates1*, *cert2*, *key2* and *intermediates2* respectively.
-2. Create a barbican *secret* resource *passphrase2* for the passphrase for
- *key2*
-3. Create *secret container* resources combining the above appropriately. We
- will call these *tls_container1* and *tls_container2*.
-4. Grant the *admin* user access to all the *secret* and *secret container*
- barbican resources above.
-5. Create load balancer *lb1* on subnet *public-subnet*.
-6. Create listener *listener1* as a TERMINATED_HTTPS listener referencing
- *tls_container1* as its default TLS container, and referencing both
- *tls_container1* and *tls_container2* using SNI.
-7. Create pool *pool1* as *listener1*'s default pool.
-8. Add members 192.0.2.10 and 192.0.2.11 on *private-subnet* to *pool1*.
+1. Combine the individual cert/key/intermediates to single PKCS12 files.
+2. Create barbican *secret* resources for the PKCS12 files. We will call them
+ *tls_secret1* and *tls_secret2*.
+3. Grant the *admin* user access to both *tls_secret* barbican resources.
+4. Create load balancer *lb1* on subnet *public-subnet*.
+5. Create listener *listener1* as a TERMINATED_HTTPS listener referencing
+ *tls_secret1* as its default TLS container, and referencing both
+ *tls_secret1* and *tls_secret2* using SNI.
+6. Create pool *pool1* as *listener1*'s default pool.
+7. Add members 192.0.2.10 and 192.0.2.11 on *private-subnet* to *pool1*.
**CLI commands**:
::
- openstack secret store --name='cert1' --payload-content-type='text/plain' --payload="$(cat server.crt)"
- openstack secret store --name='key1' --payload-content-type='text/plain' --payload="$(cat server.key)"
- openstack secret store --name='intermediates1' --payload-content-type='text/plain' --payload="$(cat ca-chain.p7b)"
- openstack secret container create --name='tls_container1' --type='certificate' --secret="certificate=$(openstack secret list | awk '/ cert1 / {print $2}')" --secret="private_key=$(openstack secret list | awk '/ key1 / {print $2}')" --secret="intermediates=$(openstack secret list | awk '/ intermediates1 / {print $2}')"
- openstack secret store --name='cert2' --payload-content-type='text/plain' --payload="$(cat server2.crt)"
- openstack secret store --name='key2' --payload-content-type='text/plain' --payload="$(cat server2-encrypted.key)"
- openstack secret store --name='intermediates2' --payload-content-type='text/plain' --payload="$(cat ca-chain2.p7b)"
- openstack secret store --name='passphrase2' --payload-content-type='text/plain' --payload="abc123"
- openstack secret container create --name='tls_container2' --type='certificate' --secret="certificate=$(openstack secret list | awk '/ cert2 / {print $2}')" --secret="private_key=$(openstack secret list | awk '/ key2 / {print $2}')" --secret="intermediates=$(openstack secret list | awk '/ intermediates2 / {print $2}')" --secret="private_key_passphrase=$(openstack secret list | awk '/ passphrase2 / {print $2}')"
- openstack acl user add -u admin_id $(openstack secret list | awk '/ cert1 / {print $2}')
- openstack acl user add -u admin_id $(openstack secret list | awk '/ key1 / {print $2}')
- openstack acl user add -u admin_id $(openstack secret list | awk '/ intermediates1 / {print $2}')
- openstack acl user add -u admin_id $(openstack secret list | awk '/ tls_container1 / {print $2}')
- openstack acl user add -u admin_id $(openstack secret list | awk '/ cert2 / {print $2}')
- openstack acl user add -u admin_id $(openstack secret list | awk '/ key2 / {print $2}')
- openstack acl user add -u admin_id $(openstack secret list | awk '/ intermediates2 / {print $2}')
- openstack acl user add -u admin_id $(openstack secret list | awk '/ tls_container2 / {print $2}')
- neutron lbaas-loadbalancer-create --name lb1 public-subnet
+ openssl pkcs12 -export -inkey server.key -in server.crt -certfile ca-chain.crt -passout pass: -out server.p12
+ openssl pkcs12 -export -inkey server2.key -in server2.crt -certfile ca-chain2.crt -passout pass: -out server2.p12
+ openstack secret store --name='tls_secret1' -t 'application/octet-stream' -e 'base64' --payload="$(base64 < server.p12)"
+ openstack secret store --name='tls_secret2' -t 'application/octet-stream' -e 'base64' --payload="$(base64 < server2.p12)"
+ openstack acl user add -u admin_id $(openstack secret list | awk '/ tls_secret1 / {print $2}')
+ openstack acl user add -u admin_id $(openstack secret list | awk '/ tls_secret2 / {print $2}')
+ openstack loadbalancer create --name lb1 --vip-subnet-id public-subnet
# Re-run the following until lb1 shows ACTIVE and ONLINE statuses:
- neutron lbaas-loadbalancer-show lb1
- neutron lbaas-listener-create --loadbalancer lb1 --protocol-port 443 --protocol TERMINATED_HTTPS --name listener1 --default-tls-container=$(openstack secret container list | awk '/ tls_container1 / {print $2}') --sni-container_refs $(openstack secret container list | awk '/ tls_container1 / {print $2}') $(openstack secret container list | awk '/ tls_container2 / {print $2}')
- neutron lbaas-pool-create --name pool1 --lb-algorithm ROUND_ROBIN --listener listener1 --protocol HTTP
- neutron lbaas-member-create --subnet private-subnet --address 192.0.2.10 --protocol-port 80 pool1
- neutron lbaas-member-create --subnet private-subnet --address 192.0.2.11 --protocol-port 80 pool1
+ openstack loadbalancer show lb1
+ openstack loadbalancer listener create --protocol-port 443 --protocol TERMINATED_HTTPS --name listener1 --default-tls-container=$(openstack secret list | awk '/ tls_secret1 / {print $2}' --sni-container_refs $(openstack secret list | awk '/ tls_secret1 / {print $2}') $(openstack secret list | awk '/ tls_secret2 / {print $2}') lb1
+ openstack loadbalancer pool create --name pool1 --lb-algorithm ROUND_ROBIN --listener listener1 --protocol HTTP
+ openstack loadbalancer member create --subnet-id private-subnet --address 192.0.2.10 --protocol-port 80 pool1
+ openstack loadbalancer member create --subnet-id private-subnet --address 192.0.2.11 --protocol-port 80 pool1
Deploy HTTP and TLS-terminated HTTPS load balancing on the same IP and backend
@@ -512,16 +484,14 @@ HTTP just get redirected to the HTTPS listener), then please see `the example
* Back-end servers 192.0.2.10 and 192.0.2.11 on subnet *private-subnet* have
been configured with regular HTTP application on TCP port 80.
-* These back-end servers have been configured with a health check at the URL
- path "/healthcheck". See :ref:`http-heath-monitors` below.
* Subnet *public-subnet* is a shared external subnet created by the cloud
operator which is reachable from the internet.
* A TLS certificate, key, and intermediate certificate chain for
www.example.com have been obtained from an external certificate authority.
- These now exist in the files server.crt, server.key, and ca-chain.p7b in the
+ These now exist in the files server.crt, server.key, and ca-chain.crt in the
current directory. The key and certificate are PEM-encoded, and the
- intermediate certificate chain is PKCS7 PEM encoded. The key is not encrypted
- with a passphrase.
+ intermediate certificate chain is multiple PEM-encoded certs concatenated
+ together. The key is not encrypted with a passphrase.
* The *admin* user on this cloud installation has keystone ID *admin_id*
* We want to configure a TLS-terminated HTTPS load balancer that is accessible
from the internet using the key and certificate mentioned above, which
@@ -533,16 +503,13 @@ HTTP just get redirected to the HTTPS listener), then please see `the example
**Solution**:
-1. Create barbican *secret* resources for the certificate, key, and
- intermediate certificate chain. We will call these *cert1*, *key1*, and
- *intermediates1* respectively.
-2. Create a *secret container* resource combining all of the above. We will
- call this *tls_container1*.
-3. Grant the *admin* user access to all the *secret* and *secret container*
- barbican resources above.
+1. Combine the individual cert/key/intermediates to a single PKCS12 file.
+2. Create a barbican *secret* resource for the PKCS12 file. We will call
+ this *tls_secret1*.
+3. Grant the *admin* user access to the *tls_secret1* barbican resource.
4. Create load balancer *lb1* on subnet *public-subnet*.
5. Create listener *listener1* as a TERMINATED_HTTPS listener referencing
- *tls_container1* as its default TLS container.
+ *tls_secret1* as its default TLS container.
6. Create pool *pool1* as *listener1*'s default pool.
7. Add members 192.0.2.10 and 192.0.2.11 on *private-subnet* to *pool1*.
8. Create listener *listener2* as an HTTP listener with *pool1* as its
@@ -552,22 +519,18 @@ HTTP just get redirected to the HTTPS listener), then please see `the example
::
- openstack secret store --name='cert1' --payload-content-type='text/plain' --payload="$(cat server.crt)"
- openstack secret store --name='key1' --payload-content-type='text/plain' --payload="$(cat server.key)"
- openstack secret store --name='intermediates1' --payload-content-type='text/plain' --payload="$(cat ca-chain.p7b)"
- openstack secret container create --name='tls_container1' --type='certificate' --secret="certificate=$(openstack secret list | awk '/ cert1 / {print $2}')" --secret="private_key=$(openstack secret list | awk '/ key1 / {print $2}')" --secret="intermediates=$(openstack secret list | awk '/ intermediates1 / {print $2}')"
- openstack acl user add -u admin_id $(openstack secret list | awk '/ cert1 / {print $2}')
- openstack acl user add -u admin_id $(openstack secret list | awk '/ key1 / {print $2}')
- openstack acl user add -u admin_id $(openstack secret list | awk '/ intermediates1 / {print $2}')
- openstack acl user add -u admin_id $(openstack secret list | awk '/ tls_container1 / {print $2}')
- neutron lbaas-loadbalancer-create --name lb1 public-subnet
+ openssl pkcs12 -export -inkey server.key -in server.crt -certfile ca-chain.crt -passout pass: -out server.p12
+ openstack secret store --name='tls_secret1' -t 'application/octet-stream' -e 'base64' --payload="$(base64 < server.p12)"
+ openstack acl user add -u admin_id $(openstack secret list | awk '/ tls_secret1 / {print $2}')
+ openstack loadbalancer create --name lb1 --vip-subnet-id public-subnet
# Re-run the following until lb1 shows ACTIVE and ONLINE statuses:
- neutron lbaas-loadbalancer-show lb1
- neutron lbaas-listener-create --loadbalancer lb1 --protocol-port 443 --protocol TERMINATED_HTTPS --name listener1 --default-tls-container=$(openstack secret container list | awk '/ tls_container1 / {print $2}')
- neutron lbaas-pool-create --name pool1 --lb-algorithm ROUND_ROBIN --listener listener1 --protocol HTTP
- neutron lbaas-member-create --subnet private-subnet --address 192.0.2.10 --protocol-port 80 pool1
- neutron lbaas-member-create --subnet private-subnet --address 192.0.2.11 --protocol-port 80 pool1
- neutron lbaas-listener-create --name listener2 --loadbalancer lb1 --protocol HTTP --protocol-port 80 --default-pool pool1
+ openstack loadbalancer show lb1
+ openstack loadbalancer listener create --protocol-port 443 --protocol TERMINATED_HTTPS --name listener1 --default-tls-container=$(openstack secret list | awk '/ tls_secret1 / {print $2}' lb1
+ openstack loadbalancer pool create --name pool1 --lb-algorithm ROUND_ROBIN --listener listener1 --protocol HTTP
+ openstack loadbalancer member create --subnet-id private-subnet --address 192.0.2.10 --protocol-port 80 pool1
+ openstack loadbalancer member create --subnet-id private-subnet --address 192.0.2.11 --protocol-port 80 pool1
+ openstack secret store --name='tls_secret1' --payload-content-type='text/plain' --payload="$(cat server.crt)"
+ openstack loadbalancer listener create --protocol-port 80 --protocol HTTP --name listener2 --default-pool pool1 lb1
.. _heath-monitor-best-practices:
@@ -691,39 +654,33 @@ blocks of 64-character lines of ASCII text (that will look like gobbedlygook to
a human). These files are also typically named with a ``.crt`` or ``.pem``
extension.
-To upload this type of intermediates chain to barbican, run a command similar
-to the following (assuming "intermediates-chain.pem" is the name of the file):
-
-::
-
- openstack secret store --name='intermediates1' --payload-content-type='text/plain' --payload="$(cat intermediates-chain.pem)"
-
DER-encoded chains
------------------
If the intermediates chain provided to you is a file that contains what appears
to be random binary data, it is likely that it is a PKCS7 chain in DER format.
-These files also may be named with a ``.p7b`` extension. In order to use this
-intermediates chain, you can either convert it to a series of PEM-encoded
-certificates with the following command:
+These files also may be named with a ``.p7b`` extension.
+
+You may use the binary DER file as-is when building your PKCS12 bundle:
::
- openssl pkcs7 -in intermediates-chain.p7b -inform DER -print_certs -out intermediates-chain.pem
+ openssl pkcs12 -export -inkey server.key -in server.crt -certfile ca-chain.p7b -passout pass: -out server.p12
-...or convert it into a PEM-encoded PKCS7 bundle with the following command:
+... or you can convert it to a series of PEM-encoded certificates:
::
- openssl pkcs7 -in intermediates-chain.p7b -inform DER -outform PEM -out intermediates-chain.pem
+ openssl pkcs7 -in intermediates-chain.p7b -inform DER -print_certs -out intermediates-chain.crt
-...or simply upload the binary DER file to barbican without conversion:
+... or you can convert it to a PEM-encoded PKCS7 bundle:
::
- openstack secret store --name='intermediates1' --payload-content-type='application/octet-stream' --payload-content-encoding='base64' --payload="$(cat intermediates-chain.p7b | base64)"
+ openssl pkcs7 -in intermediates-chain.p7b -inform DER -outform PEM -out intermediates-chain.crt
-In any case, if the file is not a PKCS7 DER bundle, then either of the above
-two openssl commands will fail.
+
+If the file is not a PKCS7 DER bundle, either of the two ``openssl pkcs7``
+commands will fail.
Further reading
===============
diff --git a/etc/octavia.conf b/etc/octavia.conf
index c90da6162e..2c8e2a5625 100644
--- a/etc/octavia.conf
+++ b/etc/octavia.conf
@@ -108,6 +108,7 @@
# For the TLS management
# Certificate Manager options are local_cert_manager
# barbican_cert_manager
+# castellan_cert_manager
# cert_manager = barbican_cert_manager
# For Barbican authentication (if using any Barbican based cert class)
# barbican_auth = barbican_acl_auth
diff --git a/octavia/certificates/common/barbican.py b/octavia/certificates/common/barbican.py
index 47f3a6f559..0306e41714 100644
--- a/octavia/certificates/common/barbican.py
+++ b/octavia/certificates/common/barbican.py
@@ -19,7 +19,7 @@ Common classes for Barbican certificate handling
import abc
-from barbicanclient import client as barbican_client
+from barbicanclient.v1 import containers
from oslo_utils import encodeutils
import six
@@ -31,8 +31,7 @@ from octavia.i18n import _
class BarbicanCert(cert.Cert):
"""Representation of a Cert based on the Barbican CertificateContainer."""
def __init__(self, cert_container):
- if not isinstance(cert_container,
- barbican_client.containers.CertificateContainer):
+ if not isinstance(cert_container, containers.CertificateContainer):
raise TypeError(_("Retrieved Barbican Container is not of the "
"correct type (certificate)."))
self._cert_container = cert_container
diff --git a/octavia/certificates/common/pkcs12.py b/octavia/certificates/common/pkcs12.py
new file mode 100644
index 0000000000..93d1e8dd81
--- /dev/null
+++ b/octavia/certificates/common/pkcs12.py
@@ -0,0 +1,54 @@
+# Copyright (c) 2017 GoDaddy
+# All Rights Reserved.
+#
+# 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.
+
+"""
+Common classes for pkcs12 based certificate handling
+"""
+
+from cryptography.hazmat.primitives import serialization
+from OpenSSL import crypto
+
+from octavia.certificates.common import cert
+
+
+class PKCS12Cert(cert.Cert):
+ """Representation of a Cert for local storage."""
+ def __init__(self, certbag):
+ p12 = crypto.load_pkcs12(certbag)
+ self.certificate = p12.get_certificate()
+ self.intermediates = p12.get_ca_certificates()
+ self.private_key = p12.get_privatekey()
+
+ def get_certificate(self):
+ return self.certificate.to_cryptography().public_bytes(
+ encoding=serialization.Encoding.PEM).strip()
+
+ def get_intermediates(self):
+ if self.intermediates:
+ int_data = [
+ ic.to_cryptography().public_bytes(
+ encoding=serialization.Encoding.PEM).strip()
+ for ic in self.intermediates
+ ]
+ return int_data
+
+ def get_private_key(self):
+ return self.private_key.to_cryptography_key().private_bytes(
+ encoding=serialization.Encoding.PEM,
+ format=serialization.PrivateFormat.TraditionalOpenSSL,
+ encryption_algorithm=serialization.NoEncryption()).strip()
+
+ def get_private_key_passphrase(self):
+ return None
diff --git a/octavia/certificates/manager/barbican.py b/octavia/certificates/manager/barbican.py
index 4e2d47d0f4..d799e71508 100644
--- a/octavia/certificates/manager/barbican.py
+++ b/octavia/certificates/manager/barbican.py
@@ -1,4 +1,5 @@
# Copyright (c) 2014 Rackspace US, Inc
+# Copyright (c) 2017 GoDaddy
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -14,15 +15,21 @@
# under the License.
"""
-Cert manager implementation for Barbican
+Cert manager implementation for Barbican using a single PKCS12 secret
"""
+from OpenSSL import crypto
+
from oslo_config import cfg
from oslo_log import log as logging
+from oslo_utils import encodeutils
from oslo_utils import excutils
from stevedore import driver as stevedore_driver
-from octavia.certificates.common import barbican as barbican_common
+from octavia.certificates.common import pkcs12
+from octavia.certificates.manager import barbican_legacy
from octavia.certificates.manager import cert_mgr
+from octavia.common import exceptions
+from octavia.common.tls_utils import cert_parser
LOG = logging.getLogger(__name__)
@@ -38,11 +45,12 @@ class BarbicanCertManager(cert_mgr.CertManager):
invoke_on_load=True,
).driver
- def store_cert(self, project_id, certificate, private_key,
- intermediates=None, private_key_passphrase=None,
- expiration=None, name='Octavia TLS Cert'):
+ def store_cert(self, context, certificate, private_key, intermediates=None,
+ private_key_passphrase=None, expiration=None,
+ name="PKCS12 Certificate Bundle"):
"""Stores a certificate in the certificate manager.
+ :param context: Oslo context of the request
:param certificate: PEM encoded TLS certificate
:param private_key: private key for the supplied certificate
:param intermediates: ordered and concatenated intermediate certs
@@ -53,68 +61,42 @@ class BarbicanCertManager(cert_mgr.CertManager):
:returns: the container_ref of the stored cert
:raises Exception: if certificate storage fails
"""
- connection = self.auth.get_barbican_client(project_id)
+ connection = self.auth.get_barbican_client(context.project_id)
- LOG.info("Storing certificate container '%s' in Barbican.", name)
-
- certificate_secret = None
- private_key_secret = None
- intermediates_secret = None
- pkp_secret = None
+ LOG.info("Storing certificate secret '%s' in Barbican.", name)
+ p12 = crypto.PKCS12()
+ p12.set_friendlyname(encodeutils.to_utf8(name))
+ x509_cert = crypto.load_certificate(crypto.FILETYPE_PEM, certificate)
+ p12.set_certificate(x509_cert)
+ x509_pk = crypto.load_privatekey(crypto.FILETYPE_PEM, private_key)
+ p12.set_privatekey(x509_pk)
+ if intermediates:
+ cert_ints = list(cert_parser.get_intermediates_pems(intermediates))
+ x509_ints = [
+ crypto.load_certificate(crypto.FILETYPE_PEM, ci)
+ for ci in cert_ints]
+ p12.set_ca_certificates(x509_ints)
+ if private_key_passphrase:
+ raise exceptions.CertificateStorageException(
+ "Passphrase protected PKCS12 certificates are not supported.")
try:
certificate_secret = connection.secrets.create(
- payload=certificate,
+ payload=p12.export(),
expiration=expiration,
- name="Certificate"
+ name=name
)
- private_key_secret = connection.secrets.create(
- payload=private_key,
- expiration=expiration,
- name="Private Key"
- )
- certificate_container = connection.containers.create_certificate(
- name=name,
- certificate=certificate_secret,
- private_key=private_key_secret
- )
- if intermediates:
- intermediates_secret = connection.secrets.create(
- payload=intermediates,
- expiration=expiration,
- name="Intermediates"
- )
- certificate_container.intermediates = intermediates_secret
- if private_key_passphrase:
- pkp_secret = connection.secrets.create(
- payload=private_key_passphrase,
- expiration=expiration,
- name="Private Key Passphrase"
- )
- certificate_container.private_key_passphrase = pkp_secret
-
- certificate_container.store()
- return certificate_container.container_ref
+ certificate_secret.store()
+ return certificate_secret.secret_ref
except Exception as e:
- for i in [certificate_secret, private_key_secret,
- intermediates_secret, pkp_secret]:
- if i and i.secret_ref:
- old_ref = i.secret_ref
- try:
- i.delete()
- LOG.info('Deleted secret %s (%s) during rollback.',
- i.name, old_ref)
- except Exception:
- LOG.warning('Failed to delete %s (%s) during '
- 'rollback. This might not be a problem.',
- i.name, old_ref)
with excutils.save_and_reraise_exception():
LOG.error('Error storing certificate data: %s', e)
- def get_cert(self, project_id, cert_ref, resource_ref=None,
- check_only=False, service_name='Octavia'):
+ def get_cert(self, context, cert_ref, resource_ref=None, check_only=False,
+ service_name=None):
"""Retrieves the specified cert and registers as a consumer.
+ :param context: Oslo context of the request
:param cert_ref: the UUID of the cert to retrieve
:param resource_ref: Full HATEOAS reference to the consuming resource
:param check_only: Read Certificate data without registering
@@ -124,45 +106,40 @@ class BarbicanCertManager(cert_mgr.CertManager):
certificate data
:raises Exception: if certificate retrieval fails
"""
- connection = self.auth.get_barbican_client(project_id)
+ connection = self.auth.get_barbican_client(context.project_id)
- LOG.info('Loading certificate container %s from Barbican.', cert_ref)
+ LOG.info('Loading certificate secret %s from Barbican.', cert_ref)
try:
- if check_only:
- cert_container = connection.containers.get(
- container_ref=cert_ref
- )
- else:
- cert_container = connection.containers.register_consumer(
- container_ref=cert_ref,
- name=service_name,
- url=resource_ref
- )
- return barbican_common.BarbicanCert(cert_container)
- except Exception as e:
- with excutils.save_and_reraise_exception():
- LOG.error('Error getting %s: %s', cert_ref, e)
+ cert_secret = connection.secrets.get(secret_ref=cert_ref)
+ return pkcs12.PKCS12Cert(cert_secret.payload)
+ except Exception:
+ # If our get fails, try with the legacy driver.
+ # TODO(rm_work): Remove this code when the deprecation cycle for
+ # the legacy driver is complete.
+ legacy_mgr = barbican_legacy.BarbicanCertManager()
+ legacy_cert = legacy_mgr.get_cert(
+ context, cert_ref, resource_ref=resource_ref,
+ check_only=check_only, service_name=service_name
+ )
+ return legacy_cert
- def delete_cert(self, project_id, cert_ref, resource_ref=None,
- service_name='Octavia'):
+ def delete_cert(self, context, cert_ref, resource_ref, service_name=None):
"""Deregister as a consumer for the specified cert.
+ :param context: Oslo context of the request
:param cert_ref: the UUID of the cert to retrieve
:param resource_ref: Full HATEOAS reference to the consuming resource
:param service_name: Friendly name for the consuming service
:raises Exception: if deregistration fails
"""
- connection = self.auth.get_barbican_client(project_id)
-
- LOG.info('Deregistering as a consumer of %s in Barbican.', cert_ref)
+ # TODO(rm_work): We won't take any action on a delete in this driver,
+ # but for now try the legacy driver's delete and ignore failure.
try:
- connection.containers.remove_consumer(
- container_ref=cert_ref,
- name=service_name,
- url=resource_ref
- )
- except Exception as e:
- with excutils.save_and_reraise_exception():
- LOG.error('Error deregistering as a consumer of %s: %s',
- cert_ref, e)
+ legacy_mgr = barbican_legacy.BarbicanCertManager(auth=self.auth)
+ legacy_mgr.delete_cert(
+ context, cert_ref, resource_ref, service_name=service_name)
+ except Exception:
+ # If the delete failed, it was probably because it isn't legacy
+ # (this will be fixed once Secrets have Consumer registration).
+ pass
diff --git a/octavia/certificates/manager/barbican_legacy.py b/octavia/certificates/manager/barbican_legacy.py
new file mode 100644
index 0000000000..89c6144397
--- /dev/null
+++ b/octavia/certificates/manager/barbican_legacy.py
@@ -0,0 +1,172 @@
+# Copyright (c) 2014 Rackspace US, Inc
+# All Rights Reserved.
+#
+# 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.
+
+"""
+Legacy cert manager implementation for Barbican (container+secrets)
+"""
+from oslo_config import cfg
+from oslo_log import log as logging
+from oslo_utils import excutils
+from stevedore import driver as stevedore_driver
+
+from octavia.certificates.common import barbican as barbican_common
+from octavia.certificates.manager import cert_mgr
+
+LOG = logging.getLogger(__name__)
+
+
+class BarbicanCertManager(cert_mgr.CertManager):
+ """Certificate Manager that wraps the Barbican client API."""
+
+ def __init__(self, auth=None):
+ super(BarbicanCertManager, self).__init__()
+ if auth:
+ self.auth = auth
+ else:
+ self.auth = stevedore_driver.DriverManager(
+ namespace='octavia.barbican_auth',
+ name=cfg.CONF.certificates.barbican_auth,
+ invoke_on_load=True,
+ ).driver
+
+ def store_cert(self, context, certificate, private_key, intermediates=None,
+ private_key_passphrase=None, expiration=None, name=None):
+ """Stores a certificate in the certificate manager.
+
+ :param context: Oslo context of the request
+ :param certificate: PEM encoded TLS certificate
+ :param private_key: private key for the supplied certificate
+ :param intermediates: ordered and concatenated intermediate certs
+ :param private_key_passphrase: optional passphrase for the supplied key
+ :param expiration: the expiration time of the cert in ISO 8601 format
+ :param name: a friendly name for the cert
+
+ :returns: the container_ref of the stored cert
+ :raises Exception: if certificate storage fails
+ """
+ connection = self.auth.get_barbican_client(context.project_id)
+
+ LOG.info("Storing certificate container '%s' in Barbican.", name)
+
+ certificate_secret = None
+ private_key_secret = None
+ intermediates_secret = None
+ pkp_secret = None
+
+ try:
+ certificate_secret = connection.secrets.create(
+ payload=certificate,
+ expiration=expiration,
+ name="Certificate"
+ )
+ private_key_secret = connection.secrets.create(
+ payload=private_key,
+ expiration=expiration,
+ name="Private Key"
+ )
+ certificate_container = connection.containers.create_certificate(
+ name=name,
+ certificate=certificate_secret,
+ private_key=private_key_secret
+ )
+ if intermediates:
+ intermediates_secret = connection.secrets.create(
+ payload=intermediates,
+ expiration=expiration,
+ name="Intermediates"
+ )
+ certificate_container.intermediates = intermediates_secret
+ if private_key_passphrase:
+ pkp_secret = connection.secrets.create(
+ payload=private_key_passphrase,
+ expiration=expiration,
+ name="Private Key Passphrase"
+ )
+ certificate_container.private_key_passphrase = pkp_secret
+
+ certificate_container.store()
+ return certificate_container.container_ref
+ except Exception as e:
+ for i in [certificate_secret, private_key_secret,
+ intermediates_secret, pkp_secret]:
+ if i and i.secret_ref:
+ old_ref = i.secret_ref
+ try:
+ i.delete()
+ LOG.info('Deleted secret %s (%s) during rollback.',
+ i.name, old_ref)
+ except Exception:
+ LOG.warning('Failed to delete %s (%s) during '
+ 'rollback. This might not be a problem.',
+ i.name, old_ref)
+ with excutils.save_and_reraise_exception():
+ LOG.error('Error storing certificate data: %s', e)
+
+ def get_cert(self, context, cert_ref, resource_ref=None, check_only=False,
+ service_name=None):
+ """Retrieves the specified cert and registers as a consumer.
+
+ :param context: Oslo context of the request
+ :param cert_ref: the UUID of the cert to retrieve
+ :param resource_ref: Full HATEOAS reference to the consuming resource
+ :param check_only: Read Certificate data without registering
+ :param service_name: Friendly name for the consuming service
+
+ :return: octavia.certificates.common.Cert representation of the
+ certificate data
+ :raises Exception: if certificate retrieval fails
+ """
+ connection = self.auth.get_barbican_client(context.project_id)
+
+ LOG.info('Loading certificate container %s from Barbican.', cert_ref)
+ try:
+ if check_only:
+ cert_container = connection.containers.get(
+ container_ref=cert_ref
+ )
+ else:
+ cert_container = connection.containers.register_consumer(
+ container_ref=cert_ref,
+ name=service_name,
+ url=resource_ref
+ )
+ return barbican_common.BarbicanCert(cert_container)
+ except Exception as e:
+ with excutils.save_and_reraise_exception():
+ LOG.error('Error getting %s: %s', cert_ref, e)
+
+ def delete_cert(self, context, cert_ref, resource_ref, service_name=None):
+ """Deregister as a consumer for the specified cert.
+
+ :param context: Oslo context of the request
+ :param cert_ref: the UUID of the cert to retrieve
+ :param resource_ref: Full HATEOAS reference to the consuming resource
+ :param service_name: Friendly name for the consuming service
+
+ :raises Exception: if deregistration fails
+ """
+ connection = self.auth.get_barbican_client(context.project_id)
+
+ LOG.info('Deregistering as a consumer of %s in Barbican.', cert_ref)
+ try:
+ connection.containers.remove_consumer(
+ container_ref=cert_ref,
+ name=service_name,
+ url=resource_ref
+ )
+ except Exception as e:
+ with excutils.save_and_reraise_exception():
+ LOG.error('Error deregistering as a consumer of %s: %s',
+ cert_ref, e)
diff --git a/octavia/certificates/manager/castellan_mgr.py b/octavia/certificates/manager/castellan_mgr.py
new file mode 100644
index 0000000000..0186ddfd8f
--- /dev/null
+++ b/octavia/certificates/manager/castellan_mgr.py
@@ -0,0 +1,63 @@
+# Copyright (c) 2017 GoDaddy
+# All Rights Reserved.
+#
+# 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.
+
+"""
+Cert manager implementation for Castellan
+"""
+from castellan.common.objects import opaque_data
+from castellan import key_manager
+from OpenSSL import crypto
+from oslo_log import log as logging
+
+from octavia.certificates.common import pkcs12
+from octavia.certificates.manager import cert_mgr
+from octavia.common import exceptions
+
+LOG = logging.getLogger(__name__)
+
+
+class CastellanCertManager(cert_mgr.CertManager):
+ """Certificate Manager for the Castellan library."""
+
+ def __init__(self):
+ super(CastellanCertManager, self).__init__()
+ self.manager = key_manager.API()
+
+ def store_cert(self, context, certificate, private_key, intermediates=None,
+ private_key_passphrase=None, expiration=None,
+ name="PKCS12 Certificate Bundle"):
+ p12 = crypto.PKCS12()
+ p12.set_certificate(certificate)
+ p12.set_privatekey(private_key)
+ if intermediates:
+ p12.set_ca_certificates(intermediates)
+ if private_key_passphrase:
+ raise exceptions.CertificateStorageException(
+ "Passphrases protected PKCS12 certificates are not supported.")
+
+ p12_data = opaque_data.OpaqueData(p12.export(), name=name)
+ self.manager.store(context, p12_data)
+
+ def get_cert(self, context, cert_ref, resource_ref=None, check_only=False,
+ service_name=None):
+ certbag = self.manager.get(context, cert_ref)
+ certbag_data = certbag.get_encoded()
+ cert = pkcs12.PKCS12Cert(certbag_data)
+ return cert
+
+ def delete_cert(self, context, cert_ref, resource_ref, service_name=None):
+ # Delete is not a great name for this -- we don't delete anything
+ # in reality, we just do cleanup here. For castellan, none is required
+ pass
diff --git a/octavia/certificates/manager/cert_mgr.py b/octavia/certificates/manager/cert_mgr.py
index 1daf5ffede..b4fe6ffffb 100644
--- a/octavia/certificates/manager/cert_mgr.py
+++ b/octavia/certificates/manager/cert_mgr.py
@@ -29,9 +29,8 @@ class CertManager(object):
"""
@abc.abstractmethod
- def store_cert(self, project_id, certificate, private_key,
- intermediates=None, private_key_passphrase=None,
- expiration=None, name=None):
+ def store_cert(self, context, certificate, private_key, intermediates=None,
+ private_key_passphrase=None, expiration=None, name=None):
"""Stores (i.e., registers) a cert with the cert manager.
This method stores the specified cert and returns its UUID that
@@ -42,8 +41,8 @@ class CertManager(object):
pass
@abc.abstractmethod
- def get_cert(self, project_id, cert_ref, resource_ref=None,
- check_only=False, service_name=None):
+ def get_cert(self, context, cert_ref, resource_ref=None, check_only=False,
+ service_name=None):
"""Retrieves the specified cert.
If check_only is True, don't perform any sort of registration.
@@ -53,8 +52,7 @@ class CertManager(object):
pass
@abc.abstractmethod
- def delete_cert(self, project_id, cert_ref, resource_ref,
- service_name=None):
+ def delete_cert(self, context, cert_ref, resource_ref, service_name=None):
"""Deletes the specified cert.
If the specified cert does not exist, a CertificateStorageException
diff --git a/octavia/certificates/manager/local.py b/octavia/certificates/manager/local.py
index 03b7ff8428..fa5226e377 100644
--- a/octavia/certificates/manager/local.py
+++ b/octavia/certificates/manager/local.py
@@ -31,14 +31,14 @@ class LocalCertManager(cert_mgr.CertManager):
"""Cert Manager Interface that stores data locally."""
@staticmethod
- def store_cert(project_id, certificate, private_key, intermediates=None,
+ def store_cert(context, certificate, private_key, intermediates=None,
private_key_passphrase=None, **kwargs):
"""Stores (i.e., registers) a cert with the cert manager.
This method stores the specified cert to the filesystem and returns
a UUID that can be used to retrieve it.
- :param project_id: Ignored in this implementation
+ :param context: Ignored in this implementation
:param certificate: PEM encoded TLS certificate
:param private_key: private key for the supplied certificate
:param intermediates: ordered and concatenated intermediate certs
@@ -82,10 +82,10 @@ class LocalCertManager(cert_mgr.CertManager):
return cert_ref
@staticmethod
- def get_cert(project_id, cert_ref, **kwargs):
+ def get_cert(context, cert_ref, **kwargs):
"""Retrieves the specified cert.
- :param project_id: Ignored in this implementation
+ :param context: Ignored in this implementation
:param cert_ref: the UUID of the cert to retrieve
:return: octavia.certificates.common.Cert representation of the
@@ -134,10 +134,10 @@ class LocalCertManager(cert_mgr.CertManager):
return local_common.LocalCert(**cert_data)
@staticmethod
- def delete_cert(project_id, cert_ref, **kwargs):
+ def delete_cert(context, cert_ref, **kwargs):
"""Deletes the specified cert.
- :param project_id: Ignored in this implementation
+ :param context: Ignored in this implementation
:param cert_ref: the UUID of the cert to delete
:raises CertificateStorageException: if certificate deletion fails
diff --git a/octavia/common/tls_utils/cert_parser.py b/octavia/common/tls_utils/cert_parser.py
index 5b2ac8e378..60aac4a4b4 100644
--- a/octavia/common/tls_utils/cert_parser.py
+++ b/octavia/common/tls_utils/cert_parser.py
@@ -18,6 +18,7 @@ import base64
from cryptography.hazmat import backends
from cryptography.hazmat.primitives import serialization
from cryptography import x509
+from oslo_context import context as oslo_context
from oslo_log import log as logging
from pyasn1.codec.der import decoder as der_decoder
from pyasn1.codec.der import encoder as der_encoder
@@ -332,23 +333,25 @@ def build_pem(tls_container):
return b'\n'.join(pem) + b'\n'
-def load_certificates_data(cert_mngr, listener):
+def load_certificates_data(cert_mngr, listener, context=None):
"""Load TLS certificate data from the listener.
return TLS_CERT and SNI_CERTS
"""
tls_cert = None
sni_certs = []
+ if not context:
+ context = oslo_context.RequestContext(project_id=listener.project_id)
if listener.tls_certificate_id:
tls_cert = _map_cert_tls_container(
- cert_mngr.get_cert(listener.project_id,
+ cert_mngr.get_cert(context,
listener.tls_certificate_id,
check_only=True))
if listener.sni_containers:
for sni_cont in listener.sni_containers:
cert_container = _map_cert_tls_container(
- cert_mngr.get_cert(listener.project_id,
+ cert_mngr.get_cert(context,
sni_cont.tls_container_id,
check_only=True))
sni_certs.append(cert_container)
diff --git a/octavia/tests/unit/certificates/common/test_barbican.py b/octavia/tests/unit/certificates/common/test_barbican.py
index 830d546663..e02edc8448 100644
--- a/octavia/tests/unit/certificates/common/test_barbican.py
+++ b/octavia/tests/unit/certificates/common/test_barbican.py
@@ -12,7 +12,8 @@
# License for the specific language governing permissions and limitations
# under the License.
-from barbicanclient import client as barbican_client
+from barbicanclient.v1 import containers
+from barbicanclient.v1 import secrets
import mock
import six
@@ -24,19 +25,19 @@ import octavia.tests.unit.common.sample_configs.sample_certs as sample
class TestBarbicanCert(base.TestCase):
def _prepare(self):
- self.certificate_secret = barbican_client.secrets.Secret(
+ self.certificate_secret = secrets.Secret(
api=mock.MagicMock(),
payload=self.certificate
)
- self.intermediates_secret = barbican_client.secrets.Secret(
+ self.intermediates_secret = secrets.Secret(
api=mock.MagicMock(),
payload=sample.X509_IMDS
)
- self.private_key_secret = barbican_client.secrets.Secret(
+ self.private_key_secret = secrets.Secret(
api=mock.MagicMock(),
payload=self.private_key
)
- self.private_key_passphrase_secret = barbican_client.secrets.Secret(
+ self.private_key_passphrase_secret = secrets.Secret(
api=mock.MagicMock(),
payload=self.private_key_passphrase
)
@@ -49,7 +50,7 @@ class TestBarbicanCert(base.TestCase):
self.private_key_passphrase = sample.X509_CERT_KEY_PASSPHRASE
self._prepare()
- container = barbican_client.containers.CertificateContainer(
+ container = containers.CertificateContainer(
api=mock.MagicMock(),
certificate=self.certificate_secret,
intermediates=self.intermediates_secret,
@@ -78,7 +79,7 @@ class TestBarbicanCert(base.TestCase):
sample.X509_CERT_KEY_PASSPHRASE)
self._prepare()
- container = barbican_client.containers.CertificateContainer(
+ container = containers.CertificateContainer(
api=mock.MagicMock(),
certificate=self.certificate_secret,
intermediates=self.intermediates_secret,
diff --git a/octavia/tests/unit/certificates/manager/test_barbican.py b/octavia/tests/unit/certificates/manager/test_barbican.py
index 63347e321c..21d213172d 100644
--- a/octavia/tests/unit/certificates/manager/test_barbican.py
+++ b/octavia/tests/unit/certificates/manager/test_barbican.py
@@ -14,10 +14,8 @@
import uuid
-from barbicanclient import containers
-from barbicanclient import secrets
+from barbicanclient.v1 import secrets
import mock
-import six
import octavia.certificates.common.barbican as barbican_common
import octavia.certificates.common.cert as cert
@@ -32,47 +30,21 @@ PROJECT_ID = "12345"
class TestBarbicanManager(base.TestCase):
def setUp(self):
- # Make a fake Container and contents
+ # Make a fake Secret and contents
self.barbican_endpoint = 'http://localhost:9311/v1'
- self.container_uuid = uuid.uuid4()
+ self.secret_uuid = uuid.uuid4()
- self.container_ref = '{0}/containers/{1}'.format(
- self.barbican_endpoint, self.container_uuid
+ self.secret_ref = '{0}/secrets/{1}'.format(
+ self.barbican_endpoint, self.secret_uuid
)
self.name = 'My Fancy Cert'
- self.certificate = secrets.Secret(
+ self.secret = secrets.Secret(
api=mock.MagicMock(),
- payload=sample.X509_CERT
- )
- self.intermediates = secrets.Secret(
- api=mock.MagicMock(),
- payload=sample.X509_IMDS
- )
- self.private_key = secrets.Secret(
- api=mock.MagicMock(),
- payload=sample.X509_CERT_KEY_ENCRYPTED
- )
- self.private_key_passphrase = secrets.Secret(
- api=mock.MagicMock(),
- payload=sample.X509_CERT_KEY_PASSPHRASE
+ payload=sample.PKCS12_BUNDLE
)
- container = mock.Mock(spec=containers.CertificateContainer)
- container.container_ref = self.container_ref
- container.name = self.name
- container.private_key = self.private_key
- container.certificate = self.certificate
- container.intermediates = self.intermediates
- container.private_key_passphrase = self.private_key_passphrase
- self.container = container
-
- self.empty_container = mock.Mock(spec=containers.CertificateContainer)
-
- self.secret1 = mock.Mock(spec=secrets.Secret)
- self.secret2 = mock.Mock(spec=secrets.Secret)
- self.secret3 = mock.Mock(spec=secrets.Secret)
- self.secret4 = mock.Mock(spec=secrets.Secret)
+ self.empty_secret = mock.Mock(spec=secrets.Secret)
# Mock out the client
self.bc = mock.Mock()
@@ -82,159 +54,97 @@ class TestBarbicanManager(base.TestCase):
self.cert_manager = barbican_cert_mgr.BarbicanCertManager()
self.cert_manager.auth = barbican_auth
+ self.context = mock.Mock()
+ self.context.project_id = PROJECT_ID
+
super(TestBarbicanManager, self).setUp()
def test_store_cert(self):
# Mock out the client
- self.bc.containers.create_certificate.return_value = (
- self.empty_container)
+ self.bc.secrets.create.return_value = (
+ self.empty_secret)
# Attempt to store a cert
- container_ref = self.cert_manager.store_cert(
- project_id=PROJECT_ID,
- certificate=self.certificate,
- private_key=self.private_key,
- intermediates=self.intermediates,
- private_key_passphrase=self.private_key_passphrase,
+ secret_ref = self.cert_manager.store_cert(
+ context=self.context,
+ certificate=sample.X509_CERT,
+ private_key=sample.X509_CERT_KEY,
+ intermediates=sample.X509_IMDS,
name=self.name
)
- self.assertEqual(self.empty_container.container_ref, container_ref)
+ self.assertEqual(secret_ref, self.empty_secret.secret_ref)
- # create_secret should be called four times with our data
+ # create_secret should be called once with our data
calls = [
- mock.call(payload=self.certificate, expiration=None,
- name=mock.ANY),
- mock.call(payload=self.private_key, expiration=None,
- name=mock.ANY),
- mock.call(payload=self.intermediates, expiration=None,
- name=mock.ANY),
- mock.call(payload=self.private_key_passphrase, expiration=None,
- name=mock.ANY)
+ mock.call(payload=mock.ANY, expiration=None,
+ name=self.name)
]
- self.bc.secrets.create.assert_has_calls(calls, any_order=True)
-
- # create_certificate should be called once
- self.assertEqual(1, self.bc.containers.create_certificate.call_count)
+ self.bc.secrets.create.assert_has_calls(calls)
# Container should be stored once
- self.empty_container.store.assert_called_once_with()
+ self.empty_secret.store.assert_called_once_with()
def test_store_cert_failure(self):
# Mock out the client
- self.bc.containers.create_certificate.return_value = (
- self.empty_container)
- test_secrets = [
- self.secret1,
- self.secret2,
- self.secret3,
- self.secret4
- ]
- self.bc.secrets.create.side_effect = test_secrets
- self.empty_container.store.side_effect = ValueError()
+ self.bc.secrets.create.return_value = (
+ self.empty_secret)
+
+ self.empty_secret.store.side_effect = ValueError()
# Attempt to store a cert
self.assertRaises(
ValueError,
self.cert_manager.store_cert,
- project_id=PROJECT_ID,
- certificate=self.certificate,
- private_key=self.private_key,
- intermediates=self.intermediates,
- private_key_passphrase=self.private_key_passphrase,
+ context=self.context,
+ certificate=sample.X509_CERT,
+ private_key=sample.X509_CERT_KEY,
+ intermediates=sample.X509_IMDS,
name=self.name
)
- # create_secret should be called four times with our data
- calls = [
- mock.call(payload=self.certificate, expiration=None,
- name=mock.ANY),
- mock.call(payload=self.private_key, expiration=None,
- name=mock.ANY),
- mock.call(payload=self.intermediates, expiration=None,
- name=mock.ANY),
- mock.call(payload=self.private_key_passphrase, expiration=None,
- name=mock.ANY)
- ]
- self.bc.secrets.create.assert_has_calls(calls, any_order=True)
-
# create_certificate should be called once
- self.assertEqual(1, self.bc.containers.create_certificate.call_count)
+ self.assertEqual(1, self.bc.secrets.create.call_count)
# Container should be stored once
- self.empty_container.store.assert_called_once_with()
-
- # All secrets should be deleted (or at least an attempt made)
- for s in test_secrets:
- s.delete.assert_called_once_with()
+ self.empty_secret.store.assert_called_once_with()
def test_get_cert(self):
# Mock out the client
- self.bc.containers.register_consumer.return_value = self.container
+ self.bc.secrets.get.return_value = self.secret
- # Get the container data
+ # Get the secret data
data = self.cert_manager.get_cert(
- project_id=PROJECT_ID,
- cert_ref=self.container_ref,
- resource_ref=self.container_ref,
+ context=self.context,
+ cert_ref=self.secret_ref,
+ resource_ref=self.secret_ref,
service_name='Octavia'
)
- # 'register_consumer' should be called once with the container_ref
- self.bc.containers.register_consumer.assert_called_once_with(
- container_ref=self.container_ref,
- url=self.container_ref,
- name='Octavia'
+ # 'get_secret' should be called once with the secret_ref
+ self.bc.secrets.get.assert_called_once_with(
+ secret_ref=self.secret_ref
)
# The returned data should be a Cert object with the correct values
self.assertIsInstance(data, cert.Cert)
- self.assertEqual(data.get_private_key(),
- self.private_key.payload)
- self.assertEqual(data.get_certificate(),
- self.certificate.payload)
- self.assertEqual(data.get_intermediates(),
- sample.X509_IMDS_LIST)
- self.assertEqual(data.get_private_key_passphrase(),
- six.b(self.private_key_passphrase.payload))
+ self.assertEqual(sample.X509_CERT_KEY, data.get_private_key())
+ self.assertEqual(sample.X509_CERT, data.get_certificate())
+ self.assertItemsEqual(sample.X509_IMDS_LIST, data.get_intermediates())
+ self.assertIsNone(data.get_private_key_passphrase())
- def test_get_cert_no_registration(self):
- self.bc.containers.get.return_value = self.container
-
- # Get the container data
- data = self.cert_manager.get_cert(
- project_id=PROJECT_ID,
- cert_ref=self.container_ref, check_only=True
- )
-
- # 'get' should be called once with the container_ref
- self.bc.containers.get.assert_called_once_with(
- container_ref=self.container_ref
- )
-
- # The returned data should be a Cert object with the correct values
- self.assertIsInstance(data, cert.Cert)
- self.assertEqual(data.get_private_key(),
- self.private_key.payload)
- self.assertEqual(data.get_certificate(),
- self.certificate.payload)
- self.assertEqual(data.get_intermediates(),
- sample.X509_IMDS_LIST)
- self.assertEqual(data.get_private_key_passphrase(),
- six.b(self.private_key_passphrase.payload))
-
- def test_delete_cert(self):
+ def test_delete_cert_legacy(self):
# Attempt to deregister as a consumer
self.cert_manager.delete_cert(
- project_id=PROJECT_ID,
- cert_ref=self.container_ref,
- resource_ref=self.container_ref,
+ context=self.context,
+ cert_ref=self.secret_ref,
+ resource_ref=self.secret_ref,
service_name='Octavia'
)
- # remove_consumer should be called once with the container_ref
+ # remove_consumer should be called once with the container_ref (legacy)
self.bc.containers.remove_consumer.assert_called_once_with(
- container_ref=self.container_ref,
- url=self.container_ref,
+ container_ref=self.secret_ref,
+ url=self.secret_ref,
name='Octavia'
)
diff --git a/octavia/tests/unit/certificates/manager/test_barbican_legacy.py b/octavia/tests/unit/certificates/manager/test_barbican_legacy.py
new file mode 100644
index 0000000000..d548794ab6
--- /dev/null
+++ b/octavia/tests/unit/certificates/manager/test_barbican_legacy.py
@@ -0,0 +1,242 @@
+# Copyright 2014 Rackspace
+#
+# 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.
+import uuid
+
+from barbicanclient.v1 import containers
+from barbicanclient.v1 import secrets
+import mock
+import six
+
+import octavia.certificates.common.barbican as barbican_common
+import octavia.certificates.common.cert as cert
+import octavia.certificates.manager.barbican_legacy as barbican_cert_mgr
+import octavia.tests.unit.base as base
+import octavia.tests.unit.common.sample_configs.sample_certs as sample
+
+
+PROJECT_ID = "12345"
+
+
+class TestBarbicanManager(base.TestCase):
+
+ def setUp(self):
+ # Make a fake Container and contents
+ self.barbican_endpoint = 'http://localhost:9311/v1'
+ self.container_uuid = uuid.uuid4()
+
+ self.container_ref = '{0}/containers/{1}'.format(
+ self.barbican_endpoint, self.container_uuid
+ )
+
+ self.name = 'My Fancy Cert'
+ self.certificate = secrets.Secret(
+ api=mock.MagicMock(),
+ payload=sample.X509_CERT
+ )
+ self.intermediates = secrets.Secret(
+ api=mock.MagicMock(),
+ payload=sample.X509_IMDS
+ )
+ self.private_key = secrets.Secret(
+ api=mock.MagicMock(),
+ payload=sample.X509_CERT_KEY_ENCRYPTED
+ )
+ self.private_key_passphrase = secrets.Secret(
+ api=mock.MagicMock(),
+ payload=sample.X509_CERT_KEY_PASSPHRASE
+ )
+
+ container = mock.Mock(spec=containers.CertificateContainer)
+ container.container_ref = self.container_ref
+ container.name = self.name
+ container.private_key = self.private_key
+ container.certificate = self.certificate
+ container.intermediates = self.intermediates
+ container.private_key_passphrase = self.private_key_passphrase
+ self.container = container
+
+ self.empty_container = mock.Mock(spec=containers.CertificateContainer)
+
+ self.secret1 = mock.Mock(spec=secrets.Secret)
+ self.secret2 = mock.Mock(spec=secrets.Secret)
+ self.secret3 = mock.Mock(spec=secrets.Secret)
+ self.secret4 = mock.Mock(spec=secrets.Secret)
+
+ # Mock out the client
+ self.bc = mock.Mock()
+ barbican_auth = mock.Mock(spec=barbican_common.BarbicanAuth)
+ barbican_auth.get_barbican_client.return_value = self.bc
+
+ self.cert_manager = barbican_cert_mgr.BarbicanCertManager()
+ self.cert_manager.auth = barbican_auth
+
+ self.context = mock.Mock()
+ self.context.project_id = PROJECT_ID
+
+ super(TestBarbicanManager, self).setUp()
+
+ def test_store_cert(self):
+ # Mock out the client
+ self.bc.containers.create_certificate.return_value = (
+ self.empty_container)
+
+ # Attempt to store a cert
+ container_ref = self.cert_manager.store_cert(
+ context=self.context,
+ certificate=self.certificate,
+ private_key=self.private_key,
+ intermediates=self.intermediates,
+ private_key_passphrase=self.private_key_passphrase,
+ name=self.name
+ )
+
+ self.assertEqual(self.empty_container.container_ref, container_ref)
+
+ # create_secret should be called four times with our data
+ calls = [
+ mock.call(payload=self.certificate, expiration=None,
+ name=mock.ANY),
+ mock.call(payload=self.private_key, expiration=None,
+ name=mock.ANY),
+ mock.call(payload=self.intermediates, expiration=None,
+ name=mock.ANY),
+ mock.call(payload=self.private_key_passphrase, expiration=None,
+ name=mock.ANY)
+ ]
+ self.bc.secrets.create.assert_has_calls(calls, any_order=True)
+
+ # create_certificate should be called once
+ self.assertEqual(1, self.bc.containers.create_certificate.call_count)
+
+ # Container should be stored once
+ self.empty_container.store.assert_called_once_with()
+
+ def test_store_cert_failure(self):
+ # Mock out the client
+ self.bc.containers.create_certificate.return_value = (
+ self.empty_container)
+ test_secrets = [
+ self.secret1,
+ self.secret2,
+ self.secret3,
+ self.secret4
+ ]
+ self.bc.secrets.create.side_effect = test_secrets
+ self.empty_container.store.side_effect = ValueError()
+
+ # Attempt to store a cert
+ self.assertRaises(
+ ValueError,
+ self.cert_manager.store_cert,
+ context=self.context,
+ certificate=self.certificate,
+ private_key=self.private_key,
+ intermediates=self.intermediates,
+ private_key_passphrase=self.private_key_passphrase,
+ name=self.name
+ )
+
+ # create_secret should be called four times with our data
+ calls = [
+ mock.call(payload=self.certificate, expiration=None,
+ name=mock.ANY),
+ mock.call(payload=self.private_key, expiration=None,
+ name=mock.ANY),
+ mock.call(payload=self.intermediates, expiration=None,
+ name=mock.ANY),
+ mock.call(payload=self.private_key_passphrase, expiration=None,
+ name=mock.ANY)
+ ]
+ self.bc.secrets.create.assert_has_calls(calls, any_order=True)
+
+ # create_certificate should be called once
+ self.assertEqual(1, self.bc.containers.create_certificate.call_count)
+
+ # Container should be stored once
+ self.empty_container.store.assert_called_once_with()
+
+ # All secrets should be deleted (or at least an attempt made)
+ for s in test_secrets:
+ s.delete.assert_called_once_with()
+
+ def test_get_cert(self):
+ # Mock out the client
+ self.bc.containers.register_consumer.return_value = self.container
+
+ # Get the container data
+ data = self.cert_manager.get_cert(
+ context=self.context,
+ cert_ref=self.container_ref,
+ resource_ref=self.container_ref,
+ service_name='Octavia'
+ )
+
+ # 'register_consumer' should be called once with the container_ref
+ self.bc.containers.register_consumer.assert_called_once_with(
+ container_ref=self.container_ref,
+ url=self.container_ref,
+ name='Octavia'
+ )
+
+ # The returned data should be a Cert object with the correct values
+ self.assertIsInstance(data, cert.Cert)
+ self.assertEqual(data.get_private_key(),
+ self.private_key.payload)
+ self.assertEqual(data.get_certificate(),
+ self.certificate.payload)
+ self.assertEqual(data.get_intermediates(),
+ sample.X509_IMDS_LIST)
+ self.assertEqual(data.get_private_key_passphrase(),
+ six.b(self.private_key_passphrase.payload))
+
+ def test_get_cert_no_registration(self):
+ self.bc.containers.get.return_value = self.container
+
+ # Get the container data
+ data = self.cert_manager.get_cert(
+ context=self.context,
+ cert_ref=self.container_ref, check_only=True
+ )
+
+ # 'get' should be called once with the container_ref
+ self.bc.containers.get.assert_called_once_with(
+ container_ref=self.container_ref
+ )
+
+ # The returned data should be a Cert object with the correct values
+ self.assertIsInstance(data, cert.Cert)
+ self.assertEqual(data.get_private_key(),
+ self.private_key.payload)
+ self.assertEqual(data.get_certificate(),
+ self.certificate.payload)
+ self.assertEqual(data.get_intermediates(),
+ sample.X509_IMDS_LIST)
+ self.assertEqual(data.get_private_key_passphrase(),
+ six.b(self.private_key_passphrase.payload))
+
+ def test_delete_cert(self):
+ # Attempt to deregister as a consumer
+ self.cert_manager.delete_cert(
+ context=self.context,
+ cert_ref=self.container_ref,
+ resource_ref=self.container_ref,
+ service_name='Octavia'
+ )
+
+ # remove_consumer should be called once with the container_ref
+ self.bc.containers.remove_consumer.assert_called_once_with(
+ container_ref=self.container_ref,
+ url=self.container_ref,
+ name='Octavia'
+ )
diff --git a/octavia/tests/unit/certificates/manager/test_local.py b/octavia/tests/unit/certificates/manager/test_local.py
index 6c4f049447..d86bb3d0c4 100644
--- a/octavia/tests/unit/certificates/manager/test_local.py
+++ b/octavia/tests/unit/certificates/manager/test_local.py
@@ -44,7 +44,7 @@ class TestLocalManager(base.TestCase):
with mock.patch('os.open', open_mock), mock.patch.object(
os, 'fdopen', fd_mock):
cert_id = local_cert_mgr.LocalCertManager.store_cert(
- None,
+ context=None,
certificate=self.certificate,
intermediates=self.intermediates,
private_key=self.private_key,
diff --git a/octavia/tests/unit/common/sample_configs/sample_certs.py b/octavia/tests/unit/common/sample_configs/sample_certs.py
index 56e6ddf79f..2bc858a650 100644
--- a/octavia/tests/unit/common/sample_configs/sample_certs.py
+++ b/octavia/tests/unit/common/sample_configs/sample_certs.py
@@ -15,6 +15,7 @@
import base64
+import pkg_resources
import six
@@ -813,3 +814,6 @@ zfJ3Bo+P7In9fsHbyDAqIhMwDQYJKoZIhvcNAQELBQADQQBenkZ2k7RgZqgj+dxA
D7BF8MN1oUAOpyYqAjkGddSEuMyNmwtHKZI1dyQ0gBIQdiU9yAG2oTbUIK4msbBV
uJIQ
-----END CERTIFICATE-----"""
+
+PKCS12_BUNDLE = pkg_resources.resource_string(
+ 'octavia.tests.unit.common.sample_configs', 'sample_pkcs12.p12')
diff --git a/octavia/tests/unit/common/sample_configs/sample_pkcs12.p12 b/octavia/tests/unit/common/sample_configs/sample_pkcs12.p12
new file mode 100644
index 0000000000..bb5d006bdc
Binary files /dev/null and b/octavia/tests/unit/common/sample_configs/sample_pkcs12.p12 differ
diff --git a/octavia/tests/unit/common/tls_utils/test_cert_parser.py b/octavia/tests/unit/common/tls_utils/test_cert_parser.py
index f7df02bd80..cf737ca190 100644
--- a/octavia/tests/unit/common/tls_utils/test_cert_parser.py
+++ b/octavia/tests/unit/common/tls_utils/test_cert_parser.py
@@ -134,18 +134,20 @@ class TestTLSParseUtils(base.TestCase):
def test_load_certificates(self):
listener = sample_configs.sample_listener_tuple(tls=True, sni=True)
client = mock.MagicMock()
+ context = mock.Mock()
+ context.project_id = '12345'
with mock.patch.object(cert_parser,
'get_host_names') as cp:
with mock.patch.object(cert_parser,
'_map_cert_tls_container'):
cp.return_value = {'cn': 'fakeCN'}
- cert_parser.load_certificates_data(client, listener)
+ cert_parser.load_certificates_data(client, listener, context)
# Ensure upload_cert is called three times
calls_cert_mngr = [
- mock.call.get_cert('12345', 'cont_id_1', check_only=True),
- mock.call.get_cert('12345', 'cont_id_2', check_only=True),
- mock.call.get_cert('12345', 'cont_id_3', check_only=True)
+ mock.call.get_cert(context, 'cont_id_1', check_only=True),
+ mock.call.get_cert(context, 'cont_id_2', check_only=True),
+ mock.call.get_cert(context, 'cont_id_3', check_only=True)
]
client.assert_has_calls(calls_cert_mngr)
diff --git a/releasenotes/notes/Support-PKCS12-certificate-objects-1c6e896be9d35977.yaml b/releasenotes/notes/Support-PKCS12-certificate-objects-1c6e896be9d35977.yaml
new file mode 100644
index 0000000000..aca42bd879
--- /dev/null
+++ b/releasenotes/notes/Support-PKCS12-certificate-objects-1c6e896be9d35977.yaml
@@ -0,0 +1,21 @@
+---
+features:
+ - |
+ Users can now use a reference to a single PKCS12 bundle as their
+ `default_tls_container_ref` instead of a Barbican container with
+ individual secret objects. PKCS12 supports bundling a private key,
+ certificate, and intermediates. Private keys can no longer be passphrase
+ protected when using PKCS12 bundles.
+ No configuration change is necessary to enable this feature. Users may
+ simply begin using this. Any use of the old style containers will be
+ detected and automatically fall back to using the old Barbican driver.
+ - |
+ Certificate bundles can now be stored in any backend Castellan supports,
+ and can be retrieved via a Castellan driver, even if Barbican is not
+ deployed.
+security:
+ - |
+ Private keys can no longer be password protected, as PKCS12 does not
+ support storing a passphrase in an explicitly defined way. Note that this
+ is not noticeably less secure than storing a passphrase protected private
+ key in the same place as the passphrase, as was the case with Barbican.
diff --git a/requirements.txt b/requirements.txt
index 8950cbd293..5380e1000e 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -38,6 +38,7 @@ Jinja2!=2.9.0,!=2.9.1,!=2.9.2,!=2.9.3,!=2.9.4,>=2.8 # BSD License (3 clause)
taskflow>=2.7.0 # Apache-2.0
diskimage-builder!=1.6.0,!=1.7.0,!=1.7.1,>=1.1.2 # Apache-2.0
futures>=3.0.0;python_version=='2.7' or python_version=='2.6' # BSD
+castellan>=0.16.0 # Apache-2.0
#for the amphora api
Flask!=0.11,<1.0,>=0.10 # BSD
diff --git a/setup.cfg b/setup.cfg
index 0a98244547..8af396c6b8 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -94,6 +94,7 @@ octavia.cert_generator =
octavia.cert_manager =
local_cert_manager = octavia.certificates.manager.local:LocalCertManager
barbican_cert_manager = octavia.certificates.manager.barbican:BarbicanCertManager
+ castellan_cert_manager = octavia.certificates.manager.castellan_mgr:CastellanCertManager
octavia.barbican_auth =
barbican_acl_auth = octavia.certificates.common.auth.barbican_acl:BarbicanACLAuth
octavia.plugins =