diff --git a/aci-preupgrade-validation-script.py b/aci-preupgrade-validation-script.py index ebe0477..6a513aa 100644 --- a/aci-preupgrade-validation-script.py +++ b/aci-preupgrade-validation-script.py @@ -6007,6 +6007,37 @@ def apic_vmm_inventory_sync_faults_check(**kwargs): recommended_action=recommended_action, doc_url=doc_url) + +@check_wrapper(check_title='OSPFv3 IPSec ESP ESN stuck in 0') +def ospfv3_ipsec_esn_check(tversion, **kwargs): + result = PASS + headers = ["dn","Policy Name"] + data = [] + recommended_action = 'Contact cisco TAC for Support.' + doc_url = 'https://datacenter.github.io/ACI-Pre-Upgrade-Validation-Script/validations/#ospfv3-ipsec-esn-stuck-in-0' + + if not tversion: + return Result(result=MANUAL, msg=TVER_MISSING) + + if (tversion.newer_than("6.1(1f)") or tversion.same_as("6.1(1f)")) and (tversion.older_than("6.1(4h)") or tversion.same_as("6.1(4h)")): + esp_config_api = 'fvProtoAuthPol.json?query-target-filter=and(wcard(fvProtoAuthPol.proto,"esp"))' + + esp_configs = icurl('class', esp_config_api) + + if esp_configs: + for esp in esp_configs: + policy_name = esp['fvProtoAuthPol']['attributes']['name'] + dn = esp['fvProtoAuthPol']['attributes']['dn'] + data.append([dn, policy_name]) + + else: + return Result(result=NA, msg=VER_NOT_AFFECTED) + + if data: + result = FAIL_O + + return Result(result=result, headers=headers, data=data, recommended_action=recommended_action, doc_url=doc_url) + # ---- Script Execution ---- @@ -6168,6 +6199,7 @@ class CheckManager: standby_sup_sync_check, isis_database_byte_check, configpush_shard_check, + ospfv3_ipsec_esn_check, ] ssh_checks = [ diff --git a/docs/docs/validations.md b/docs/docs/validations.md index fa1fc0e..34efba6 100644 --- a/docs/docs/validations.md +++ b/docs/docs/validations.md @@ -190,7 +190,8 @@ Items | Defect | This Script [Observer Database Size][d25] | CSCvw45531 | :white_check_mark: | :no_entry_sign: [Stale pconsRA Object][d26] | CSCwp22212 | :warning:{title="Deprecated"} | :no_entry_sign: [ISIS DTEPs Byte Size][d27] | CSCwp15375 | :white_check_mark: | :no_entry_sign: -[Policydist configpushShardCont Crash][d28] | CSCwp95515 | :white_check_mark: | +[Policydist configpushShardCont Crash][d28] | CSCwp95515 | :white_check_mark: | :no_entry_sign: +[OSPFv3 IPSec ESP ESN stuck in 0][d29] | CSCwp66238 | :white_check_mark: | :no_entry_sign: [d1]: #ep-announce-compatibility [d2]: #eventmgr-db-size-defect-susceptibility @@ -220,6 +221,7 @@ Items | Defect | This Script [d26]: #stale-pconsra-object [d27]: #isis-dteps-byte-size [d28]: #policydist-configpushshardcont-crash +[d29]: #ospfv3-ipsec-esp-esn-stuck-in-0 ## General Check Details @@ -2614,6 +2616,15 @@ Due to [CSCwp95515][59], upgrading to an affected version while having any `conf If any instances of `configpushShardCont` are flagged by this script, Cisco TAC must be contacted to identify and resolve the underlying issue before performing the upgrade. +### OSPFv3 IPSec ESP ESN stuck in 0 + +OSPFv3 (Open Shortest Path First for IPv6) can be protected with IPSec ESP, which encrypts and authenticates routing protocol packets. According to RFC4303, the ESP sequence number is a per-SA (Security Association) 32-bit counter that must increment by one for each packet sent. + +The bug [CSCwp66238][62] states that all ESP packets sent between ACI and NX-OS have seq=0, no matter how many packets are sent. The ESP implementation in both ACI and NX-OS is not incrementing the ESP sequence number per packet. The value stays at 0 after the Security Association (SA) is established. OSPFv3 adjacencies with ESP encryption may fail or become unstable when traversing devices that check for ESP sequence number increments, such as security appliances. + +The script identifies if the ESP protocol is configurd by checking the `fvProtoAuthPol` object. If present, then upgrade to the fixed version. + + [0]: https://github.com/datacenter/ACI-Pre-Upgrade-Validation-Script [1]: https://www.cisco.com/c/dam/en/us/td/docs/Website/datacenter/apicmatrix/index.html [2]: https://www.cisco.com/c/en/us/support/switches/nexus-9000-series-switches/products-release-notes-list.html @@ -2676,3 +2687,4 @@ If any instances of `configpushShardCont` are flagged by this script, Cisco TAC [59]: https://bst.cloudapps.cisco.com/bugsearch/bug/CSCwp95515 [60]: https://www.cisco.com/c/en/us/solutions/collateral/data-center-virtualization/application-centric-infrastructure/white-paper-c11-743951.html#Inter [61]: https://www.cisco.com/c/en/us/solutions/collateral/data-center-virtualization/application-centric-infrastructure/white-paper-c11-743951.html#EnablePolicyCompression +[62]: https://bst.cloudapps.cisco.com/bugsearch/bug/CSCwp66238 diff --git a/tests/checks/ospfv3_ipsec_esn_check/esp_enabled.json b/tests/checks/ospfv3_ipsec_esn_check/esp_enabled.json new file mode 100644 index 0000000..8e9d48d --- /dev/null +++ b/tests/checks/ospfv3_ipsec_esn_check/esp_enabled.json @@ -0,0 +1,71 @@ +[ + { + "fvProtoAuthPol": { + "attributes": { + "annotation": "", + "childAction": "", + "descr": "", + "dn": "uni/tn-ten1ant/protoAuthPol-ipsec_pol1", + "extMngdBy": "", + "lcOwn": "local", + "modTs": "2025-12-23T10:13:32.132+00:00", + "monPolDn": "uni/tn-common/monepg-default", + "name": "ipsec_pol1", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "", + "proto": "esp", + "spi": "256", + "status": "", + "uid": "15374", + "userdom": ":all:" + } + } + }, + { + "fvProtoAuthPol": { + "attributes": { + "annotation": "", + "childAction": "", + "descr": "", + "dn": "uni/tn-ten1ant/protoAuthPol-ipsec_pol2", + "extMngdBy": "", + "lcOwn": "local", + "modTs": "2025-12-23T10:13:32.132+00:00", + "monPolDn": "uni/tn-common/monepg-pol1_1", + "name": "ipsec_pol2", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "", + "proto": "esp", + "spi": "256", + "status": "", + "uid": "15374", + "userdom": ":all:" + } + } + }, + { + "fvProtoAuthPol": { + "attributes": { + "annotation": "", + "childAction": "", + "descr": "", + "dn": "uni/tn-ten2ant/protoAuthPol-ipsec_pol3", + "extMngdBy": "", + "lcOwn": "local", + "modTs": "2025-12-23T10:13:32.132+00:00", + "monPolDn": "uni/tn-common/monepg-pol5_2", + "name": "ipsec_pol3", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "", + "proto": "esp", + "spi": "256", + "status": "", + "uid": "15374", + "userdom": ":all:" + } + } + } +] diff --git a/tests/checks/ospfv3_ipsec_esn_check/esp_not_enabled.json b/tests/checks/ospfv3_ipsec_esn_check/esp_not_enabled.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/tests/checks/ospfv3_ipsec_esn_check/esp_not_enabled.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/tests/checks/ospfv3_ipsec_esn_check/test_ospfv3_ipsec_esn_check.py b/tests/checks/ospfv3_ipsec_esn_check/test_ospfv3_ipsec_esn_check.py new file mode 100644 index 0000000..35c19c7 --- /dev/null +++ b/tests/checks/ospfv3_ipsec_esn_check/test_ospfv3_ipsec_esn_check.py @@ -0,0 +1,93 @@ +import os +import pytest +import logging +import importlib +from helpers.utils import read_data + +log = logging.getLogger(__name__) +dir = os.path.dirname(os.path.abspath(__file__)) + +script = importlib.import_module("aci-preupgrade-validation-script") + +test_function = "ospfv3_ipsec_esn_check" + +esp_config_api = 'fvProtoAuthPol.json?query-target-filter=and(wcard(fvProtoAuthPol.proto,"esp"))' + +@pytest.mark.parametrize( + "icurl_outputs, tversion, expected_result", + [ + # PASS cases + # Targer version in affected range, IPSec ESP not enabled + ( + {esp_config_api: read_data(dir, "esp_not_enabled.json")}, + "6.1(3f)", + script.PASS, + ), + # Target version in lower affected version, IPSec ESP not enabled + ( + {esp_config_api: read_data(dir, "esp_not_enabled.json")}, + "6.1(1f)", + script.PASS, + ), + # Target version in upper affected version, IPSec ESP not enabled + ( + {esp_config_api: read_data(dir, "esp_not_enabled.json")}, + "6.1(4h)", + script.PASS, + ), + # Not Applicable cases + # Target version below affected range + ( + {esp_config_api: read_data(dir, "esp_enabled.json")}, + "6.0(9e)", + script.NA, + ), + # Target version above affected range + ( + {esp_config_api: read_data(dir, "esp_enabled.json")}, + "6.2(2f)", + script.NA, + ), + # Manual cases + # Target version not provided + ( + {esp_config_api: read_data(dir, "esp_enabled.json")}, + "", + script.MANUAL, + ), + # Fail cases + # Target version in affected range, IPSec ESP enabled + ( + {esp_config_api: read_data(dir, "esp_enabled.json")}, + "6.1(3f)", + script.FAIL_O, + ), + # Target version in lower affected version, IPSec ESP enabled + ( + {esp_config_api: read_data(dir, "esp_enabled.json")}, + "6.1(1f)", + script.FAIL_O, + ), + # Target version in upper affected version, IPSec ESP enabled + ( + {esp_config_api: read_data(dir, "esp_enabled.json")}, + "6.1(4h)", + script.FAIL_O, + ), + ], + ids = [ + "PASS - Target version in affected range, IPSec ESP not enabled", + "PASS - Target version in lower affected version, IPSec ESP not enabled", + "PASS - Target version in upper affected version, IPSec ESP not enabled", + "NA - Target version below affected range", + "NA - Target version above affected range", + "MANUAL - Target version not provided", + "FAIL - Target version in affected range, IPSec ESP enabled", + "FAIL - Target version in lower affected version, IPSec ESP enabled", + "FAIL - Target version in upper affected version, IPSec ESP enabled", + ] +) +def test_ospfv3_ipsec_esn_check(run_check, mock_icurl, tversion, expected_result): + """Test OSPFv3 IPsec ESN Check""" + result = run_check(tversion = script.AciVersion(tversion) if tversion else None) + assert result.result == expected_result \ No newline at end of file