From 74b6f2ff50380ae12fe7a93ea932b9deff71bcf1 Mon Sep 17 00:00:00 2001 From: Michael Johnson Date: Thu, 29 Oct 2020 15:11:39 -0700 Subject: [PATCH] Adds a pool re-encryption scenario test This patch adds a pool re-encryption scenario test that covers TLS enabled pools, pools with CA validation, and pools with certificate revocation lists. Co-Authored-By: Gregory Thiemonge Change-Id: Ib3d8d766b8eb358b48da74f8634f6d24510394b4 --- octavia_tempest_plugin/common/constants.py | 3 + .../contrib/test_server/test_server.go | 7 +- .../services/load_balancer/v2/pool_client.py | 30 +++- .../barbican_scenario/v2/test_tls_barbican.py | 165 ++++++++++++++++++ octavia_tempest_plugin/tests/test_base.py | 26 +++ ...ol-tls-scenario-test-0eb88e731c595b67.yaml | 4 + 6 files changed, 232 insertions(+), 3 deletions(-) create mode 100644 releasenotes/notes/pool-tls-scenario-test-0eb88e731c595b67.yaml diff --git a/octavia_tempest_plugin/common/constants.py b/octavia_tempest_plugin/common/constants.py index 927aa840..d4660827 100644 --- a/octavia_tempest_plugin/common/constants.py +++ b/octavia_tempest_plugin/common/constants.py @@ -21,6 +21,7 @@ AVAILABILITY_ZONE_PROFILE_ID = 'availability_zone_profile_id' ADMIN_STATE_UP = 'admin_state_up' BYTES_IN = 'bytes_in' BYTES_OUT = 'bytes_out' +CA_TLS_CONTAINER_REF = 'ca_tls_container_ref' CLIENT_AUTHENTICATION = 'client_authentication' CLIENT_AUTH_NONE = 'NONE' CLIENT_AUTH_OPTIONAL = 'OPTIONAL' @@ -28,6 +29,7 @@ CLIENT_AUTH_MANDATORY = 'MANDATORY' CLIENT_CA_TLS_CONTAINER_REF = 'client_ca_tls_container_ref' CLIENT_CRL_CONTAINER_REF = 'client_crl_container_ref' CREATED_AT = 'created_at' +CRL_CONTAINER_REF = 'crl_container_ref' DESCRIPTION = 'description' FLAVOR_DATA = 'flavor_data' FLAVOR_ID = 'flavor_id' @@ -63,6 +65,7 @@ TIMEOUT_CLIENT_DATA = 'timeout_client_data' TIMEOUT_MEMBER_CONNECT = 'timeout_member_connect' TIMEOUT_MEMBER_DATA = 'timeout_member_data' TIMEOUT_TCP_INSPECT = 'timeout_tcp_inspect' +TLS_ENABLED = 'tls_enabled' DEFAULT_TLS_CONTAINER_REF = 'default_tls_container_ref' SNI_CONTAINER_REFS = 'sni_container_refs' DEFAULT_POOL_ID = 'default_pool_id' diff --git a/octavia_tempest_plugin/contrib/test_server/test_server.go b/octavia_tempest_plugin/contrib/test_server/test_server.go index fa8f8d75..1671b5c1 100644 --- a/octavia_tempest_plugin/contrib/test_server/test_server.go +++ b/octavia_tempest_plugin/contrib/test_server/test_server.go @@ -171,11 +171,14 @@ func httpsServe(port int, id string, cert tls.Certificate, tls.CurveP256}, PreferServerCipherSuites: true, CipherSuites: []uint16{ + tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, tls.TLS_RSA_WITH_AES_256_GCM_SHA384, tls.TLS_RSA_WITH_AES_256_CBC_SHA, }, + NextProtos: []string{"h2", "http/1.1", "http/1.0"}, } } else { tlsConfig = &tls.Config{ @@ -186,6 +189,8 @@ func httpsServe(port int, id string, cert tls.Certificate, tls.CurveP256}, PreferServerCipherSuites: true, CipherSuites: []uint16{ + tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, tls.TLS_RSA_WITH_AES_256_GCM_SHA384, @@ -200,8 +205,6 @@ func httpsServe(port int, id string, cert tls.Certificate, Addr: portStr, Handler: mux, TLSConfig: tlsConfig, - TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, - http.Handler), 0), } log.Fatal(srv.ListenAndServeTLS(serverCertPem, serverKeyPem)) } diff --git a/octavia_tempest_plugin/services/load_balancer/v2/pool_client.py b/octavia_tempest_plugin/services/load_balancer/v2/pool_client.py index 98f4bfa7..c19d97c3 100644 --- a/octavia_tempest_plugin/services/load_balancer/v2/pool_client.py +++ b/octavia_tempest_plugin/services/load_balancer/v2/pool_client.py @@ -32,6 +32,8 @@ class PoolClient(base_client.BaseLBaaSClient): listener_id=Unset, name=Unset, description=Unset, tags=Unset, admin_state_up=Unset, session_persistence=Unset, + ca_tls_container_ref=Unset, crl_container_ref=Unset, + tls_enabled=Unset, tls_container_ref=Unset, return_object_only=True): """Create a pool. @@ -47,6 +49,18 @@ class PoolClient(base_client.BaseLBaaSClient): :param session_persistence: A JSON object specifying the session persistence for the pool or null for no session persistence. + :param ca_tls_container_ref: The key manager ref for a secret + containing the PEM encoded CA certificate + to validate pool members against. + :param crl_container_ref: The key manager ref for a secret containing + the PEM encoded CRL to use when validating + pool members. + :param tls_enabled: A boolean, True when the pool should connect to + members using TLS. + :param tls_container_ref: The key manager ref for a secret containing + a PKCS12 bundle with the client + authentication certificate and key used + when connecting to pool members over TLS. :param return_object_only: If True, the response returns the object inside the root tag. False returns the full response from the API. @@ -164,7 +178,9 @@ class PoolClient(base_client.BaseLBaaSClient): @skip_if_not_implemented def update_pool(self, pool_id, lb_algorithm=Unset, name=Unset, description=Unset, tags=Unset, admin_state_up=Unset, - session_persistence=Unset, return_object_only=True): + session_persistence=Unset, ca_tls_container_ref=Unset, + crl_container_ref=Unset, tls_enabled=Unset, + tls_container_ref=Unset, return_object_only=True): """Update a pool. :param pool_id: The pool ID to update. @@ -177,6 +193,18 @@ class PoolClient(base_client.BaseLBaaSClient): :param session_persistence: A JSON object specifying the session persistence for the pool or null for no session persistence. + :param ca_tls_container_ref: The key manager ref for a secret + containing the PEM encoded CA certificate + to validate pool members against. + :param crl_container_ref: The key manager ref for a secret containing + the PEM encoded CRL to use when validating + pool members. + :param tls_enabled: A boolean, True when the pool should connect to + members using TLS. + :param tls_container_ref: The key manager ref for a secret containing + a PKCS12 bundle with the client + authentication certificate and key used + when connecting to pool members over TLS. :param return_object_only: If True, the response returns the object inside the root tag. False returns the full response from the API. diff --git a/octavia_tempest_plugin/tests/barbican_scenario/v2/test_tls_barbican.py b/octavia_tempest_plugin/tests/barbican_scenario/v2/test_tls_barbican.py index 843388e5..3f418927 100644 --- a/octavia_tempest_plugin/tests/barbican_scenario/v2/test_tls_barbican.py +++ b/octavia_tempest_plugin/tests/barbican_scenario/v2/test_tls_barbican.py @@ -98,6 +98,23 @@ class TLSWithBarbicanTest(test_base.LoadBalancerBaseTestWithCompute): return new_cert, new_key, new_secret_ref + @classmethod + def _load_pool_pki(cls): + # Create the pkcs12 bundle + pkcs12 = cert_utils.generate_pkcs12_bundle(cls.member_client_cert, + cls.member_client_key) + LOG.debug('Pool client PKCS12 bundle: %s', base64.b64encode(pkcs12)) + + cls.pool_client_ref = cls._store_secret(cls.barbican_mgr, pkcs12) + + cls.pool_CA_ref = cls._store_secret( + cls.barbican_mgr, + cls.member_ca_cert.public_bytes(serialization.Encoding.PEM)) + + cls.pool_CRL_ref = cls._store_secret( + cls.barbican_mgr, + cls.member_crl.public_bytes(serialization.Encoding.PEM)) + @classmethod def resource_setup(cls): """Setup resources needed by the tests.""" @@ -174,6 +191,8 @@ class TLSWithBarbicanTest(test_base.LoadBalancerBaseTestWithCompute): cls.barbican_mgr, cls.client_crl.public_bytes(serialization.Encoding.PEM)) + cls._load_pool_pki() + # Setup a load balancer for the tests to use lb_name = data_utils.rand_name("lb_member_lb1-tls") lb_kwargs = {const.PROVIDER: CONF.load_balancer.provider, @@ -1197,3 +1216,149 @@ class TLSWithBarbicanTest(test_base.LoadBalancerBaseTestWithCompute): def test_http_1_1_tls_traffic(self): self._test_http_versions_tls_traffic( 'HTTP/1.1', ['http/1.1', 'http/1.0']) + + @decorators.idempotent_id('ee0faf71-d11e-4323-8673-e5e15779749b') + def test_pool_reencryption(self): + if not self.mem_listener_client.is_version_supported( + self.api_version, '2.8'): + raise self.skipException('Pool re-encryption is only available on ' + 'Octavia API version 2.8 or newer.') + pool_name = data_utils.rand_name("lb_member_pool1-tls-reencrypt") + pool_kwargs = { + const.NAME: pool_name, + const.PROTOCOL: const.HTTP, + const.LB_ALGORITHM: self.lb_algorithm, + const.LOADBALANCER_ID: self.lb_id, + const.TLS_ENABLED: True + } + pool = self.mem_pool_client.create_pool(**pool_kwargs) + pool_id = pool[const.ID] + + waiters.wait_for_status(self.mem_lb_client.show_loadbalancer, + self.lb_id, const.PROVISIONING_STATUS, + const.ACTIVE, + CONF.load_balancer.build_interval, + CONF.load_balancer.build_timeout) + + # Set up Member 1 for Webserver 1 + member1_name = data_utils.rand_name("lb_member_member1-tls-reencrypt") + member1_kwargs = { + const.POOL_ID: pool_id, + const.NAME: member1_name, + const.ADMIN_STATE_UP: True, + const.ADDRESS: self.webserver1_ip, + const.PROTOCOL_PORT: 443, + } + if self.lb_member_1_subnet: + member1_kwargs[const.SUBNET_ID] = self.lb_member_1_subnet[const.ID] + + self.mem_member_client.create_member(**member1_kwargs) + waiters.wait_for_status( + self.mem_lb_client.show_loadbalancer, self.lb_id, + const.PROVISIONING_STATUS, const.ACTIVE, + CONF.load_balancer.check_interval, + CONF.load_balancer.check_timeout) + + # Set up Member 2 for Webserver 2 + member2_name = data_utils.rand_name("lb_member_member2-tls-reencrypt") + member2_kwargs = { + const.POOL_ID: pool_id, + const.NAME: member2_name, + const.ADMIN_STATE_UP: True, + const.ADDRESS: self.webserver2_ip, + const.PROTOCOL_PORT: 443, + } + if self.lb_member_2_subnet: + member2_kwargs[const.SUBNET_ID] = self.lb_member_2_subnet[const.ID] + + self.mem_member_client.create_member(**member2_kwargs) + waiters.wait_for_status( + self.mem_lb_client.show_loadbalancer, self.lb_id, + const.PROVISIONING_STATUS, const.ACTIVE, + CONF.load_balancer.check_interval, + CONF.load_balancer.check_timeout) + + listener_name = data_utils.rand_name( + "lb_member_listener1-tls-reencrypt") + listener_kwargs = { + const.NAME: listener_name, + const.PROTOCOL: const.HTTP, + const.PROTOCOL_PORT: '84', + const.LOADBALANCER_ID: self.lb_id, + const.DEFAULT_POOL_ID: pool_id, + } + listener = self.mem_listener_client.create_listener(**listener_kwargs) + self.listener_id = listener[const.ID] + + waiters.wait_for_status(self.mem_lb_client.show_loadbalancer, + self.lb_id, const.PROVISIONING_STATUS, + const.ACTIVE, + CONF.load_balancer.build_interval, + CONF.load_balancer.build_timeout) + + # Test with no CA validation + self.check_members_balanced(self.lb_vip_address, protocol=const.HTTP, + protocol_port=84) + + # Test with CA validation - invalid CA + pool_update_kwargs = { + const.CA_TLS_CONTAINER_REF: self.client_ca_cert_ref + } + + self.mem_pool_client.update_pool(pool_id, **pool_update_kwargs) + + waiters.wait_for_status( + self.mem_lb_client.show_loadbalancer, self.lb_id, + const.PROVISIONING_STATUS, const.ACTIVE, + CONF.load_balancer.check_interval, + CONF.load_balancer.check_timeout) + waiters.wait_for_status( + self.mem_pool_client.show_pool, pool_id, + const.PROVISIONING_STATUS, const.ACTIVE, + CONF.load_balancer.check_interval, + CONF.load_balancer.check_timeout) + + url = 'http://{0}:84'.format(self.lb_vip_address) + self.validate_URL_response(url, expected_status_code=503) + + # Test with CA validation - valid CA + pool_update_kwargs = { + const.CA_TLS_CONTAINER_REF: self.pool_CA_ref + } + + self.mem_pool_client.update_pool(pool_id, **pool_update_kwargs) + + waiters.wait_for_status( + self.mem_lb_client.show_loadbalancer, self.lb_id, + const.PROVISIONING_STATUS, const.ACTIVE, + CONF.load_balancer.check_interval, + CONF.load_balancer.check_timeout) + waiters.wait_for_status( + self.mem_pool_client.show_pool, pool_id, + const.PROVISIONING_STATUS, const.ACTIVE, + CONF.load_balancer.check_interval, + CONF.load_balancer.check_timeout) + + self.check_members_balanced(self.lb_vip_address, protocol=const.HTTP, + protocol_port=84) + + # Test with CRL including one webserver certificate revoked + pool_update_kwargs = { + const.CRL_CONTAINER_REF: self.pool_CRL_ref + } + + self.mem_pool_client.update_pool(pool_id, **pool_update_kwargs) + + waiters.wait_for_status( + self.mem_lb_client.show_loadbalancer, self.lb_id, + const.PROVISIONING_STATUS, const.ACTIVE, + CONF.load_balancer.check_interval, + CONF.load_balancer.check_timeout) + waiters.wait_for_status( + self.mem_pool_client.show_pool, pool_id, + const.PROVISIONING_STATUS, const.ACTIVE, + CONF.load_balancer.check_interval, + CONF.load_balancer.check_timeout) + + self.check_members_balanced(self.lb_vip_address, protocol=const.HTTP, + protocol_port=84, traffic_member_count=1) diff --git a/octavia_tempest_plugin/tests/test_base.py b/octavia_tempest_plugin/tests/test_base.py index 887c6446..8a2c3c70 100644 --- a/octavia_tempest_plugin/tests/test_base.py +++ b/octavia_tempest_plugin/tests/test_base.py @@ -567,6 +567,19 @@ class LoadBalancerBaseTestWithCompute(LoadBalancerBaseTest): cls.lb_mem_SGr_client.delete_security_group_rule, cls.lb_mem_SGr_client.show_security_group_rule, SGr['id']) + # Create a security group rule to allow 443 (test webservers) + SGr = cls.lb_mem_SGr_client.create_security_group_rule( + direction='ingress', + security_group_id=cls.lb_member_sec_group['id'], + protocol='tcp', + ethertype='IPv4', + port_range_min=443, + port_range_max=443)['security_group_rule'] + cls.addClassResourceCleanup( + waiters.wait_for_not_found, + cls.lb_mem_SGr_client.delete_security_group_rule, + cls.lb_mem_SGr_client.show_security_group_rule, + SGr['id']) # Create a security group rule to allow UDP 9999 (test webservers) # Port 9999 is used to illustrate health monitor ERRORs on closed # ports. @@ -623,6 +636,19 @@ class LoadBalancerBaseTestWithCompute(LoadBalancerBaseTest): cls.lb_mem_SGr_client.delete_security_group_rule, cls.lb_mem_SGr_client.show_security_group_rule, SGr['id']) + # Create a security group rule to allow 443 (test webservers) + SGr = cls.lb_mem_SGr_client.create_security_group_rule( + direction='ingress', + security_group_id=cls.lb_member_sec_group['id'], + protocol='tcp', + ethertype='IPv6', + port_range_min=443, + port_range_max=443)['security_group_rule'] + cls.addClassResourceCleanup( + waiters.wait_for_not_found, + cls.lb_mem_SGr_client.delete_security_group_rule, + cls.lb_mem_SGr_client.show_security_group_rule, + SGr['id']) # Create a security group rule to allow 22 (ssh) SGr = cls.lb_mem_SGr_client.create_security_group_rule( direction='ingress', diff --git a/releasenotes/notes/pool-tls-scenario-test-0eb88e731c595b67.yaml b/releasenotes/notes/pool-tls-scenario-test-0eb88e731c595b67.yaml new file mode 100644 index 00000000..429874a0 --- /dev/null +++ b/releasenotes/notes/pool-tls-scenario-test-0eb88e731c595b67.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + Added scenario test coverage for pool re-encryption.