From 45494462956430bf1daf5af523141afb1780ca5d Mon Sep 17 00:00:00 2001 From: Qin Zhao Date: Fri, 11 Dec 2020 15:53:32 +0800 Subject: [PATCH 1/2] Support client authentication --- .../lbaasv2/drivers/bigip/listener_service.py | 26 ++++++++++++++++--- .../lbaasv2/drivers/bigip/service_adapter.py | 17 ++++++------ .../lbaasv2/drivers/bigip/ssl_profile.py | 20 ++++++++++++++ .../bigip/test/test_listener_services.py | 4 +-- .../drivers/bigip/test/test_ssl_profile.py | 18 +++++++++++++ 5 files changed, 71 insertions(+), 14 deletions(-) diff --git a/f5_openstack_agent/lbaasv2/drivers/bigip/listener_service.py b/f5_openstack_agent/lbaasv2/drivers/bigip/listener_service.py index 2887a6f63..bac60825a 100644 --- a/f5_openstack_agent/lbaasv2/drivers/bigip/listener_service.py +++ b/f5_openstack_agent/lbaasv2/drivers/bigip/listener_service.py @@ -179,21 +179,35 @@ def add_ssl_profile(self, tls, vip, bigip): if "default_tls_container_id" in tls: container_ref = tls["default_tls_container_id"] self._create_ssl_profile( - container_ref, bigip, vip, True) + container_ref, bigip, vip, True, + client_auth=tls.get("mutual_authentication_up", False), + ca_container_id=tls.get("ca_container_id", None)) if "sni_containers" in tls and tls["sni_containers"]: for container in tls["sni_containers"]: container_ref = container["tls_container_id"] - self._create_ssl_profile(container_ref, bigip, vip, False) + self._create_ssl_profile( + container_ref, bigip, vip, False, + client_auth=tls.get("mutual_authentication_up", False), + ca_container_id=tls.get("ca_container_id", None)) - def _create_ssl_profile( - self, container_ref, bigip, vip, sni_default=False): + def _create_ssl_profile(self, container_ref, bigip, vip, + sni_default=False, **kwargs): cert = self.cert_manager.get_certificate(container_ref) intermediates = self.cert_manager.get_intermediates(container_ref) key = self.cert_manager.get_private_key(container_ref) key_passphrase = self.cert_manager.get_private_key_passphrase( container_ref) + client_auth = kwargs.get("client_auth", False) + c_ca_cref = kwargs.get("ca_container_id", None) + c_ca_cert = None + c_ca_file = None + if c_ca_cref: + c_ca_cert = self.cert_manager.get_certificate(c_ca_cref) + i = c_ca_cref.rindex("/") + 1 + c_ca_file = self.service_adapter.prefix + c_ca_cref[i:] + ".crt" + chain = None if intermediates: chain = '\n'.join(list(intermediates)) @@ -208,6 +222,9 @@ def _create_ssl_profile( bigip, name, cert, key, key_passphrase=key_passphrase, sni_default=sni_default, intermediates=chain, parent_profile=self.parent_ssl_profile, + client_auth=client_auth, + client_ca_cert=c_ca_cert, + ca_cert_filename=c_ca_file, profile_name=profile_name) except HTTPError as err: if err.response.status_code != 409: @@ -218,6 +235,7 @@ def _create_ssl_profile( del cert del chain del key + del c_ca_cert # add ssl profile to virtual server if 'profiles' not in vip: diff --git a/f5_openstack_agent/lbaasv2/drivers/bigip/service_adapter.py b/f5_openstack_agent/lbaasv2/drivers/bigip/service_adapter.py index a2d2c9f22..448745e51 100644 --- a/f5_openstack_agent/lbaasv2/drivers/bigip/service_adapter.py +++ b/f5_openstack_agent/lbaasv2/drivers/bigip/service_adapter.py @@ -746,14 +746,15 @@ def get_tls(self, service): tls = {} listener = service['listener'] if listener['protocol'] == 'TERMINATED_HTTPS': - if 'default_tls_container_id' in listener and \ - listener['default_tls_container_id']: - tls['default_tls_container_id'] = \ - listener['default_tls_container_id'] - - if 'sni_containers' in listener and listener['sni_containers']: - tls['sni_containers'] = listener['sni_containers'] - + for tls_key in [ + 'default_tls_container_id', + 'sni_containers', + # China mobile client auth parameters + 'mutual_authentication_up', + 'ca_container_id' + ]: + if tls_key in listener and listener[tls_key]: + tls[tls_key] = listener[tls_key] return tls def get_name(self, uuid): diff --git a/f5_openstack_agent/lbaasv2/drivers/bigip/ssl_profile.py b/f5_openstack_agent/lbaasv2/drivers/bigip/ssl_profile.py index b7c05242d..fe5cac072 100644 --- a/f5_openstack_agent/lbaasv2/drivers/bigip/ssl_profile.py +++ b/f5_openstack_agent/lbaasv2/drivers/bigip/ssl_profile.py @@ -36,6 +36,14 @@ def create_client_ssl_profile(bigip, name, cert, key, **kwargs): if not profile_name: profile_name = name + peerCertMode = "ignore" + client_auth = kwargs.get("client_auth", False) + if client_auth: + peerCertMode = "require" + + client_ca_cert = kwargs.get("client_ca_cert", None) + ca_cert_filename = kwargs.get("ca_cert_filename", None) + uploader = bigip.shared.file_transfer.uploads cert_registrar = bigip.tm.sys.crypto.certs key_registrar = bigip.tm.sys.crypto.keys @@ -86,6 +94,16 @@ def create_client_ssl_profile(bigip, name, cert, key, **kwargs): '/var/config/rest/downloads/', keyfilename) key_registrar.exec_cmd('install', **param_set) + if client_ca_cert: + uploader.upload_bytes(client_ca_cert, ca_cert_filename) + # import certificate + param_set = {} + param_set['name'] = ca_cert_filename + param_set['from-local-file'] = os.path.join( + '/var/config/rest/downloads/', ca_cert_filename) + cert_registrar.exec_cmd('install', **param_set) + ca_cert_filename = '/Common/' + ca_cert_filename + # create ssl-client profile from cert/key pair chain = [{'name': name, 'cert': '/Common/' + certfilename, @@ -97,6 +115,8 @@ def create_client_ssl_profile(bigip, name, cert, key, **kwargs): partition='Common', certKeyChain=chain, sniDefault=sni_default, + peerCertMode=peerCertMode, + caFile=ca_cert_filename, defaultsFrom=parent_profile) except Exception as err: LOG.error("Error creating SSL profile: %s" % err.message) diff --git a/f5_openstack_agent/lbaasv2/drivers/bigip/test/test_listener_services.py b/f5_openstack_agent/lbaasv2/drivers/bigip/test/test_listener_services.py index b6ac0bf0c..f7f199f1f 100644 --- a/f5_openstack_agent/lbaasv2/drivers/bigip/test/test_listener_services.py +++ b/f5_openstack_agent/lbaasv2/drivers/bigip/test/test_listener_services.py @@ -419,7 +419,7 @@ def add_ssl_profile_one_tls_no_sni(target, service_with_listener): assert target._create_ssl_profile.call_count == 1 target._create_ssl_profile.assert_called_once_with( '12345', bigip, dict(name='name', partition='partition'), - True) + True, ca_container_id=None, client_auth=False) def add_ssl_profile_no_tls_one_sni(target, service_with_listener): svc['listener']['protocol'] = 'HTTP' @@ -435,7 +435,7 @@ def add_ssl_profile_no_tls_one_sni(target, service_with_listener): assert target._create_ssl_profile.call_count == 1 target._create_ssl_profile.assert_called_once_with( '12345', bigip, dict(name='name', partition='partition'), - False) + False, ca_container_id=None, client_auth=False) self.creation_mode_listener(svc, svc['listeners'][0]) add_ssl_profile_no_tls_or_sni(target, svc) diff --git a/f5_openstack_agent/lbaasv2/drivers/bigip/test/test_ssl_profile.py b/f5_openstack_agent/lbaasv2/drivers/bigip/test/test_ssl_profile.py index 1b05e0a96..eea25014d 100644 --- a/f5_openstack_agent/lbaasv2/drivers/bigip/test/test_ssl_profile.py +++ b/f5_openstack_agent/lbaasv2/drivers/bigip/test/test_ssl_profile.py @@ -56,6 +56,8 @@ def test_create_client_ssl_profile_parent_none(self): 'passphrase': None} ], sniDefault=False, + peerCertMode='ignore', + caFile=None, defaultsFrom=None, ) @@ -85,6 +87,8 @@ def test_create_client_ssl_profile_parent_not_exist(self): 'passphrase': None} ], sniDefault=False, + peerCertMode='ignore', + caFile=None, defaultsFrom=None, ) @@ -108,6 +112,8 @@ def test_create_client_ssl_profile_parent(self): 'passphrase': None} ], sniDefault=False, + peerCertMode='ignore', + caFile=None, defaultsFrom='parentprofile', ) @@ -143,6 +149,8 @@ def test_create_client_ssl_intermediates_none(self): 'passphrase': "password"} ], sniDefault=False, + peerCertMode='ignore', + caFile=None, defaultsFrom=None, ) @@ -163,6 +171,8 @@ def test_create_client_ssl_intermediates(self): 'passphrase': None} ], sniDefault=False, + peerCertMode='ignore', + caFile=None, defaultsFrom=None, ) @@ -187,6 +197,8 @@ def test_create_client_ssl_intermediates_parent_profile(self): 'passphrase': None} ], sniDefault=False, + peerCertMode='ignore', + caFile=None, defaultsFrom=None, ) @@ -208,6 +220,8 @@ def test_create_client_ssl_passphrase_none(self): 'passphrase': None} ], sniDefault=False, + peerCertMode='ignore', + caFile=None, defaultsFrom=None, ) @@ -229,6 +243,8 @@ def test_create_client_ssl_passphrase(self): 'passphrase': "password"} ], sniDefault=False, + peerCertMode='ignore', + caFile=None, defaultsFrom=None, ) @@ -254,5 +270,7 @@ def test_create_client_ssl_inter_passphrase_profile(self): 'passphrase': 'password'} ], sniDefault=False, + peerCertMode='ignore', + caFile=None, defaultsFrom=None, ) From b84f6bc0f19ad3cdecd1255aea048eb088f93876 Mon Sep 17 00:00:00 2001 From: Qin Zhao Date: Mon, 21 Dec 2020 15:06:56 +0800 Subject: [PATCH 2/2] Support to modify client ca certificate --- .../lbaasv2/drivers/bigip/resource_manager.py | 9 ++++++ .../lbaasv2/drivers/bigip/ssl_profile.py | 31 ++++++++++++------- 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/f5_openstack_agent/lbaasv2/drivers/bigip/resource_manager.py b/f5_openstack_agent/lbaasv2/drivers/bigip/resource_manager.py index bcabea305..d553d374e 100644 --- a/f5_openstack_agent/lbaasv2/drivers/bigip/resource_manager.py +++ b/f5_openstack_agent/lbaasv2/drivers/bigip/resource_manager.py @@ -340,6 +340,15 @@ def _update_payload(self, old_listener, listener, service, **kwargs): payload['tls'] = "tls change" LOG.debug("tls changes happen") + # If client authentication setting changes, + # also need to update client ssl profile. + old_mode = old_listener.get('mutual_authentication_up', False) + new_mode = listener.get('mutual_authentication_up', False) + old_ca = old_listener.get('ca_container_id', "") + new_ca = listener.get('ca_container_id', "") + if old_mode != new_mode or old_ca != new_ca: + payload['tls'] = "tls change" + return super(ListenerManager, self)._update_payload( old_listener, listener, service, payload=payload, create_payload=create_payload diff --git a/f5_openstack_agent/lbaasv2/drivers/bigip/ssl_profile.py b/f5_openstack_agent/lbaasv2/drivers/bigip/ssl_profile.py index fe5cac072..374ef7dff 100644 --- a/f5_openstack_agent/lbaasv2/drivers/bigip/ssl_profile.py +++ b/f5_openstack_agent/lbaasv2/drivers/bigip/ssl_profile.py @@ -49,10 +49,6 @@ def create_client_ssl_profile(bigip, name, cert, key, **kwargs): key_registrar = bigip.tm.sys.crypto.keys ssl_client_profile = bigip.tm.ltm.profile.client_ssls.client_ssl - # No need to create if it exists - if ssl_client_profile.exists(name=profile_name, partition='Common'): - return - # Check that parent profile exists; use default if not. if parent_profile and not ssl_client_profile.exists( name=parent_profile, partition='Common'): @@ -111,13 +107,26 @@ def create_client_ssl_profile(bigip, name, cert, key, **kwargs): 'key': '/Common/' + keyfilename, 'passphrase': key_passphrase}] - ssl_client_profile.create(name=profile_name, - partition='Common', - certKeyChain=chain, - sniDefault=sni_default, - peerCertMode=peerCertMode, - caFile=ca_cert_filename, - defaultsFrom=parent_profile) + if ssl_client_profile.exists(name=profile_name, + partition='Common'): + profile = ssl_client_profile.load(name=profile_name, + partition='Common') + # BIG-IP v14 or higher version support to modify client + # authentication settings individually. + # See https://cdn.f5.com/product/bugtracker/ID674106.html + profile.modify(certKeyChain=chain, + sniDefault=sni_default, + peerCertMode=peerCertMode, + caFile=ca_cert_filename, + defaultsFrom=parent_profile) + else: + ssl_client_profile.create(name=profile_name, + partition='Common', + certKeyChain=chain, + sniDefault=sni_default, + peerCertMode=peerCertMode, + caFile=ca_cert_filename, + defaultsFrom=parent_profile) except Exception as err: LOG.error("Error creating SSL profile: %s" % err.message) raise SSLProfileError(err.message)