Browse Source

Add a TLS scenario using Barbican

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

Story: 1627383
Task: 5149

Change-Id: I7013888f94261d94e1cd4c3167dc84da7125d1da
Michael Johnson 3 months ago
parent
commit
0a0f9b342a

+ 1
- 1
doc/requirements.txt View File

@@ -2,7 +2,7 @@
2 2
 # of appearance. Changing the order has an impact on the overall integration
3 3
 # process, which may cause wedges in the gate later.
4 4
 
5
-sphinxcontrib-apidoc # BSD
5
+sphinxcontrib-apidoc>=0.2.0 # BSD
6 6
 sphinx>=1.6.2,!=1.6.6,!=1.6.7 # BSD
7 7
 openstackdocstheme>=1.18.1 # Apache-2.0
8 8
 

+ 88
- 0
octavia_tempest_plugin/common/barbican_client_mgr.py View File

@@ -0,0 +1,88 @@
1
+# Copyright 2019 Rackspace US Inc.  All rights reserved.
2
+#
3
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
4
+#    not use this file except in compliance with the License. You may obtain
5
+#    a copy of the License at
6
+#
7
+#         http://www.apache.org/licenses/LICENSE-2.0
8
+#
9
+#    Unless required by applicable law or agreed to in writing, software
10
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12
+#    License for the specific language governing permissions and limitations
13
+#    under the License.
14
+
15
+from barbicanclient import client
16
+from keystoneauth1 import identity
17
+from keystoneauth1 import session
18
+from oslo_log import log as logging
19
+from tempest.lib.common.utils import data_utils
20
+
21
+LOG = logging.getLogger(__name__)
22
+
23
+
24
+class BarbicanClientManager(object):
25
+    """Class for interacting with the barbican service.
26
+
27
+    This class is an abstraction for interacting with the barbican service.
28
+    This class currently uses the barbican client code to access barbican due
29
+    to the following reasons:
30
+    1. Octavia users typically load secrets into barbican via the client.
31
+    2. The barbican-tempest-plugin is lightly tested (no py3 tests, etc.).
32
+    3. barbican-tempest-plugin is not in global requirements.
33
+
34
+    This led to the decision to not use the service client in the
35
+    barbican-tempest-plugin.
36
+
37
+    In the future it may be better to use the barbican-tempest-plugin
38
+    service client or the openstacksdk.
39
+    """
40
+
41
+    def __init__(self, tempest_client_mgr):
42
+        """Setup the barbican client.
43
+
44
+        :param tempest_client_mgr: A tempest client manager object, such as
45
+                                   os_primary.
46
+        """
47
+        # Convert the tempest credential passed in into a keystone session
48
+        auth_provider = tempest_client_mgr.auth_provider
49
+        cert_validation = False
50
+        if not auth_provider.dscv:
51
+            cert_validation = auth_provider.ca_certs
52
+        credentials = tempest_client_mgr.credentials
53
+        keystone_auth = identity.v3.Token(
54
+            auth_url=auth_provider.auth_url,
55
+            token=auth_provider.get_token(),
56
+            project_id=credentials.project_id,
57
+            project_name=credentials.project_name,
58
+            project_domain_id=credentials.project_domain_id,
59
+            project_domain_name=credentials.project_domain_name)
60
+        id_session = session.Session(auth=keystone_auth,
61
+                                     verify=cert_validation)
62
+
63
+        # Setup the barbican client
64
+        self.barbican = client.Client(session=id_session)
65
+
66
+    def store_secret(self, pkcs12_secret):
67
+        """Store a secret in barbican.
68
+
69
+        :param pkcs12_secret: A pkcs12 secret.
70
+        :returns: The barbican secret_ref.
71
+        """
72
+        p12_secret = self.barbican.secrets.create()
73
+        p12_secret.name = data_utils.rand_name("lb_member_barbican_pkcs12")
74
+        p12_secret.payload = pkcs12_secret
75
+        secret_ref = p12_secret.store()
76
+        LOG.debug('Secret {0} has ref {1}'.format(p12_secret.name, secret_ref))
77
+        return secret_ref
78
+
79
+    def delete_secret(self, secret_ref):
80
+        self.barbican.secrets.delete(secret_ref)
81
+
82
+    def add_acl(self, secret_ref, user_id):
83
+        acl_entity = self.barbican.acls.create(entity_ref=secret_ref,
84
+                                               users=[user_id],
85
+                                               project_access=True)
86
+        acl_ref = acl_entity.submit()
87
+        LOG.debug('Secret ACL {0} added user {1}'.format(acl_ref, user_id))
88
+        return acl_ref

+ 130
- 0
octavia_tempest_plugin/common/cert_utils.py View File

@@ -0,0 +1,130 @@
1
+# Copyright 2018 Rackspace US Inc.  All rights reserved.
2
+#
3
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
4
+#    not use this file except in compliance with the License. You may obtain
5
+#    a copy of the License at
6
+#
7
+#         http://www.apache.org/licenses/LICENSE-2.0
8
+#
9
+#    Unless required by applicable law or agreed to in writing, software
10
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12
+#    License for the specific language governing permissions and limitations
13
+#    under the License.
14
+
15
+import datetime
16
+
17
+from cryptography.hazmat.backends import default_backend
18
+from cryptography.hazmat.primitives.asymmetric import rsa
19
+from cryptography.hazmat.primitives import hashes
20
+from cryptography import x509
21
+from cryptography.x509.oid import NameOID
22
+import OpenSSL
23
+
24
+
25
+def generate_ca_cert_and_key():
26
+    """Creates a CA cert and key for testing.
27
+
28
+    :returns: The cryptography CA cert and CA key objects.
29
+    """
30
+
31
+    ca_key = rsa.generate_private_key(
32
+        public_exponent=65537, key_size=2048, backend=default_backend())
33
+
34
+    subject = issuer = x509.Name([
35
+        x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"),
36
+        x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"Denial"),
37
+        x509.NameAttribute(NameOID.LOCALITY_NAME, u"Corvallis"),
38
+        x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"OpenStack"),
39
+        x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, u"Octavia"),
40
+        x509.NameAttribute(NameOID.COMMON_NAME, u"ca_cert.example.com"),
41
+    ])
42
+
43
+    ca_cert = x509.CertificateBuilder().subject_name(
44
+        subject
45
+    ).issuer_name(
46
+        issuer
47
+    ).public_key(
48
+        ca_key.public_key()
49
+    ).serial_number(
50
+        x509.random_serial_number()
51
+    ).not_valid_before(
52
+        datetime.datetime.utcnow()
53
+    ).not_valid_after(
54
+        datetime.datetime.utcnow() + datetime.timedelta(days=10)
55
+    ).add_extension(
56
+        x509.SubjectAlternativeName([x509.DNSName(u"ca_cert.example.com")]),
57
+        critical=False,
58
+    ).add_extension(
59
+        x509.BasicConstraints(ca=True, path_length=None),
60
+        critical=True,
61
+    ).sign(ca_key, hashes.SHA256(), default_backend())
62
+
63
+    return ca_cert, ca_key
64
+
65
+
66
+def generate_server_cert_and_key(ca_cert, ca_key, server_uuid):
67
+    """Creates a server cert and key for testing.
68
+
69
+    :param ca_cert: A cryptography CA certificate (x509) object.
70
+    :param ca_key: A cryptography CA key (x509) object.
71
+    :param server_uuid: A UUID identifying the server.
72
+    :returns: The cryptography server cert and key objects.
73
+    """
74
+
75
+    server_key = rsa.generate_private_key(
76
+        public_exponent=65537, key_size=2048, backend=default_backend())
77
+
78
+    subject = x509.Name([
79
+        x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"),
80
+        x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"Denial"),
81
+        x509.NameAttribute(NameOID.LOCALITY_NAME, u"Corvallis"),
82
+        x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"OpenStack"),
83
+        x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, u"Octavia"),
84
+        x509.NameAttribute(NameOID.COMMON_NAME, u"{}.example.com".format(
85
+            server_uuid)),
86
+    ])
87
+
88
+    server_cert = x509.CertificateBuilder().subject_name(
89
+        subject
90
+    ).issuer_name(
91
+        ca_cert.subject
92
+    ).public_key(
93
+        server_key.public_key()
94
+    ).serial_number(
95
+        x509.random_serial_number()
96
+    ).not_valid_before(
97
+        datetime.datetime.utcnow()
98
+    ).not_valid_after(
99
+        datetime.datetime.utcnow() + datetime.timedelta(days=10)
100
+    ).add_extension(
101
+        x509.SubjectAlternativeName(
102
+            [x509.DNSName(u"{}.example.com".format(server_uuid))]),
103
+        critical=False,
104
+    ).add_extension(
105
+        x509.BasicConstraints(ca=False, path_length=None),
106
+        critical=True,
107
+    ).sign(ca_key, hashes.SHA256(), default_backend())
108
+
109
+    return server_cert, server_key
110
+
111
+
112
+def generate_pkcs12_bundle(server_cert, server_key):
113
+    """Creates a pkcs12 formated bundle.
114
+
115
+    Note: This uses pyOpenSSL as the cryptography package does not yet
116
+          support creating pkcs12 bundles. The currently un-released
117
+          2.5 version of cryptography supports reading pkcs12, but not
118
+          creation. This method should be updated to only use
119
+          cryptography once it supports creating pkcs12 bundles.
120
+
121
+    :param server_cert: A cryptography certificate (x509) object.
122
+    :param server_key: A cryptography key (x509) object.
123
+    :returns: A pkcs12 bundle.
124
+    """
125
+    # TODO(johnsom) Replace with cryptography once it supports creating pkcs12
126
+    pkcs12 = OpenSSL.crypto.PKCS12()
127
+    pkcs12.set_privatekey(
128
+        OpenSSL.crypto.PKey.from_cryptography_key(server_key))
129
+    pkcs12.set_certificate(OpenSSL.crypto.X509.from_cryptography(server_cert))
130
+    return pkcs12.export()

+ 24
- 0
octavia_tempest_plugin/config.py View File

@@ -14,9 +14,13 @@
14 14
 
15 15
 
16 16
 from oslo_config import cfg
17
+from oslo_log import log as logging
17 18
 
18 19
 from octavia_tempest_plugin.common import constants as const
19 20
 
21
+
22
+LOG = logging.getLogger(__name__)
23
+
20 24
 service_available_group = cfg.OptGroup(name='service_available',
21 25
                                        title='Available OpenStack Services')
22 26
 
@@ -27,6 +31,19 @@ ServiceAvailableGroup = [
27 31
                      "to be available."),
28 32
 ]
29 33
 
34
+# Pull in the service_available for barbican if it is not defined.
35
+# If the barbican tempest plugin isn't loaded, this won't load from
36
+# tempest.conf.
37
+try:
38
+    if cfg.CONF.service_available.barbican is not None:
39
+        LOG.info('Barbican service_available state: {}'.format(
40
+            cfg.CONF.service_available.barbican))
41
+except cfg.NoSuchOptError:
42
+    ServiceAvailableGroup.append(
43
+        cfg.BoolOpt('barbican', default=False,
44
+                    help="Whether or not the barbican service is expected to "
45
+                         "be available."))
46
+
30 47
 octavia_group = cfg.OptGroup(name='load_balancer',
31 48
                              title='load-balancer service options')
32 49
 
@@ -54,6 +71,9 @@ OctaviaGroup = [
54 71
                default=300,
55 72
                help='Timeout in seconds to wait for non-load-balancer '
56 73
                     'resources to build'),
74
+    cfg.StrOpt('octavia_svc_username', default='admin',
75
+               help='The service_auth username the Octavia services are using'
76
+                    'to access other OpenStack services.'),
57 77
     # load-balancer specific options
58 78
     cfg.IntOpt('check_interval',
59 79
                default=5,
@@ -152,4 +172,8 @@ LBFeatureEnabledGroup = [
152 172
                 default=True,
153 173
                 help="Whether Health Monitor is available with provider"
154 174
                      " driver or not."),
175
+    cfg.BoolOpt('terminated_tls_enabled',
176
+                default=True,
177
+                help="Whether TLS termination is available with provider "
178
+                     "driver or not."),
155 179
 ]

+ 0
- 0
octavia_tempest_plugin/tests/barbican_scenario/__init__.py View File


+ 0
- 0
octavia_tempest_plugin/tests/barbican_scenario/v2/__init__.py View File


+ 274
- 0
octavia_tempest_plugin/tests/barbican_scenario/v2/test_tls_barbican.py View File

@@ -0,0 +1,274 @@
1
+# Copyright 2019 Rackspace US Inc.  All rights reserved.
2
+#
3
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
4
+#    not use this file except in compliance with the License. You may obtain
5
+#    a copy of the License at
6
+#
7
+#         http://www.apache.org/licenses/LICENSE-2.0
8
+#
9
+#    Unless required by applicable law or agreed to in writing, software
10
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12
+#    License for the specific language governing permissions and limitations
13
+#    under the License.
14
+
15
+import base64
16
+import socket
17
+
18
+from cryptography.hazmat.primitives import serialization
19
+from OpenSSL.crypto import X509
20
+from OpenSSL import SSL
21
+
22
+from oslo_log import log as logging
23
+from oslo_utils import uuidutils
24
+from tempest import config
25
+from tempest.lib.common.utils import data_utils
26
+from tempest.lib import decorators
27
+
28
+from octavia_lib.common import constants as lib_consts
29
+
30
+from octavia_tempest_plugin.common import barbican_client_mgr
31
+from octavia_tempest_plugin.common import cert_utils
32
+from octavia_tempest_plugin.common import constants as const
33
+from octavia_tempest_plugin.tests import test_base
34
+from octavia_tempest_plugin.tests import waiters
35
+
36
+CONF = config.CONF
37
+LOG = logging.getLogger(__name__)
38
+
39
+
40
+class TLSWithBarbicanTest(test_base.LoadBalancerBaseTestWithCompute):
41
+
42
+    @classmethod
43
+    def skip_checks(cls):
44
+        super(TLSWithBarbicanTest, cls).skip_checks()
45
+        if not CONF.loadbalancer_feature_enabled.terminated_tls_enabled:
46
+            raise cls.skipException('[loadbalancer-feature-enabled] '
47
+                                    '"terminated_tls_enabled" is False in '
48
+                                    'the tempest configuration. TLS tests '
49
+                                    'will be skipped.')
50
+        if not CONF.validation.run_validation:
51
+            raise cls.skipException('Traffic tests will not work without '
52
+                                    'run_validation enabled.')
53
+        if not CONF.service_available.barbican:
54
+            raise cls.skipException('TLS with Barbican tests require the '
55
+                                    'barbican service.')
56
+
57
+    @classmethod
58
+    def resource_setup(cls):
59
+        """Setup resources needed by the tests."""
60
+        super(TLSWithBarbicanTest, cls).resource_setup()
61
+
62
+        # Create a CA self-signed cert and key
63
+        cls.ca_cert, ca_key = cert_utils.generate_ca_cert_and_key()
64
+
65
+        LOG.debug('CA Cert: %s' % cls.ca_cert.public_bytes(
66
+            serialization.Encoding.PEM))
67
+        LOG.debug('CA private Key: %s' % ca_key.private_bytes(
68
+            encoding=serialization.Encoding.PEM,
69
+            format=serialization.PrivateFormat.TraditionalOpenSSL,
70
+            encryption_algorithm=serialization.NoEncryption()))
71
+        LOG.debug('CA public Key: %s' % ca_key.public_key().public_bytes(
72
+            encoding=serialization.Encoding.PEM,
73
+            format=serialization.PublicFormat.SubjectPublicKeyInfo))
74
+
75
+        # Create a server cert and key
76
+        cls.server_uuid = uuidutils.generate_uuid()
77
+        server_cert, server_key = cert_utils.generate_server_cert_and_key(
78
+            cls.ca_cert, ca_key, cls.server_uuid)
79
+
80
+        LOG.debug('Server Cert: %s' % server_cert.public_bytes(
81
+            serialization.Encoding.PEM))
82
+        LOG.debug('Server private Key: %s' % server_key.private_bytes(
83
+            encoding=serialization.Encoding.PEM,
84
+            format=serialization.PrivateFormat.TraditionalOpenSSL,
85
+            encryption_algorithm=serialization.NoEncryption()))
86
+        server_public_key = server_key.public_key()
87
+        LOG.debug('Server public Key: %s' % server_public_key.public_bytes(
88
+            encoding=serialization.Encoding.PEM,
89
+            format=serialization.PublicFormat.SubjectPublicKeyInfo))
90
+
91
+        # Create the pkcs12 bundle
92
+        pkcs12 = cert_utils.generate_pkcs12_bundle(server_cert, server_key)
93
+        LOG.debug('Server PKCS12 bundle: %s' % base64.b64encode(pkcs12))
94
+
95
+        # Load the secret into the barbican service under the
96
+        # os_roles_lb_member tenant
97
+        barbican_mgr = barbican_client_mgr.BarbicanClientManager(
98
+            cls.os_roles_lb_member)
99
+
100
+        cls.secret_ref = barbican_mgr.store_secret(pkcs12)
101
+        cls.addClassResourceCleanup(barbican_mgr.delete_secret, cls.secret_ref)
102
+
103
+        # Set the barbican ACL if the Octavia API version doesn't do it
104
+        # automatically.
105
+        if not cls.mem_lb_client.is_version_supported(
106
+                cls.api_version, '2.1'):
107
+            user_list = cls.os_admin.users_v3_client.list_users(
108
+                name=CONF.load_balancer.octavia_svc_username)
109
+            msg = 'Only one user named "{0}" should exist, {1} found.'.format(
110
+                CONF.load_balancer.octavia_svc_username,
111
+                len(user_list['users']))
112
+            assert 1 == len(user_list['users']), msg
113
+            barbican_mgr.add_acl(cls.secret_ref, user_list['users'][0]['id'])
114
+
115
+        # Setup a load balancer for the tests to use
116
+        lb_name = data_utils.rand_name("lb_member_lb1-tls")
117
+        lb_kwargs = {const.PROVIDER: CONF.load_balancer.provider,
118
+                     const.NAME: lb_name}
119
+
120
+        # TODO(johnsom) Update for IPv6
121
+        cls._setup_lb_network_kwargs(lb_kwargs, 4)
122
+
123
+        lb = cls.mem_lb_client.create_loadbalancer(**lb_kwargs)
124
+        cls.lb_id = lb[const.ID]
125
+        cls.addClassResourceCleanup(
126
+            cls.mem_lb_client.cleanup_loadbalancer,
127
+            cls.lb_id)
128
+
129
+        waiters.wait_for_status(cls.mem_lb_client.show_loadbalancer,
130
+                                cls.lb_id, const.PROVISIONING_STATUS,
131
+                                const.ACTIVE,
132
+                                CONF.load_balancer.lb_build_interval,
133
+                                CONF.load_balancer.lb_build_timeout)
134
+
135
+        if CONF.validation.connect_method == 'floating':
136
+            port_id = lb[const.VIP_PORT_ID]
137
+            result = cls.lb_mem_float_ip_client.create_floatingip(
138
+                floating_network_id=CONF.network.public_network_id,
139
+                port_id=port_id)
140
+            floating_ip = result['floatingip']
141
+            LOG.info('lb1_floating_ip: {}'.format(floating_ip))
142
+            cls.addClassResourceCleanup(
143
+                waiters.wait_for_not_found,
144
+                cls.lb_mem_float_ip_client.delete_floatingip,
145
+                cls.lb_mem_float_ip_client.show_floatingip,
146
+                floatingip_id=floating_ip['id'])
147
+            cls.lb_vip_address = floating_ip['floating_ip_address']
148
+        else:
149
+            cls.lb_vip_address = lb[const.VIP_ADDRESS]
150
+
151
+        pool_name = data_utils.rand_name("lb_member_pool1-tls")
152
+        pool_kwargs = {
153
+            const.NAME: pool_name,
154
+            const.PROTOCOL: const.HTTP,
155
+            const.LB_ALGORITHM: const.LB_ALGORITHM_ROUND_ROBIN,
156
+            const.LOADBALANCER_ID: cls.lb_id,
157
+        }
158
+        pool = cls.mem_pool_client.create_pool(**pool_kwargs)
159
+        cls.pool_id = pool[const.ID]
160
+        cls.addClassResourceCleanup(
161
+            cls.mem_pool_client.cleanup_pool,
162
+            cls.pool_id,
163
+            lb_client=cls.mem_lb_client, lb_id=cls.lb_id)
164
+
165
+        waiters.wait_for_status(cls.mem_lb_client.show_loadbalancer,
166
+                                cls.lb_id, const.PROVISIONING_STATUS,
167
+                                const.ACTIVE,
168
+                                CONF.load_balancer.build_interval,
169
+                                CONF.load_balancer.build_timeout)
170
+
171
+        # Set up Member 1 for Webserver 1
172
+        member1_name = data_utils.rand_name("lb_member_member1-tls")
173
+        member1_kwargs = {
174
+            const.POOL_ID: cls.pool_id,
175
+            const.NAME: member1_name,
176
+            const.ADMIN_STATE_UP: True,
177
+            const.ADDRESS: cls.webserver1_ip,
178
+            const.PROTOCOL_PORT: 80,
179
+        }
180
+        if cls.lb_member_1_subnet:
181
+            member1_kwargs[const.SUBNET_ID] = cls.lb_member_1_subnet[const.ID]
182
+
183
+        member1 = cls.mem_member_client.create_member(
184
+            **member1_kwargs)
185
+        cls.addClassResourceCleanup(
186
+            cls.mem_member_client.cleanup_member,
187
+            member1[const.ID], pool_id=cls.pool_id,
188
+            lb_client=cls.mem_lb_client, lb_id=cls.lb_id)
189
+        waiters.wait_for_status(
190
+            cls.mem_lb_client.show_loadbalancer, cls.lb_id,
191
+            const.PROVISIONING_STATUS, const.ACTIVE,
192
+            CONF.load_balancer.check_interval,
193
+            CONF.load_balancer.check_timeout)
194
+
195
+        # Set up Member 2 for Webserver 2
196
+        member2_name = data_utils.rand_name("lb_member_member2-tls")
197
+        member2_kwargs = {
198
+            const.POOL_ID: cls.pool_id,
199
+            const.NAME: member2_name,
200
+            const.ADMIN_STATE_UP: True,
201
+            const.ADDRESS: cls.webserver2_ip,
202
+            const.PROTOCOL_PORT: 80,
203
+        }
204
+        if cls.lb_member_2_subnet:
205
+            member2_kwargs[const.SUBNET_ID] = cls.lb_member_2_subnet[const.ID]
206
+
207
+        member2 = cls.mem_member_client.create_member(
208
+            **member2_kwargs)
209
+        cls.addClassResourceCleanup(
210
+            cls.mem_member_client.cleanup_member,
211
+            member2[const.ID], pool_id=cls.pool_id,
212
+            lb_client=cls.mem_lb_client, lb_id=cls.lb_id)
213
+        waiters.wait_for_status(
214
+            cls.mem_lb_client.show_loadbalancer, cls.lb_id,
215
+            const.PROVISIONING_STATUS, const.ACTIVE,
216
+            CONF.load_balancer.check_interval,
217
+            CONF.load_balancer.check_timeout)
218
+
219
+    @decorators.idempotent_id('887ece26-0f7b-4933-89ab-5bb00b106ee0')
220
+    def test_basic_tls_traffic(self):
221
+
222
+        listener_name = data_utils.rand_name("lb_member_listener1-tls")
223
+        listener_kwargs = {
224
+            const.NAME: listener_name,
225
+            const.PROTOCOL: lib_consts.PROTOCOL_TERMINATED_HTTPS,
226
+            const.PROTOCOL_PORT: '443',
227
+            const.LOADBALANCER_ID: self.lb_id,
228
+            const.DEFAULT_POOL_ID: self.pool_id,
229
+            const.DEFAULT_TLS_CONTAINER_REF: self.secret_ref,
230
+        }
231
+        listener = self.mem_listener_client.create_listener(**listener_kwargs)
232
+        self.listener_id = listener[const.ID]
233
+        self.addCleanup(
234
+            self.mem_listener_client.cleanup_listener,
235
+            self.listener_id,
236
+            lb_client=self.mem_lb_client, lb_id=self.lb_id)
237
+
238
+        waiters.wait_for_status(self.mem_lb_client.show_loadbalancer,
239
+                                self.lb_id, const.PROVISIONING_STATUS,
240
+                                const.ACTIVE,
241
+                                CONF.load_balancer.build_interval,
242
+                                CONF.load_balancer.build_timeout)
243
+
244
+        # Test HTTPS listener load balancing.
245
+        # Note: certificate validation tests will follow this test
246
+        self.check_members_balanced(self.lb_vip_address, protocol='https',
247
+                                    verify=False)
248
+
249
+        def _verify_cb(connection, x509, errno, errdepth, retcode):
250
+            """Callback for certificate validation."""
251
+            # don't validate names of root certificates
252
+            if errdepth != 0:
253
+                return True
254
+            if errno == 0:
255
+                # Make sure the certificate is the one we generated
256
+                self.assertEqual('{}.example.com'.format(self.server_uuid),
257
+                                 x509.get_subject().commonName)
258
+            else:
259
+                LOG.error('Certificate with CN: {0} failed validation with '
260
+                          'OpenSSL verify errno {1}'.format(
261
+                              x509.get_subject().commonName, errno))
262
+                return False
263
+            return True
264
+
265
+        context = SSL.Context(SSL.SSLv23_METHOD)
266
+        context.set_verify(SSL.VERIFY_PEER | SSL.VERIFY_FAIL_IF_NO_PEER_CERT,
267
+                           _verify_cb)
268
+        ca_store = context.get_cert_store()
269
+        ca_store.add_cert(X509.from_cryptography(self.ca_cert))
270
+        sock = socket.socket()
271
+        sock = SSL.Connection(context, sock)
272
+        sock.connect((self.lb_vip_address, 443))
273
+        # Validate the certificate is signed by the ca_cert we created
274
+        sock.do_handshake()

+ 9
- 6
octavia_tempest_plugin/tests/test_base.py View File

@@ -766,13 +766,15 @@ class LoadBalancerBaseTestWithCompute(LoadBalancerBaseTest):
766 766
         URL = 'http://{0}:81'.format(ip_address)
767 767
         validators.validate_URL_response(URL, expected_body=str(start_id + 1))
768 768
 
769
-    def _wait_for_lb_functional(self, vip_address):
769
+    def _wait_for_lb_functional(self, vip_address,
770
+                                protocol='http', verify=True):
770 771
         session = requests.Session()
771 772
         start = time.time()
772 773
 
773 774
         while time.time() - start < CONF.load_balancer.build_timeout:
774 775
             try:
775
-                session.get("http://{0}".format(vip_address), timeout=2)
776
+                session.get("{0}://{1}".format(protocol, vip_address),
777
+                            timeout=2, verify=verify)
776 778
                 time.sleep(1)
777 779
                 return
778 780
             except Exception:
@@ -782,20 +784,21 @@ class LoadBalancerBaseTestWithCompute(LoadBalancerBaseTest):
782 784
                   'period. Failing test.')
783 785
         raise Exception()
784 786
 
785
-    def check_members_balanced(self, vip_address, traffic_member_count=2):
787
+    def check_members_balanced(self, vip_address, traffic_member_count=2,
788
+                               protocol='http', verify=True):
786 789
         session = requests.Session()
787 790
         response_counts = {}
788 791
 
789 792
         if ipaddress.ip_address(vip_address).version == 6:
790 793
             vip_address = '[{}]'.format(vip_address)
791 794
 
792
-        self._wait_for_lb_functional(vip_address)
795
+        self._wait_for_lb_functional(vip_address, protocol, verify)
793 796
 
794 797
         # Send a number requests to lb vip
795 798
         for i in range(20):
796 799
             try:
797
-                r = session.get('http://{0}'.format(vip_address),
798
-                                timeout=2)
800
+                r = session.get('{0}://{1}'.format(protocol, vip_address),
801
+                                timeout=2, verify=verify)
799 802
 
800 803
                 if r.content in response_counts:
801 804
                     response_counts[r.content] += 1

+ 4
- 0
requirements.txt View File

@@ -2,13 +2,17 @@
2 2
 # of appearance. Changing the order has an impact on the overall integration
3 3
 # process, which may cause wedges in the gate later.
4 4
 
5
+cryptography>=2.1 # BSD/Apache-2.0
5 6
 python-dateutil>=2.5.3 # BSD
6 7
 ipaddress>=1.0.17;python_version<'3.3' # PSF
7 8
 pbr!=2.1.0,>=2.0.0 # Apache-2.0
9
+octavia-lib>=1.0.0 # Apache-2.0
8 10
 oslo.config>=5.2.0 # Apache-2.0
9 11
 oslo.log>=3.36.0  # Apache-2.0
10 12
 oslo.utils>=3.33.0  # Apache-2.0
11 13
 oslotest>=3.2.0 # Apache-2.0
14
+python-barbicanclient>=4.5.2 # Apache-2.0
15
+pyOpenSSL>=17.1.0 # Apache-2.0
12 16
 requests>=2.14.2  # Apache-2.0
13 17
 six>=1.10.0 # MIT
14 18
 tempest>=17.1.0 # Apache-2.0

+ 15
- 8
zuul.d/jobs.yaml View File

@@ -87,17 +87,13 @@
87 87
     parent: octavia-dsvm-base
88 88
     timeout: 9000
89 89
     required-projects:
90
-      - openstack/barbican
91 90
       - openstack/diskimage-builder
92
-      - openstack/python-barbicanclient
93 91
     vars:
94 92
       devstack_localrc:
95 93
         DIB_LOCAL_ELEMENTS: openstack-ci-mirrors
96 94
       devstack_services:
97
-        barbican: true
98 95
         neutron-qos: true
99 96
       devstack_plugins:
100
-        barbican: https://git.openstack.org/openstack/barbican.git
101 97
         neutron: https://git.openstack.org/openstack/neutron.git
102 98
       zuul_copy_output:
103 99
         '/var/log/dib-build' : logs
@@ -108,9 +104,7 @@
108 104
     nodeset: octavia-two-node
109 105
     timeout: 9000
110 106
     required-projects:
111
-      - openstack/barbican
112 107
       - openstack/diskimage-builder
113
-      - openstack/python-barbicanclient
114 108
     host-vars:
115 109
       controller:
116 110
         devstack_localrc:
@@ -143,7 +137,6 @@
143 137
           OCTAVIA_NODES: "main:{{ hostvars['controller']['nodepool']['private_ipv4'] }},second:{{ hostvars['controller2']['nodepool']['private_ipv4'] }}"
144 138
           OCTAVIA_USE_PREGENERATED_CERTS: true
145 139
         devstack_plugins:
146
-          barbican: https://git.openstack.org/openstack/barbican.git
147 140
           neutron: https://git.openstack.org/openstack/neutron.git
148 141
           octavia: https://git.openstack.org/openstack/octavia.git
149 142
       controller2:
@@ -191,7 +184,7 @@
191 184
                 api_v1_enabled: False
192 185
         devstack_services:
193 186
           base: false
194
-          barbican: true
187
+          barbican: false
195 188
           dstat: true
196 189
           g-api: true
197 190
           g-reg: true
@@ -362,6 +355,20 @@
362 355
         OCTAVIA_AMP_BASE_OS: ubuntu
363 356
         OCTAVIA_AMP_DISTRIBUTION_RELEASE_ID: bionic
364 357
 
358
+- job:
359
+    name: octavia-v2-dsvm-tls-barbican
360
+    parent: octavia-v2-dsvm-scenario
361
+    required-projects:
362
+      - openstack/barbican
363
+      - openstack/diskimage-builder
364
+      - openstack/python-barbicanclient
365
+    vars:
366
+      tempest_test_regex: ^octavia_tempest_plugin.tests.barbican_scenario.v2
367
+      devstack_services:
368
+        barbican: true
369
+      devstack_plugins:
370
+        barbican: https://git.openstack.org/openstack/barbican.git
371
+
365 372
 # Temporary transitional aliases for gates used in other repos
366 373
 # Remove once octavia has transitioned job names
367 374
 - job:

+ 2
- 0
zuul.d/projects.yaml View File

@@ -23,6 +23,8 @@
23 23
             voting: false
24 24
         - octavia-v2-dsvm-py2-scenario-two-node:
25 25
             voting: false
26
+        - octavia-v2-dsvm-tls-barbican:
27
+            voting: false
26 28
     gate:
27 29
       queue: octavia
28 30
       jobs:

Loading…
Cancel
Save