From 409d5af00f8c508db5906b696831dd209a2fc633 Mon Sep 17 00:00:00 2001 From: aticig Date: Fri, 29 Jul 2022 14:27:16 +0300 Subject: [PATCH] Fixing improper certificate validation security vulnerability Enabling certificate validation in Juniper Contrail SDN plugin. Change-Id: I75b523fbff3d450599f888b06549e46b61dcf95a Signed-off-by: aticig --- .../osm_rosdn_juniper_contrail/rest_lib.py | 14 ++- .../osm_rosdn_juniper_contrail/sdn_api.py | 3 +- .../sdn_assist_juniper_contrail.py | 19 +++ .../test_sdn_asssist_juniper_contrail.py | 117 ++++++++++++++++++ ...rtificate_validation-d1370e1df8c0846e.yaml | 21 ++++ tox.ini | 6 +- 6 files changed, 171 insertions(+), 9 deletions(-) create mode 100644 RO-SDN-juniper_contrail/osm_rosdn_juniper_contrail/tests/test_sdn_asssist_juniper_contrail.py create mode 100644 releasenotes/notes/fixing_certificate_validation-d1370e1df8c0846e.yaml diff --git a/RO-SDN-juniper_contrail/osm_rosdn_juniper_contrail/rest_lib.py b/RO-SDN-juniper_contrail/osm_rosdn_juniper_contrail/rest_lib.py index 4c04c1c9..31ea213b 100644 --- a/RO-SDN-juniper_contrail/osm_rosdn_juniper_contrail/rest_lib.py +++ b/RO-SDN-juniper_contrail/osm_rosdn_juniper_contrail/rest_lib.py @@ -43,10 +43,10 @@ class ServiceUnavailableException(HttpException): class ContrailHttp(object): - def __init__(self, auth_info, logger): + def __init__(self, auth_info, logger, verify): self._logger = logger - # default don't verify client cert - self._ssl_verify = False + # Verify client cert + self.ssl_verify = verify # auth info: must contain auth_url and auth_dict self.auth_url = auth_info["auth_url"] self.auth_dict = auth_info["auth_dict"] @@ -247,10 +247,14 @@ class ContrailHttp(object): return requests.get(url, headers=headers, params=query_params) def _http_post_headers(self, url, headers, json_data=None): - return requests.head(url, json=json_data, headers=headers, verify=False) + return requests.head( + url, json=json_data, headers=headers, verify=self.ssl_verify + ) def _http_post(self, url, headers, json_data=None): - return requests.post(url, json=json_data, headers=headers, verify=False) + return requests.post( + url, json=json_data, headers=headers, verify=self.ssl_verify + ) def _http_delete(self, url, headers, json_data=None): return requests.delete(url, json=json_data, headers=headers) diff --git a/RO-SDN-juniper_contrail/osm_rosdn_juniper_contrail/sdn_api.py b/RO-SDN-juniper_contrail/osm_rosdn_juniper_contrail/sdn_api.py index 38b4db57..6d08def4 100644 --- a/RO-SDN-juniper_contrail/osm_rosdn_juniper_contrail/sdn_api.py +++ b/RO-SDN-juniper_contrail/osm_rosdn_juniper_contrail/sdn_api.py @@ -54,6 +54,7 @@ class UnderlayApi: self.domain = config.get("domain") self.asn = config.get("asn") self.fabric = config.get("fabric") + self.verify = config.get("verify") # Init http headers for all requests self.http_header = {"Content-Type": "application/json"} @@ -90,7 +91,7 @@ class UnderlayApi: # Init http lib auth_info = {"auth_url": self.auth_url, "auth_dict": auth_dict} - self.http = ContrailHttp(auth_info, self.logger) + self.http = ContrailHttp(auth_info, self.logger, self.verify) def check_auth(self): response = self.http.get_cmd(url=self.auth_url, headers=self.http_header) diff --git a/RO-SDN-juniper_contrail/osm_rosdn_juniper_contrail/sdn_assist_juniper_contrail.py b/RO-SDN-juniper_contrail/osm_rosdn_juniper_contrail/sdn_assist_juniper_contrail.py index 6714132e..691397d7 100644 --- a/RO-SDN-juniper_contrail/osm_rosdn_juniper_contrail/sdn_assist_juniper_contrail.py +++ b/RO-SDN-juniper_contrail/osm_rosdn_juniper_contrail/sdn_assist_juniper_contrail.py @@ -80,6 +80,7 @@ class JuniperContrail(SdnConnectorBase): self.fabric = None overlay_url = None self.vni_range = None + self.verify = True if config: auth_url = config.get("auth_url") @@ -90,6 +91,23 @@ class JuniperContrail(SdnConnectorBase): self.overlay_url = config.get("overlay_url") self.vni_range = config.get("vni_range") + if config.get("insecure") and config.get("ca_cert"): + raise SdnConnectorError( + "options insecure and ca_cert are mutually exclusive" + ) + + if config.get("ca_cert"): + self.verify = config.get("ca_cert") + + elif config.get("insecure"): + self.verify = False + + else: + raise SdnConnectorError( + "certificate should provided or ssl verification should be " + "disabled by setting insecure as True in sdn/wim config." + ) + if not url: raise SdnConnectorError("'url' must be provided") @@ -150,6 +168,7 @@ class JuniperContrail(SdnConnectorBase): "domain": self.domain, "asn": self.asn, "fabric": self.fabric, + "verify": self.verify, } self.underlay_api = UnderlayApi( url, diff --git a/RO-SDN-juniper_contrail/osm_rosdn_juniper_contrail/tests/test_sdn_asssist_juniper_contrail.py b/RO-SDN-juniper_contrail/osm_rosdn_juniper_contrail/tests/test_sdn_asssist_juniper_contrail.py new file mode 100644 index 00000000..8ef04e1b --- /dev/null +++ b/RO-SDN-juniper_contrail/osm_rosdn_juniper_contrail/tests/test_sdn_asssist_juniper_contrail.py @@ -0,0 +1,117 @@ +####################################################################################### +# Copyright ETSI Contributors and Others. +# +# 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 unittest +from unittest.mock import patch + +from osm_ro_plugin.sdnconn import SdnConnectorError +from osm_rosdn_juniper_contrail.sdn_assist_juniper_contrail import JuniperContrail + + +class TestJuniperContrail(unittest.TestCase): + @patch("logging.getLogger") + def setUp(self, mock_logger): + self.wim = {"wim_url": "http://dummy.url"} + self.wim_account = { + "user": "sdn_user", + "password": "dummy_pass", + } + self.logger = None + self.mock_logger = mock_logger + + @patch("osm_rosdn_juniper_contrail.sdn_api.UnderlayApi") + def test_juniper_contrail_sdn_with_ssl_cert(self, mock_underlay_api): + + config = { + "ca_cert": "/path/to/certfile", + "project": "test_project", + "domain": "test_default", + "asn": "test_asn", + "fabric": "test_fabric", + } + + underlay_api_config = { + "auth_url": self.wim, + "verify": config["ca_cert"], + "user": self.wim_account["user"], + "password": self.wim_account["password"], + } + expected_underlay_api_call = [ + self.wim, + underlay_api_config, + self.wim_account["user"], + self.wim_account["password"], + self.mock_logger, + ] + + JuniperContrail(self.wim, self.wim_account, config, self.logger) + mock_underlay_api.called_once_with(expected_underlay_api_call) + + @patch("osm_rosdn_juniper_contrail.sdn_api.UnderlayApi") + def test_juniper_contrail_sdn_insecure_connection(self, mock_underlay_api): + config = { + "insecure": True, + "project": "test_project", + "domain": "test_default", + "asn": "test_asn", + "fabric": "test_fabric", + } + underlay_api_config = { + "auth_url": self.wim, + "verify": False, + "user": self.wim_account["user"], + "password": self.wim_account["password"], + } + expected_underlay_api_call = [ + self.wim, + underlay_api_config, + self.wim_account["user"], + self.wim_account["password"], + self.mock_logger, + ] + + JuniperContrail(self.wim, self.wim_account, config, self.logger) + mock_underlay_api.called_once_with(expected_underlay_api_call) + + @patch("osm_rosdn_juniper_contrail.sdn_api.UnderlayApi") + def test_juniper_contrail_sdn_config_does_not_include_ssl_config_options( + self, mock_underlay_api + ): + config = { + "project": "test_project", + "domain": "test_default", + "asn": "test_asn", + "fabric": "test_fabric", + } + with self.assertRaises(SdnConnectorError): + JuniperContrail(self.wim, self.wim_account, config, self.logger) + + @patch("osm_rosdn_juniper_contrail.sdn_api.UnderlayApi") + def test_juniper_contrail_sdn_config_includes_both_ca_cert_and_insecure( + self, mock_underlay_api + ): + config = { + "project": "test_project", + "domain": "test_default", + "asn": "test_asn", + "fabric": "test_fabric", + "insecure": True, + "ca_cert": "/path/to/certfile", + } + + with self.assertRaises(SdnConnectorError): + JuniperContrail(self.wim, self.wim_account, config, self.logger) diff --git a/releasenotes/notes/fixing_certificate_validation-d1370e1df8c0846e.yaml b/releasenotes/notes/fixing_certificate_validation-d1370e1df8c0846e.yaml new file mode 100644 index 00000000..fbef04e2 --- /dev/null +++ b/releasenotes/notes/fixing_certificate_validation-d1370e1df8c0846e.yaml @@ -0,0 +1,21 @@ +####################################################################################### +# Copyright ETSI Contributors and Others. +# +# 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. +####################################################################################### +--- +security: + - | + Fixing following RO security vulnerabilities. Improper Certificate Validation in Juniper + Contrail SDN Plugin. \ No newline at end of file diff --git a/tox.ini b/tox.ini index 43b1a92a..567cefdb 100644 --- a/tox.ini +++ b/tox.ini @@ -98,8 +98,8 @@ commands = # nose2 -C --coverage RO-SDN-ietfl2vpn/osm_rosdn_ietfl2vpn -s RO-SDN-ietfl2vpn/osm_rosdn_ietfl2vpn # sh -c 'mv .coverage .coverage_rosdn_ietfl2vpn' # RO-SDN-juniper_contrail - # nose2 -C --coverage RO-SDN-juniper_contrail/osm_rosdn_juniper_contrail -s RO-SDN-juniper_contrail/osm_rosdn_juniper_contrail - # sh -c 'mv .coverage .coverage_rosdn_juniper_contrail' + nose2 -C --coverage RO-SDN-juniper_contrail/osm_rosdn_juniper_contrail -s RO-SDN-juniper_contrail/osm_rosdn_juniper_contrail + sh -c 'mv .coverage .coverage_rosdn_juniper_contrail' # RO-SDN-odl_openflow # nose2 -C --coverage RO-SDN-odl_openflow/osm_rosdn_odlof -s RO-SDN-odl_openflow/osm_rosdn_odlof # sh -c 'mv .coverage .coverage_rosdn_odlof' @@ -129,7 +129,7 @@ commands = # sh -c 'mv .coverage .coverage_rovim_gcp' # Combine results and generate reports # coverage combine .coverage_ng_ro .coverage_ro_plugin .coverage_rosdn_arista_cloudvision .coverage_rosdn_dpb .coverage_rosdn_dynpac .coverage_rosdn_floodlightof .coverage_rosdn_ietfl2vpn .coverage_rosdn_juniper_contrail .coverage_rosdn_odlof .coverage_rosdn_onos_vpls .coverage_rosdn_onosof .coverage_rovim_aws .coverage_rovim_azure .coverage_rovim_openvim .coverage_rovim_gcp # .coverage_rovim_openstack .coverage_rovim_vmware - coverage combine .coverage_ng_ro .coverage_rovim_openstack + coverage combine .coverage_ng_ro .coverage_rovim_openstack .coverage_rosdn_juniper_contrail coverage report --omit='*tests*' coverage html -d ./cover --omit='*tests*' coverage xml -o coverage.xml --omit='*tests*' -- 2.17.1