Fixing improper certificate validation security vulnerability 14/12414/3 master
authoraticig <gulsum.atici@canonical.com>
Fri, 29 Jul 2022 11:27:16 +0000 (14:27 +0300)
committeraticig <gulsum.atici@canonical.com>
Wed, 3 Aug 2022 08:11:46 +0000 (10:11 +0200)
Enabling certificate validation in Juniper Contrail SDN plugin.

Change-Id: I75b523fbff3d450599f888b06549e46b61dcf95a
Signed-off-by: aticig <gulsum.atici@canonical.com>
RO-SDN-juniper_contrail/osm_rosdn_juniper_contrail/rest_lib.py
RO-SDN-juniper_contrail/osm_rosdn_juniper_contrail/sdn_api.py
RO-SDN-juniper_contrail/osm_rosdn_juniper_contrail/sdn_assist_juniper_contrail.py
RO-SDN-juniper_contrail/osm_rosdn_juniper_contrail/tests/test_sdn_asssist_juniper_contrail.py [new file with mode: 0644]
releasenotes/notes/fixing_certificate_validation-d1370e1df8c0846e.yaml [new file with mode: 0644]
tox.ini

index 4c04c1c..31ea213 100644 (file)
@@ -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)
index 38b4db5..6d08def 100644 (file)
@@ -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)
index 6714132..691397d 100644 (file)
@@ -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 (file)
index 0000000..8ef04e1
--- /dev/null
@@ -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 (file)
index 0000000..fbef04e
--- /dev/null
@@ -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 43b1a92..567cefd 100644 (file)
--- 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*'