Switch to using PKCS12 for TLS Term certs

*NOT* deprecating the old way of storing these, as I believe that would
create a huge mess for anyone already using it.

Change-Id: I1fee174d8b8956f3d2053781a7f18c2940b21765
This commit is contained in:
Adam Harwell 2017-09-14 13:17:37 -06:00 committed by Michael Johnson
parent d7cc05be39
commit 8934a629df
22 changed files with 801 additions and 389 deletions

View File

@ -245,17 +245,21 @@ default_pool_id-optional:
type: string
default_tls_container_ref:
description: |
The URI to the `key manager service
<https://docs.openstack.org/barbican/latest/>`__ secrets container
containing the certificate and key for ``TERMINATED_TLS`` listeners.
The URI of the `key manager service
<https://docs.openstack.org/castellan/latest/>`__ 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
<https://docs.openstack.org/barbican/latest/>`__ secrets container
containing the certificate and key for ``TERMINATED_TLS`` listeners.
The URI of the `key manager service
<https://docs.openstack.org/castellan/latest/>`__ 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
<https://docs.openstack.org/barbican/latest/>`__ secrets containers
containing the certificates and keys for ``TERMINATED_TLS`` the listener
using Server Name Indication.
<https://docs.openstack.org/barbican/latest/>`__ 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
<https://docs.openstack.org/barbican/latest/>`__ secrets containers
containing the certificates and keys for ``TERMINATED_TLS`` the listener
using Server Name Indication.
<https://docs.openstack.org/barbican/latest/>`__ 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

View File

@ -1,5 +1,5 @@
Octavia API v1 (SUPORTED)
=========================
Octavia API v1 (SUPPORTED)
==========================
Authentication
--------------

View File

@ -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
===============

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
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
)
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)
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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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,

View File

@ -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'
)

View File

@ -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'
)

View File

@ -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,

View File

@ -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')

View File

@ -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)

View File

@ -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.

View File

@ -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

View File

@ -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 =