Add a TLS scenario using Barbican

This patch adds a TLS load balancer scenario test using Barbican.

Story: 1627383
Task: 5149

Change-Id: I7013888f94261d94e1cd4c3167dc84da7125d1da
diff --git a/octavia_tempest_plugin/common/barbican_client_mgr.py b/octavia_tempest_plugin/common/barbican_client_mgr.py
new file mode 100644
index 0000000..e93f903
--- /dev/null
+++ b/octavia_tempest_plugin/common/barbican_client_mgr.py
@@ -0,0 +1,88 @@
+# Copyright 2019 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.
+
+from barbicanclient import client
+from keystoneauth1 import identity
+from keystoneauth1 import session
+from oslo_log import log as logging
+from tempest.lib.common.utils import data_utils
+
+LOG = logging.getLogger(__name__)
+
+
+class BarbicanClientManager(object):
+    """Class for interacting with the barbican service.
+
+    This class is an abstraction for interacting with the barbican service.
+    This class currently uses the barbican client code to access barbican due
+    to the following reasons:
+    1. Octavia users typically load secrets into barbican via the client.
+    2. The barbican-tempest-plugin is lightly tested (no py3 tests, etc.).
+    3. barbican-tempest-plugin is not in global requirements.
+
+    This led to the decision to not use the service client in the
+    barbican-tempest-plugin.
+
+    In the future it may be better to use the barbican-tempest-plugin
+    service client or the openstacksdk.
+    """
+
+    def __init__(self, tempest_client_mgr):
+        """Setup the barbican client.
+
+        :param tempest_client_mgr: A tempest client manager object, such as
+                                   os_primary.
+        """
+        # Convert the tempest credential passed in into a keystone session
+        auth_provider = tempest_client_mgr.auth_provider
+        cert_validation = False
+        if not auth_provider.dscv:
+            cert_validation = auth_provider.ca_certs
+        credentials = tempest_client_mgr.credentials
+        keystone_auth = identity.v3.Token(
+            auth_url=auth_provider.auth_url,
+            token=auth_provider.get_token(),
+            project_id=credentials.project_id,
+            project_name=credentials.project_name,
+            project_domain_id=credentials.project_domain_id,
+            project_domain_name=credentials.project_domain_name)
+        id_session = session.Session(auth=keystone_auth,
+                                     verify=cert_validation)
+
+        # Setup the barbican client
+        self.barbican = client.Client(session=id_session)
+
+    def store_secret(self, pkcs12_secret):
+        """Store a secret in barbican.
+
+        :param pkcs12_secret: A pkcs12 secret.
+        :returns: The barbican secret_ref.
+        """
+        p12_secret = self.barbican.secrets.create()
+        p12_secret.name = data_utils.rand_name("lb_member_barbican_pkcs12")
+        p12_secret.payload = pkcs12_secret
+        secret_ref = p12_secret.store()
+        LOG.debug('Secret {0} has ref {1}'.format(p12_secret.name, secret_ref))
+        return secret_ref
+
+    def delete_secret(self, secret_ref):
+        self.barbican.secrets.delete(secret_ref)
+
+    def add_acl(self, secret_ref, user_id):
+        acl_entity = self.barbican.acls.create(entity_ref=secret_ref,
+                                               users=[user_id],
+                                               project_access=True)
+        acl_ref = acl_entity.submit()
+        LOG.debug('Secret ACL {0} added user {1}'.format(acl_ref, user_id))
+        return acl_ref
diff --git a/octavia_tempest_plugin/common/cert_utils.py b/octavia_tempest_plugin/common/cert_utils.py
new file mode 100644
index 0000000..dcdd6f0
--- /dev/null
+++ b/octavia_tempest_plugin/common/cert_utils.py
@@ -0,0 +1,130 @@
+# Copyright 2018 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.
+
+import datetime
+
+from cryptography.hazmat.backends import default_backend
+from cryptography.hazmat.primitives.asymmetric import rsa
+from cryptography.hazmat.primitives import hashes
+from cryptography import x509
+from cryptography.x509.oid import NameOID
+import OpenSSL
+
+
+def generate_ca_cert_and_key():
+    """Creates a CA cert and key for testing.
+
+    :returns: The cryptography CA cert and CA key objects.
+    """
+
+    ca_key = rsa.generate_private_key(
+        public_exponent=65537, key_size=2048, backend=default_backend())
+
+    subject = issuer = x509.Name([
+        x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"),
+        x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"Denial"),
+        x509.NameAttribute(NameOID.LOCALITY_NAME, u"Corvallis"),
+        x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"OpenStack"),
+        x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, u"Octavia"),
+        x509.NameAttribute(NameOID.COMMON_NAME, u"ca_cert.example.com"),
+    ])
+
+    ca_cert = x509.CertificateBuilder().subject_name(
+        subject
+    ).issuer_name(
+        issuer
+    ).public_key(
+        ca_key.public_key()
+    ).serial_number(
+        x509.random_serial_number()
+    ).not_valid_before(
+        datetime.datetime.utcnow()
+    ).not_valid_after(
+        datetime.datetime.utcnow() + datetime.timedelta(days=10)
+    ).add_extension(
+        x509.SubjectAlternativeName([x509.DNSName(u"ca_cert.example.com")]),
+        critical=False,
+    ).add_extension(
+        x509.BasicConstraints(ca=True, path_length=None),
+        critical=True,
+    ).sign(ca_key, hashes.SHA256(), default_backend())
+
+    return ca_cert, ca_key
+
+
+def generate_server_cert_and_key(ca_cert, ca_key, server_uuid):
+    """Creates a server cert and key for testing.
+
+    :param ca_cert: A cryptography CA certificate (x509) object.
+    :param ca_key: A cryptography CA key (x509) object.
+    :param server_uuid: A UUID identifying the server.
+    :returns: The cryptography server cert and key objects.
+    """
+
+    server_key = rsa.generate_private_key(
+        public_exponent=65537, key_size=2048, backend=default_backend())
+
+    subject = x509.Name([
+        x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"),
+        x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"Denial"),
+        x509.NameAttribute(NameOID.LOCALITY_NAME, u"Corvallis"),
+        x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"OpenStack"),
+        x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, u"Octavia"),
+        x509.NameAttribute(NameOID.COMMON_NAME, u"{}.example.com".format(
+            server_uuid)),
+    ])
+
+    server_cert = x509.CertificateBuilder().subject_name(
+        subject
+    ).issuer_name(
+        ca_cert.subject
+    ).public_key(
+        server_key.public_key()
+    ).serial_number(
+        x509.random_serial_number()
+    ).not_valid_before(
+        datetime.datetime.utcnow()
+    ).not_valid_after(
+        datetime.datetime.utcnow() + datetime.timedelta(days=10)
+    ).add_extension(
+        x509.SubjectAlternativeName(
+            [x509.DNSName(u"{}.example.com".format(server_uuid))]),
+        critical=False,
+    ).add_extension(
+        x509.BasicConstraints(ca=False, path_length=None),
+        critical=True,
+    ).sign(ca_key, hashes.SHA256(), default_backend())
+
+    return server_cert, server_key
+
+
+def generate_pkcs12_bundle(server_cert, server_key):
+    """Creates a pkcs12 formated bundle.
+
+    Note: This uses pyOpenSSL as the cryptography package does not yet
+          support creating pkcs12 bundles. The currently un-released
+          2.5 version of cryptography supports reading pkcs12, but not
+          creation. This method should be updated to only use
+          cryptography once it supports creating pkcs12 bundles.
+
+    :param server_cert: A cryptography certificate (x509) object.
+    :param server_key: A cryptography key (x509) object.
+    :returns: A pkcs12 bundle.
+    """
+    # TODO(johnsom) Replace with cryptography once it supports creating pkcs12
+    pkcs12 = OpenSSL.crypto.PKCS12()
+    pkcs12.set_privatekey(
+        OpenSSL.crypto.PKey.from_cryptography_key(server_key))
+    pkcs12.set_certificate(OpenSSL.crypto.X509.from_cryptography(server_cert))
+    return pkcs12.export()