From 0828ccd0a5011c4f4bb4e929c8f68dc852f7e586 Mon Sep 17 00:00:00 2001 From: Adam Baumeister Date: Sun, 14 Jan 2024 21:50:33 +1100 Subject: [PATCH 01/10] New check and refactor to make version match logic reusable --- panos_upgrade_assurance/check_firewall.py | 97 ++++++++++++++++++++--- 1 file changed, 87 insertions(+), 10 deletions(-) diff --git a/panos_upgrade_assurance/check_firewall.py b/panos_upgrade_assurance/check_firewall.py index 1a254c4..57a25ae 100644 --- a/panos_upgrade_assurance/check_firewall.py +++ b/panos_upgrade_assurance/check_firewall.py @@ -6,6 +6,7 @@ import panos.errors from packaging.version import parse as parse_version +from packaging.version import Version from panos_upgrade_assurance.utils import ( CheckResult, @@ -1253,6 +1254,38 @@ def run_health_checks( return result + @staticmethod + def check_version_against_version_match_dict(version: Version, match_dict: dict): + """Compare the given software version against the match dict. + + # Parameters + version (str): The software version to compare. Example: "10.1.11" + match_dict (dict): A dictionary of tuples mapping major/minor versions to match criteria + example + + ```python + { + "81": [("==", "8.1.21.2"), (">=", "8.1.25.1")], + "90": [(">=", "9.0.16.5")], + } + ``` + + Returns + + bool: `True` If the given software version matches the provided match criteria + """ + match_versions = match_dict.get(f"{version.major}{version.minor}") + if match_versions: + for operator, match_version in match_versions: + match_version = parse_version(match_version) + if operator == "==": + if version == match_version: + return True + elif operator == ">=": + if version >= match_version: + return True + + def check_device_root_certificate_issue(self, fail_when_affected_version_only: bool = True) -> CheckResult: """Checks whether the target device is affected by the Root Certificate Expiration issue; @@ -1316,16 +1349,8 @@ def check_device_root_certificate_issue(self, fail_when_affected_version_only: b } fixed_content_version = 8776.8390 - fixed_versions = fixed_version_map.get(f"{software_version.major}{software_version.minor}") - if fixed_versions: - for operator, fixed_version in fixed_versions: - fixed_version = parse_version(fixed_version) - if operator == "==": - if software_version == fixed_version: - result.status = CheckStatus.SUCCESS - elif operator == ">=": - if software_version >= fixed_version: - result.status = CheckStatus.SUCCESS + if self.check_version_against_version_match_dict(software_version, fixed_version_map): + result.status = CheckStatus.SUCCESS # If the device is already running fixed software, we can return immediately if result.status == CheckStatus.SUCCESS: @@ -1372,3 +1397,55 @@ def check_device_root_certificate_issue(self, fail_when_affected_version_only: b "expire December 31st, 2023." ) return result + + def check_cdss_and_panorama_certificate_issue(self): + """Checks whether the device is affected by the following advisory; + + https://live.paloaltonetworks.com/t5/customer-advisories/additional-pan-os-certificate-expirations-and-new-comprehensive/ta-p/572158 + + Check will fail in either of following scenarios: + + * Device is running an affected software version + * Device has not been onboarded for CDSS + + This check disregards whether the firewalls are running a fixed content version, looking only for fixed + software instead. + """ + fixed_version_map = { + "81": [ + ("==", "8.1.21.3"), ("==", "8.1.25.3"), (">=", "8.1.26") + ], + "90": [ + ("==", "9.0.16.7"), ("==", "9.0.17.5") + ], + "91": [ + ("==", "9.1.11.5"), ("==", "9.1.12.7"), ("==", "9.1.13.5"), ("==", "9.1.14.8"), ("==", "9.1.16.5"), + (">=", "9.1.17") + ], + "100": [ + ("==", "10.0.8.11"), ("==", "10.0.11.4"), ("==", "10.0.12.5") + ], + "101": [ + ("==", "10.1.3.3"), ("==", "10.1.4.6"), ("==", "10.1.5.4"), ("==", "10.1.6.8"), ("==", "10.1.7.1"), + ("==", "10.1.8.7"), ("==", "10.1.9.8"), ("==", "10.1.10.5"), ("==", "10.1.11.4"), (">=", "10.1.12") + ], + "102": [ + ("==", "10.2.0.2"), ("==", "10.2.1.1"), ("==", "10.2.2.4"), ("==", "10.2.3.11"), ("==", "10.2.4.10"), + ("==", "10.2.5.4"), ("==", "10.2.6.1"), ("==", "10.2.7.3"), (">=", "10.2.8") + ], + "110": [ + ("==", "11.0.0.2"), ("==", "11.0.1.3"), ("==", "11.0.2.3"), (">=", "11.0.3.3"), (">=", "11.0.4") + ], + "111": [ + ("==", "11.1.0.2"), (">=", "11.1.1") + ] + } + + result = CheckResult() + + software_version = self._node.get_device_software_version() + + if self.check_version_against_version_match_dict(software_version, fixed_version_map): + # Fixed software means we can return immediately, no need to further check + result.status = CheckStatus.SUCCESS + return result \ No newline at end of file From e1524d28b975c9f7baa87ae3a1f9908a9bbb0f12 Mon Sep 17 00:00:00 2001 From: Adam Baumeister Date: Mon, 15 Jan 2024 15:53:23 +1100 Subject: [PATCH 02/10] Health check added with tests --- panos_upgrade_assurance/check_firewall.py | 27 ++++++++-- panos_upgrade_assurance/firewall_proxy.py | 31 +++++++++++- tests/test_check_firewall.py | 61 +++++++++++++++++++++++ tests/test_firewall_proxy.py | 59 +++++++++++++++++++++- 4 files changed, 172 insertions(+), 6 deletions(-) diff --git a/panos_upgrade_assurance/check_firewall.py b/panos_upgrade_assurance/check_firewall.py index 57a25ae..a63be37 100644 --- a/panos_upgrade_assurance/check_firewall.py +++ b/panos_upgrade_assurance/check_firewall.py @@ -1406,10 +1406,10 @@ def check_cdss_and_panorama_certificate_issue(self): Check will fail in either of following scenarios: * Device is running an affected software version - * Device has not been onboarded for CDSS + * Device is running an affected content version + * Device is running the fixed content version or higher but has not been rebooted - note this is best effort, + and is based on when the content version was released and the device was rebooted - This check disregards whether the firewalls are running a fixed content version, looking only for fixed - software instead. """ fixed_version_map = { "81": [ @@ -1441,6 +1441,10 @@ def check_cdss_and_panorama_certificate_issue(self): ] } + # Release date and fixed version are both static + fixed_content_version = 8795.8489 + fixed_content_version_release_date = datetime(2024, 1, 8, 19, 26, 43) + result = CheckResult() software_version = self._node.get_device_software_version() @@ -1448,4 +1452,19 @@ def check_cdss_and_panorama_certificate_issue(self): if self.check_version_against_version_match_dict(software_version, fixed_version_map): # Fixed software means we can return immediately, no need to further check result.status = CheckStatus.SUCCESS - return result \ No newline at end of file + return result + + content_version = float(self._node.get_content_db_version().replace("-", ".")) + + if content_version >= fixed_content_version: + # Check the device has been rebooted since the release of the fixed content version + # This is not a perfect test - if the customer reboots without installing the content update, then + # later installs it, it will pass even though one further restart is required. + reboot_time = self._node.get_system_time_rebooted() + if reboot_time < fixed_content_version_release_date: + result.reason = ("Device is running fixed Content but still requires a restart for the fix to take " + "effect.") + else: + result.status = CheckStatus.SUCCESS + + return result \ No newline at end of file diff --git a/panos_upgrade_assurance/firewall_proxy.py b/panos_upgrade_assurance/firewall_proxy.py index e57849f..4dceb2b 100644 --- a/panos_upgrade_assurance/firewall_proxy.py +++ b/panos_upgrade_assurance/firewall_proxy.py @@ -7,7 +7,7 @@ from pan.xapi import PanXapiError from panos_upgrade_assurance import exceptions from math import floor -from datetime import datetime +from datetime import datetime, timedelta from packaging import version @@ -1461,3 +1461,32 @@ def get_fib(self) -> dict: results[key] = result_entry return results + + def get_system_time_rebooted(self) -> datetime: + """Returns the date and time the system last rebooted using the system uptime. + + The actual API command is `show system info`. + + # Returns + + datetime: Time system was last rebooted based on current time - system uptime string + + ```python showLineNumbers title="Sample output" + datetime(2024, 01, 01, 00, 00, 00) + ``` + + """ + response = self.op_parser(cmd="show system info", return_xml=True) + uptime_string = response.findtext("./system/uptime") + current_time = datetime.now() + + time_re_match = re.search("(\d+) days, (\d+):(\d+):(\d+)", uptime_string) + + rebooted_time = current_time - timedelta( + days=int(time_re_match.group(1)), + hours=int(time_re_match.group(2)), + minutes=int(time_re_match.group(3)), + seconds=int(time_re_match.group(4)), + ) + + return rebooted_time \ No newline at end of file diff --git a/tests/test_check_firewall.py b/tests/test_check_firewall.py index db3e62a..d573c45 100644 --- a/tests/test_check_firewall.py +++ b/tests/test_check_firewall.py @@ -1307,3 +1307,64 @@ def test_run_health_checks(self, check_firewall_mock): check_firewall_mock._health_check_method_mapping["check1"].assert_called_once_with() check_firewall_mock._health_check_method_mapping["check2"].assert_called_once_with(param1=123) + + @pytest.mark.parametrize( + "running_software, expected_status", + [ + ("10.1.2", CheckStatus.FAIL), # Device running broken version + ("10.1.13", CheckStatus.SUCCESS), # Device running fixed version + ], + ) + def test_check_cdss_and_panorama_certificate_issue(self, check_firewall_mock, running_software, expected_status): + """This test validates the behavior when the test is only checking the software version is affected by + the issue.""" + + from packaging import version + + check_firewall_mock._node.get_device_software_version = MagicMock(return_value=version.parse(running_software)) + assert check_firewall_mock.check_cdss_and_panorama_certificate_issue().status == expected_status + + @pytest.mark.parametrize( + "running_content_version, last_reboot, expected_status", + [ + ( + "8000-8391", + datetime(2022, 1, 1, 0, 0, 0), + CheckStatus.FAIL + ), # Device running older content version and no reboot + ( + "8795-8489", + datetime(2022, 1, 1, 0, 0, 0), + CheckStatus.FAIL + ), # Device running fixed version without reboot + ( + "8795-8489", + datetime(2024, 1, 10, 0, 0, 0), + CheckStatus.SUCCESS + ), # Device running fixed version and rebooted + ], + ) + def test_check_cdss_and_panorama_certificate_issue_by_content_version( + self, check_firewall_mock, running_content_version, expected_status, last_reboot + ): + """Tests that we check the content version and use a best effort approach for seeing if the device has been + rebooted in the time since it was released/installed""" + from packaging import version + + check_firewall_mock._node.get_device_software_version = MagicMock( + return_value=version.parse("10.1.0") # Affected Version + ) + + check_firewall_mock._node.get_content_db_version = MagicMock( + return_value=running_content_version + ) + + # Device hasn't been rebooted + check_firewall_mock._node.get_system_time_rebooted = MagicMock( + return_value=last_reboot + ) + + assert ( + check_firewall_mock.check_cdss_and_panorama_certificate_issue().status + == expected_status + ) \ No newline at end of file diff --git a/tests/test_firewall_proxy.py b/tests/test_firewall_proxy.py index 8a69eaf..270ceeb 100644 --- a/tests/test_firewall_proxy.py +++ b/tests/test_firewall_proxy.py @@ -16,7 +16,7 @@ UpdateServerConnectivityException, GetXpathConfigFailedException, ) -from datetime import datetime +from datetime import datetime, timedelta @pytest.fixture(scope="function") @@ -1761,3 +1761,60 @@ def test_get_fib_routes_none(self, fw_proxy_mock): fw_proxy_mock.op.return_value = raw_response assert fw_proxy_mock.get_fib() == {} + + def test_get_system_time_rebooted(self, fw_proxy_mock): + fw_proxy_mock.op = MagicMock() + + xml_text = """ + + + + testfw + 1.1.1.1 + unknown + 255.255.254.0 + 1.1.1.1 + no + ab:cd:ef:11:22:33 + + 5 days, 1:02:03 + testfw + 7000 + PA-7050 + 11111111111 + non-cloud + 9.1.12-h3 + 0.0.0 + 8709-8047 + + 4455-4972 + 2023/05/18 14:50:34 PDT + 8709-8047 + + 0 + unknown + paloaltonetworks + 0 + + 20231204.20037 + 1684375262 + 2023/05/17 19:01:02 + 97-245 + 2023/01/27 14:38:39 PST + 9.1.22 + 7000 + off + off + on + normal + Valid + + + + """ + + raw_response = ET.fromstring(xml_text) + fw_proxy_mock.op.return_value = raw_response + + assert type(fw_proxy_mock.get_system_time_rebooted()) is datetime \ No newline at end of file From faecfbbf3f80d8fe9ac0790e6927f4039e29ccaf Mon Sep 17 00:00:00 2001 From: Adam Baumeister Date: Tue, 16 Jan 2024 12:48:45 +1100 Subject: [PATCH 03/10] Black reformat --- panos_upgrade_assurance/check_firewall.py | 57 +++++++++++++---------- panos_upgrade_assurance/firewall_proxy.py | 2 +- tests/test_check_firewall.py | 31 +++--------- tests/test_firewall_proxy.py | 2 +- 4 files changed, 40 insertions(+), 52 deletions(-) diff --git a/panos_upgrade_assurance/check_firewall.py b/panos_upgrade_assurance/check_firewall.py index a63be37..e311cad 100644 --- a/panos_upgrade_assurance/check_firewall.py +++ b/panos_upgrade_assurance/check_firewall.py @@ -1285,7 +1285,6 @@ def check_version_against_version_match_dict(version: Version, match_dict: dict) if version >= match_version: return True - def check_device_root_certificate_issue(self, fail_when_affected_version_only: bool = True) -> CheckResult: """Checks whether the target device is affected by the Root Certificate Expiration issue; @@ -1412,33 +1411,42 @@ def check_cdss_and_panorama_certificate_issue(self): """ fixed_version_map = { - "81": [ - ("==", "8.1.21.3"), ("==", "8.1.25.3"), (">=", "8.1.26") - ], - "90": [ - ("==", "9.0.16.7"), ("==", "9.0.17.5") - ], + "81": [("==", "8.1.21.3"), ("==", "8.1.25.3"), (">=", "8.1.26")], + "90": [("==", "9.0.16.7"), ("==", "9.0.17.5")], "91": [ - ("==", "9.1.11.5"), ("==", "9.1.12.7"), ("==", "9.1.13.5"), ("==", "9.1.14.8"), ("==", "9.1.16.5"), - (">=", "9.1.17") - ], - "100": [ - ("==", "10.0.8.11"), ("==", "10.0.11.4"), ("==", "10.0.12.5") + ("==", "9.1.11.5"), + ("==", "9.1.12.7"), + ("==", "9.1.13.5"), + ("==", "9.1.14.8"), + ("==", "9.1.16.5"), + (">=", "9.1.17"), ], + "100": [("==", "10.0.8.11"), ("==", "10.0.11.4"), ("==", "10.0.12.5")], "101": [ - ("==", "10.1.3.3"), ("==", "10.1.4.6"), ("==", "10.1.5.4"), ("==", "10.1.6.8"), ("==", "10.1.7.1"), - ("==", "10.1.8.7"), ("==", "10.1.9.8"), ("==", "10.1.10.5"), ("==", "10.1.11.4"), (">=", "10.1.12") + ("==", "10.1.3.3"), + ("==", "10.1.4.6"), + ("==", "10.1.5.4"), + ("==", "10.1.6.8"), + ("==", "10.1.7.1"), + ("==", "10.1.8.7"), + ("==", "10.1.9.8"), + ("==", "10.1.10.5"), + ("==", "10.1.11.4"), + (">=", "10.1.12"), ], "102": [ - ("==", "10.2.0.2"), ("==", "10.2.1.1"), ("==", "10.2.2.4"), ("==", "10.2.3.11"), ("==", "10.2.4.10"), - ("==", "10.2.5.4"), ("==", "10.2.6.1"), ("==", "10.2.7.3"), (">=", "10.2.8") - ], - "110": [ - ("==", "11.0.0.2"), ("==", "11.0.1.3"), ("==", "11.0.2.3"), (">=", "11.0.3.3"), (">=", "11.0.4") + ("==", "10.2.0.2"), + ("==", "10.2.1.1"), + ("==", "10.2.2.4"), + ("==", "10.2.3.11"), + ("==", "10.2.4.10"), + ("==", "10.2.5.4"), + ("==", "10.2.6.1"), + ("==", "10.2.7.3"), + (">=", "10.2.8"), ], - "111": [ - ("==", "11.1.0.2"), (">=", "11.1.1") - ] + "110": [("==", "11.0.0.2"), ("==", "11.0.1.3"), ("==", "11.0.2.3"), (">=", "11.0.3.3"), (">=", "11.0.4")], + "111": [("==", "11.1.0.2"), (">=", "11.1.1")], } # Release date and fixed version are both static @@ -1462,9 +1470,8 @@ def check_cdss_and_panorama_certificate_issue(self): # later installs it, it will pass even though one further restart is required. reboot_time = self._node.get_system_time_rebooted() if reboot_time < fixed_content_version_release_date: - result.reason = ("Device is running fixed Content but still requires a restart for the fix to take " - "effect.") + result.reason = "Device is running fixed Content but still requires a restart for the fix to take " "effect." else: result.status = CheckStatus.SUCCESS - return result \ No newline at end of file + return result diff --git a/panos_upgrade_assurance/firewall_proxy.py b/panos_upgrade_assurance/firewall_proxy.py index 4dceb2b..bf24da2 100644 --- a/panos_upgrade_assurance/firewall_proxy.py +++ b/panos_upgrade_assurance/firewall_proxy.py @@ -1489,4 +1489,4 @@ def get_system_time_rebooted(self) -> datetime: seconds=int(time_re_match.group(4)), ) - return rebooted_time \ No newline at end of file + return rebooted_time diff --git a/tests/test_check_firewall.py b/tests/test_check_firewall.py index d573c45..0492dcc 100644 --- a/tests/test_check_firewall.py +++ b/tests/test_check_firewall.py @@ -1327,21 +1327,9 @@ def test_check_cdss_and_panorama_certificate_issue(self, check_firewall_mock, ru @pytest.mark.parametrize( "running_content_version, last_reboot, expected_status", [ - ( - "8000-8391", - datetime(2022, 1, 1, 0, 0, 0), - CheckStatus.FAIL - ), # Device running older content version and no reboot - ( - "8795-8489", - datetime(2022, 1, 1, 0, 0, 0), - CheckStatus.FAIL - ), # Device running fixed version without reboot - ( - "8795-8489", - datetime(2024, 1, 10, 0, 0, 0), - CheckStatus.SUCCESS - ), # Device running fixed version and rebooted + ("8000-8391", datetime(2022, 1, 1, 0, 0, 0), CheckStatus.FAIL), # Device running older content version and no reboot + ("8795-8489", datetime(2022, 1, 1, 0, 0, 0), CheckStatus.FAIL), # Device running fixed version without reboot + ("8795-8489", datetime(2024, 1, 10, 0, 0, 0), CheckStatus.SUCCESS), # Device running fixed version and rebooted ], ) def test_check_cdss_and_panorama_certificate_issue_by_content_version( @@ -1355,16 +1343,9 @@ def test_check_cdss_and_panorama_certificate_issue_by_content_version( return_value=version.parse("10.1.0") # Affected Version ) - check_firewall_mock._node.get_content_db_version = MagicMock( - return_value=running_content_version - ) + check_firewall_mock._node.get_content_db_version = MagicMock(return_value=running_content_version) # Device hasn't been rebooted - check_firewall_mock._node.get_system_time_rebooted = MagicMock( - return_value=last_reboot - ) + check_firewall_mock._node.get_system_time_rebooted = MagicMock(return_value=last_reboot) - assert ( - check_firewall_mock.check_cdss_and_panorama_certificate_issue().status - == expected_status - ) \ No newline at end of file + assert check_firewall_mock.check_cdss_and_panorama_certificate_issue().status == expected_status diff --git a/tests/test_firewall_proxy.py b/tests/test_firewall_proxy.py index 270ceeb..e3f8048 100644 --- a/tests/test_firewall_proxy.py +++ b/tests/test_firewall_proxy.py @@ -1817,4 +1817,4 @@ def test_get_system_time_rebooted(self, fw_proxy_mock): raw_response = ET.fromstring(xml_text) fw_proxy_mock.op.return_value = raw_response - assert type(fw_proxy_mock.get_system_time_rebooted()) is datetime \ No newline at end of file + assert type(fw_proxy_mock.get_system_time_rebooted()) is datetime From 675a8c94ef1ffb3a6080b18dbf474bc77adf0e08 Mon Sep 17 00:00:00 2001 From: Adam Baumeister Date: Tue, 16 Jan 2024 12:49:12 +1100 Subject: [PATCH 04/10] Fix Flake issues --- panos_upgrade_assurance/firewall_proxy.py | 2 +- tests/test_firewall_proxy.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/panos_upgrade_assurance/firewall_proxy.py b/panos_upgrade_assurance/firewall_proxy.py index bf24da2..271d625 100644 --- a/panos_upgrade_assurance/firewall_proxy.py +++ b/panos_upgrade_assurance/firewall_proxy.py @@ -1480,7 +1480,7 @@ def get_system_time_rebooted(self) -> datetime: uptime_string = response.findtext("./system/uptime") current_time = datetime.now() - time_re_match = re.search("(\d+) days, (\d+):(\d+):(\d+)", uptime_string) + time_re_match = re.search(r"(\d+) days, (\d+):(\d+):(\d+)", uptime_string) rebooted_time = current_time - timedelta( days=int(time_re_match.group(1)), diff --git a/tests/test_firewall_proxy.py b/tests/test_firewall_proxy.py index e3f8048..676bf59 100644 --- a/tests/test_firewall_proxy.py +++ b/tests/test_firewall_proxy.py @@ -16,7 +16,7 @@ UpdateServerConnectivityException, GetXpathConfigFailedException, ) -from datetime import datetime, timedelta +from datetime import datetime @pytest.fixture(scope="function") From 3d7060e0644b1da3406221532d5dd97b251e2fd1 Mon Sep 17 00:00:00 2001 From: Adam Baumeister Date: Tue, 16 Jan 2024 12:51:10 +1100 Subject: [PATCH 05/10] Docs --- .../api/check_firewall.md | 43 +++++++++++++++++++ .../api/firewall_proxy.md | 19 ++++++++ 2 files changed, 62 insertions(+) diff --git a/docs/panos-upgrade-assurance/api/check_firewall.md b/docs/panos-upgrade-assurance/api/check_firewall.md index c189cad..5a35034 100644 --- a/docs/panos-upgrade-assurance/api/check_firewall.md +++ b/docs/panos-upgrade-assurance/api/check_firewall.md @@ -686,6 +686,32 @@ __Returns__ `dict`: Results of all configured checks. +### `CheckFirewall.check_version_against_version_match_dict` + +```python +@staticmethod +def check_version_against_version_match_dict(version: Version, + match_dict: dict) +``` + +Compare the given software version against the match dict. + +# Parameters +version (str): The software version to compare. Example: "10.1.11" +match_dict (dict): A dictionary of tuples mapping major/minor versions to match criteria +example + + +Returns + +bool: `True` If the given software version matches the provided match criteria + ```python + { + "81": [("==", "8.1.21.2"), (">=", "8.1.25.1")], + "90": [(">=", "9.0.16.5")], + } + ``` + ### `CheckFirewall.check_device_root_certificate_issue` ```python @@ -712,3 +738,20 @@ __Parameters__ fail if the software version is affected by the root certificate issue, AND the device is used for data redistribution OR it's using an out-of-date content DB version. +### `CheckFirewall.check_cdss_and_panorama_certificate_issue` + +```python +def check_cdss_and_panorama_certificate_issue() +``` + +Checks whether the device is affected by the following advisory; + +https://live.paloaltonetworks.com/t5/customer-advisories/additional-pan-os-certificate-expirations-and-new-comprehensive/ta-p/572158 + +Check will fail in either of following scenarios: + + * Device is running an affected software version + * Device is running an affected content version + * Device is running the fixed content version or higher but has not been rebooted - note this is best effort, + and is based on when the content version was released and the device was rebooted + diff --git a/docs/panos-upgrade-assurance/api/firewall_proxy.md b/docs/panos-upgrade-assurance/api/firewall_proxy.md index e891ec0..7a5cbbc 100644 --- a/docs/panos-upgrade-assurance/api/firewall_proxy.md +++ b/docs/panos-upgrade-assurance/api/firewall_proxy.md @@ -1255,3 +1255,22 @@ __Returns__ } ``` +### `FirewallProxy.get_system_time_rebooted` + +```python +def get_system_time_rebooted() -> datetime +``` + +Returns the date and time the system last rebooted using the system uptime. + +The actual API command is `show system info`. + +__Returns__ + + +`datetime`: Time system was last rebooted based on current time - system uptime string + +```python showLineNumbers title="Sample output" +datetime(2024, 01, 01, 00, 00, 00) +``` + From 25c3a29e15406a300fa8284bbfeed626a198ad36 Mon Sep 17 00:00:00 2001 From: Adam Baumeister Date: Tue, 16 Jan 2024 13:24:08 +1100 Subject: [PATCH 06/10] Add health check to list of checks --- examples/readiness_checks/run_health_checks.py | 6 ++++-- panos_upgrade_assurance/check_firewall.py | 12 +++++++++++- panos_upgrade_assurance/utils.py | 1 + 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/examples/readiness_checks/run_health_checks.py b/examples/readiness_checks/run_health_checks.py index 148a9a1..c7b04b3 100644 --- a/examples/readiness_checks/run_health_checks.py +++ b/examples/readiness_checks/run_health_checks.py @@ -66,11 +66,12 @@ vsys = args.vsys if serial: + print(address) panorama = Panorama( hostname=address, api_password=password, api_username=username ) firewall = FirewallProxy(serial=serial) - panorama.add(firewall) + panorama.add(firewall._fw) else: firewall = FirewallProxy( hostname=address, api_password=password, api_username=username, vsys=vsys @@ -79,7 +80,8 @@ check_node = CheckFirewall(firewall) checks = [ - "device_root_certificate_issue" + "device_root_certificate_issue", + "check_cdss_and_panorama_certificate_issue" ] check_health = check_node.run_health_checks( diff --git a/panos_upgrade_assurance/check_firewall.py b/panos_upgrade_assurance/check_firewall.py index e311cad..e2bdf5e 100644 --- a/panos_upgrade_assurance/check_firewall.py +++ b/panos_upgrade_assurance/check_firewall.py @@ -105,7 +105,10 @@ def __init__(self, node: FirewallProxy, skip_force_locale: Optional[bool] = Fals CheckType.JOBS: self.check_non_finished_jobs, } - self._health_check_method_mapping = {HealthType.DEVICE_ROOT_CERTIFICATE_ISSUE: self.check_device_root_certificate_issue} + self._health_check_method_mapping = { + HealthType.DEVICE_ROOT_CERTIFICATE_ISSUE: self.check_device_root_certificate_issue, + HealthType.DEVICE_CDSS_AND_PANORAMA_CERTIFICATE_ISSUE: self.check_cdss_and_panorama_certificate_issue, + } if not skip_force_locale: locale.setlocale( @@ -1471,7 +1474,14 @@ def check_cdss_and_panorama_certificate_issue(self): reboot_time = self._node.get_system_time_rebooted() if reboot_time < fixed_content_version_release_date: result.reason = "Device is running fixed Content but still requires a restart for the fix to take " "effect." + return result else: result.status = CheckStatus.SUCCESS + return result + + result.reason = ( + "Device is running a software version, and a content version, that is affected by the 2024 certificate" + " expiration, the first of which will occur on the 7th of April, 2024." + ) return result diff --git a/panos_upgrade_assurance/utils.py b/panos_upgrade_assurance/utils.py index b94d498..90c80f3 100644 --- a/panos_upgrade_assurance/utils.py +++ b/panos_upgrade_assurance/utils.py @@ -63,6 +63,7 @@ class HealthType: """ DEVICE_ROOT_CERTIFICATE_ISSUE = "device_root_certificate_issue" + DEVICE_CDSS_AND_PANORAMA_CERTIFICATE_ISSUE = "check_cdss_and_panorama_certificate_issue" class CheckStatus(Enum): From 137cab9f675b00ed8499d2e12cfc388fdd16deba Mon Sep 17 00:00:00 2001 From: Alp Kose Date: Sat, 27 Jan 2024 22:09:35 +0300 Subject: [PATCH 07/10] cosmatic updates --- .../readiness_checks/run_health_checks.py | 4 +-- panos_upgrade_assurance/check_firewall.py | 27 ++++++++++++------- panos_upgrade_assurance/utils.py | 2 +- tests/test_check_firewall.py | 4 +-- 4 files changed, 23 insertions(+), 14 deletions(-) diff --git a/examples/readiness_checks/run_health_checks.py b/examples/readiness_checks/run_health_checks.py index c7b04b3..697ab31 100644 --- a/examples/readiness_checks/run_health_checks.py +++ b/examples/readiness_checks/run_health_checks.py @@ -71,7 +71,7 @@ hostname=address, api_password=password, api_username=username ) firewall = FirewallProxy(serial=serial) - panorama.add(firewall._fw) + panorama.add(firewall) else: firewall = FirewallProxy( hostname=address, api_password=password, api_username=username, vsys=vsys @@ -81,7 +81,7 @@ checks = [ "device_root_certificate_issue", - "check_cdss_and_panorama_certificate_issue" + "cdss_and_panorama_certificate_issue" ] check_health = check_node.run_health_checks( diff --git a/panos_upgrade_assurance/check_firewall.py b/panos_upgrade_assurance/check_firewall.py index c54ca3a..9f2dbe8 100644 --- a/panos_upgrade_assurance/check_firewall.py +++ b/panos_upgrade_assurance/check_firewall.py @@ -1262,9 +1262,10 @@ def check_version_against_version_match_dict(version: Version, match_dict: dict) """Compare the given software version against the match dict. # Parameters - version (str): The software version to compare. Example: "10.1.11" - match_dict (dict): A dictionary of tuples mapping major/minor versions to match criteria - example + + version (str): The software version to compare. Example: "10.1.11". + match_dict (dict): A dictionary of tuples mapping major/minor versions to match criteria. For + example: ```python { @@ -1273,9 +1274,10 @@ def check_version_against_version_match_dict(version: Version, match_dict: dict) } ``` - Returns + # Returns bool: `True` If the given software version matches the provided match criteria + """ match_versions = match_dict.get(f"{version.major}{version.minor}") if match_versions: @@ -1315,6 +1317,7 @@ def check_device_root_certificate_issue(self, fail_when_affected_version_only: b * [`CheckStatus.SUCCESS`](/panos/docs/panos-upgrade-assurance/api/utils#class-checkstatus) if the device is not affected, * [`CheckStatus.FAIL`](/panos/docs/panos-upgrade-assurance/api/utils#class-checkstatus) otherwise. + """ result = CheckResult() @@ -1360,11 +1363,9 @@ def check_device_root_certificate_issue(self, fail_when_affected_version_only: b } fixed_content_version = 8776.8390 + # If the device is already running fixed software, we can return immediately if self.check_version_against_version_match_dict(software_version, fixed_version_map): result.status = CheckStatus.SUCCESS - - # If the device is already running fixed software, we can return immediately - if result.status == CheckStatus.SUCCESS: return result # Return if this check is just looking at the software and not implementing any other checks @@ -1410,9 +1411,9 @@ def check_device_root_certificate_issue(self, fail_when_affected_version_only: b return result def check_cdss_and_panorama_certificate_issue(self): - """Checks whether the device is affected by the following advisory; + """Checks whether the device is affected by the [PAN-OS Certificate Expirations Jan 2024 advisory][live-572158]. - https://live.paloaltonetworks.com/t5/customer-advisories/additional-pan-os-certificate-expirations-and-new-comprehensive/ta-p/572158 + [live-572158]: https://live.paloaltonetworks.com/t5/customer-advisories/additional-pan-os-certificate-expirations-and-new-comprehensive/ta-p/572158 Check will fail in either of following scenarios: @@ -1421,6 +1422,14 @@ def check_cdss_and_panorama_certificate_issue(self): * Device is running the fixed content version or higher but has not been rebooted - note this is best effort, and is based on when the content version was released and the device was rebooted + # Returns + + CheckResult: Object of [`CheckResult`](/panos/docs/panos-upgrade-assurance/api/utils#class-checkresult) class taking \ + value of: + + * [`CheckStatus.SUCCESS`](/panos/docs/panos-upgrade-assurance/api/utils#class-checkstatus) if the device is not affected, + * [`CheckStatus.FAIL`](/panos/docs/panos-upgrade-assurance/api/utils#class-checkstatus) otherwise. + """ fixed_version_map = { "81": [("==", "8.1.21.3"), ("==", "8.1.25.3"), (">=", "8.1.26")], diff --git a/panos_upgrade_assurance/utils.py b/panos_upgrade_assurance/utils.py index 90c80f3..71fce4d 100644 --- a/panos_upgrade_assurance/utils.py +++ b/panos_upgrade_assurance/utils.py @@ -63,7 +63,7 @@ class HealthType: """ DEVICE_ROOT_CERTIFICATE_ISSUE = "device_root_certificate_issue" - DEVICE_CDSS_AND_PANORAMA_CERTIFICATE_ISSUE = "check_cdss_and_panorama_certificate_issue" + DEVICE_CDSS_AND_PANORAMA_CERTIFICATE_ISSUE = "cdss_and_panorama_certificate_issue" class CheckStatus(Enum): diff --git a/tests/test_check_firewall.py b/tests/test_check_firewall.py index 0492dcc..39e22d2 100644 --- a/tests/test_check_firewall.py +++ b/tests/test_check_firewall.py @@ -1315,7 +1315,7 @@ def test_run_health_checks(self, check_firewall_mock): ("10.1.13", CheckStatus.SUCCESS), # Device running fixed version ], ) - def test_check_cdss_and_panorama_certificate_issue(self, check_firewall_mock, running_software, expected_status): + def test_check_cdss_and_panorama_certificate_issue(self, running_software, expected_status, check_firewall_mock): """This test validates the behavior when the test is only checking the software version is affected by the issue.""" @@ -1333,7 +1333,7 @@ def test_check_cdss_and_panorama_certificate_issue(self, check_firewall_mock, ru ], ) def test_check_cdss_and_panorama_certificate_issue_by_content_version( - self, check_firewall_mock, running_content_version, expected_status, last_reboot + self, running_content_version, last_reboot, expected_status, check_firewall_mock ): """Tests that we check the content version and use a best effort approach for seeing if the device has been rebooted in the time since it was released/installed""" From e37a904234ec50d440b91cdd29e860f248c0fe71 Mon Sep 17 00:00:00 2001 From: Alp Kose Date: Sat, 27 Jan 2024 22:40:45 +0300 Subject: [PATCH 08/10] conventional updates --- .../api/check_firewall.md | 37 ++++++++++++------- panos_upgrade_assurance/check_firewall.py | 12 +++--- 2 files changed, 30 insertions(+), 19 deletions(-) diff --git a/docs/panos-upgrade-assurance/api/check_firewall.md b/docs/panos-upgrade-assurance/api/check_firewall.md index 641dde1..0a5729f 100644 --- a/docs/panos-upgrade-assurance/api/check_firewall.md +++ b/docs/panos-upgrade-assurance/api/check_firewall.md @@ -691,27 +691,29 @@ __Returns__ ```python @staticmethod def check_version_against_version_match_dict(version: Version, - match_dict: dict) + match_dict: dict) -> bool ``` Compare the given software version against the match dict. -# Parameters -version (str): The software version to compare. Example: "10.1.11" -match_dict (dict): A dictionary of tuples mapping major/minor versions to match criteria -example - +__Parameters__ -Returns -bool: `True` If the given software version matches the provided match criteria +- __version__ (`Version`): The software version to compare (e.g. "10.1.11"). +- __match_dict__ (`dict`): A dictionary of tuples mapping major/minor versions to match criteria. + For example; ```python { - "81": [("==", "8.1.21.2"), (">=", "8.1.25.1")], - "90": [(">=", "9.0.16.5")], +- __"81"__: [("==", "8.1.21.2"), (">=", "8.1.25.1")], +- __"90"__: [(">=", "9.0.16.5")], } ``` +__Returns__ + + +`bool`: `True` If the given software version matches the provided match criteria + ### `CheckFirewall.check_device_root_certificate_issue` ```python @@ -750,12 +752,12 @@ __Returns__ ### `CheckFirewall.check_cdss_and_panorama_certificate_issue` ```python -def check_cdss_and_panorama_certificate_issue() +def check_cdss_and_panorama_certificate_issue() -> CheckResult ``` -Checks whether the device is affected by the following advisory; +Checks whether the device is affected by the [PAN-OS Certificate Expirations Jan 2024 advisory][live-572158]. -https://live.paloaltonetworks.com/t5/customer-advisories/additional-pan-os-certificate-expirations-and-new-comprehensive/ta-p/572158 +[live-572158]: https://live.paloaltonetworks.com/t5/customer-advisories/additional-pan-os-certificate-expirations-and-new-comprehensive/ta-p/572158 Check will fail in either of following scenarios: @@ -763,3 +765,12 @@ Check will fail in either of following scenarios: * Device is running an affected content version * Device is running the fixed content version or higher but has not been rebooted - note this is best effort, and is based on when the content version was released and the device was rebooted + +__Returns__ + + +`CheckResult`: Object of [`CheckResult`](/panos/docs/panos-upgrade-assurance/api/utils#class-checkresult) class taking value of: + +* [`CheckStatus.SUCCESS`](/panos/docs/panos-upgrade-assurance/api/utils#class-checkstatus) if the device is not affected, +* [`CheckStatus.FAIL`](/panos/docs/panos-upgrade-assurance/api/utils#class-checkstatus) otherwise. + diff --git a/panos_upgrade_assurance/check_firewall.py b/panos_upgrade_assurance/check_firewall.py index 9f2dbe8..35f220e 100644 --- a/panos_upgrade_assurance/check_firewall.py +++ b/panos_upgrade_assurance/check_firewall.py @@ -1258,15 +1258,14 @@ def run_health_checks( return result @staticmethod - def check_version_against_version_match_dict(version: Version, match_dict: dict): + def check_version_against_version_match_dict(version: Version, match_dict: dict) -> bool: """Compare the given software version against the match dict. # Parameters - version (str): The software version to compare. Example: "10.1.11". - match_dict (dict): A dictionary of tuples mapping major/minor versions to match criteria. For - example: - + version (Version): The software version to compare (e.g. "10.1.11"). + match_dict (dict): A dictionary of tuples mapping major/minor versions to match criteria. + For example; ```python { "81": [("==", "8.1.21.2"), (">=", "8.1.25.1")], @@ -1289,6 +1288,7 @@ def check_version_against_version_match_dict(version: Version, match_dict: dict) elif operator == ">=": if version >= match_version: return True + return False def check_device_root_certificate_issue(self, fail_when_affected_version_only: bool = True) -> CheckResult: """Checks whether the target device is affected by the [Root Certificate Expiration][live-564672] issue. @@ -1410,7 +1410,7 @@ def check_device_root_certificate_issue(self, fail_when_affected_version_only: b ) return result - def check_cdss_and_panorama_certificate_issue(self): + def check_cdss_and_panorama_certificate_issue(self) -> CheckResult: """Checks whether the device is affected by the [PAN-OS Certificate Expirations Jan 2024 advisory][live-572158]. [live-572158]: https://live.paloaltonetworks.com/t5/customer-advisories/additional-pan-os-certificate-expirations-and-new-comprehensive/ta-p/572158 From 9449c26e676e047a7abaceba74683101a06e75b0 Mon Sep 17 00:00:00 2001 From: Alp Kose Date: Sun, 28 Jan 2024 14:38:05 +0300 Subject: [PATCH 09/10] docs: fix param example --- .../api/check_firewall.md | 16 ++++++++-------- panos_upgrade_assurance/check_firewall.py | 16 ++++++++-------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/panos-upgrade-assurance/api/check_firewall.md b/docs/panos-upgrade-assurance/api/check_firewall.md index 0a5729f..72dc44c 100644 --- a/docs/panos-upgrade-assurance/api/check_firewall.md +++ b/docs/panos-upgrade-assurance/api/check_firewall.md @@ -700,14 +700,14 @@ __Parameters__ - __version__ (`Version`): The software version to compare (e.g. "10.1.11"). -- __match_dict__ (`dict`): A dictionary of tuples mapping major/minor versions to match criteria. - For example; - ```python - { -- __"81"__: [("==", "8.1.21.2"), (">=", "8.1.25.1")], -- __"90"__: [(">=", "9.0.16.5")], - } - ``` +- __match_dict__ (`dict`): A dictionary of tuples mapping major/minor versions to match criteria: + +```python showLineNumbers title="Example" +{ + "81": [("==", "8.1.21.2"), (">=", "8.1.25.1")], + "90": [(">=", "9.0.16.5")], +} +``` __Returns__ diff --git a/panos_upgrade_assurance/check_firewall.py b/panos_upgrade_assurance/check_firewall.py index 35f220e..31061cd 100644 --- a/panos_upgrade_assurance/check_firewall.py +++ b/panos_upgrade_assurance/check_firewall.py @@ -1264,14 +1264,14 @@ def check_version_against_version_match_dict(version: Version, match_dict: dict) # Parameters version (Version): The software version to compare (e.g. "10.1.11"). - match_dict (dict): A dictionary of tuples mapping major/minor versions to match criteria. - For example; - ```python - { - "81": [("==", "8.1.21.2"), (">=", "8.1.25.1")], - "90": [(">=", "9.0.16.5")], - } - ``` + match_dict (dict): A dictionary of tuples mapping major/minor versions to match criteria: + + ```python showLineNumbers title="Example" + { + "81": [("==", "8.1.21.2"), (">=", "8.1.25.1")], + "90": [(">=", "9.0.16.5")], + } + ``` # Returns From d51dc74d39c19df62e57d9716f8a0c1c46398e15 Mon Sep 17 00:00:00 2001 From: Alp Kose Date: Sun, 28 Jan 2024 14:46:10 +0300 Subject: [PATCH 10/10] tests: correct syntax --- tests/test_firewall_proxy.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/test_firewall_proxy.py b/tests/test_firewall_proxy.py index 676bf59..8230eaa 100644 --- a/tests/test_firewall_proxy.py +++ b/tests/test_firewall_proxy.py @@ -1776,8 +1776,7 @@ def test_get_system_time_rebooted(self, fw_proxy_mock): 1.1.1.1 no ab:cd:ef:11:22:33 - + 5 days, 1:02:03 testfw 7000