diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 42b0ce1cb..b0bf3c72d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -46,7 +46,7 @@ repos: - '' - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.9.1 + rev: v0.9.2 hooks: - id: ruff name: Run Ruff linter diff --git a/anta/input_models/snmp.py b/anta/input_models/snmp.py index bd7350e45..d408d9311 100644 --- a/anta/input_models/snmp.py +++ b/anta/input_models/snmp.py @@ -9,7 +9,7 @@ from pydantic import BaseModel, ConfigDict -from anta.custom_types import Hostname, SnmpEncryptionAlgorithm, SnmpHashingAlgorithm, SnmpVersion +from anta.custom_types import Hostname, Interface, SnmpEncryptionAlgorithm, SnmpHashingAlgorithm, SnmpVersion class SnmpHost(BaseModel): @@ -54,3 +54,21 @@ def __str__(self) -> str: - User: Test Group: Test_Group Version: v2c """ return f"User: {self.username} Group: {self.group_name} Version: {self.version}" + + +class SnmpSourceInterface(BaseModel): + """Model for a SNMP source-interface.""" + + interface: Interface + """Interface to use as the source IP address of SNMP messages.""" + vrf: str = "default" + """VRF of the source interface.""" + + def __str__(self) -> str: + """Return a human-readable string representation of the SnmpSourceInterface for reporting. + + Examples + -------- + - Source Interface: Ethernet1 VRF: default + """ + return f"Source Interface: {self.interface} VRF: {self.vrf}" diff --git a/anta/tests/snmp.py b/anta/tests/snmp.py index 910d5929c..84c5470e6 100644 --- a/anta/tests/snmp.py +++ b/anta/tests/snmp.py @@ -12,7 +12,7 @@ from pydantic import field_validator from anta.custom_types import PositiveInteger, SnmpErrorCounter, SnmpPdu -from anta.input_models.snmp import SnmpHost, SnmpUser +from anta.input_models.snmp import SnmpHost, SnmpSourceInterface, SnmpUser from anta.models import AntaCommand, AntaTest from anta.tools import get_value @@ -489,3 +489,57 @@ def test(self) -> None: if user.priv_type and (act_encryption := get_value(user_details, "v3Params.privType", "Not Found")) != user.priv_type: self.result.is_failure(f"{user} - Incorrect privacy type - Expected: {user.priv_type} Actual: {act_encryption}") + + +class VerifySnmpSourceInterface(AntaTest): + """Verifies SNMP source interfaces. + + This test performs the following checks: + + 1. Verifies that source interface(s) are configured for SNMP. + 2. For each specified source interface: + - Interface is configured in the specified VRF. + + Expected Results + ---------------- + * Success: The test will pass if the provided SNMP source interface(s) are configured in their specified VRF. + * Failure: The test will fail if any of the provided SNMP source interface(s) are NOT configured in their specified VRF. + + Examples + -------- + ```yaml + anta.tests.snmp: + - VerifySnmpSourceInterface: + interfaces: + - interface: Ethernet1 + vrf: default + - interface: Management0 + vrf: MGMT + ``` + """ + + categories: ClassVar[list[str]] = ["snmp"] + commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show snmp", revision=1)] + + class Input(AntaTest.Input): + """Input model for the VerifySnmpSourceInterface test.""" + + interfaces: list[SnmpSourceInterface] + """List of source interfaces.""" + + @AntaTest.anta_test + def test(self) -> None: + """Main test function for VerifySnmpSourceInterface.""" + self.result.is_success() + command_output = self.instance_commands[0].json_output.get("srcIntf", {}) + + if not (interface_output := command_output.get("sourceInterfaces")): + self.result.is_failure("SNMP source interface(s) not configured") + return + + for interface_details in self.inputs.interfaces: + # If the source interface is not configured, or if it does not match the expected value, the test fails. + if not (actual_interface := interface_output.get(interface_details.vrf)): + self.result.is_failure(f"{interface_details} - Not configured") + elif actual_interface != interface_details.interface: + self.result.is_failure(f"{interface_details} - Incorrect source interface - Actual: {actual_interface}") diff --git a/examples/tests.yaml b/examples/tests.yaml index 4c0529225..4bc55a94f 100644 --- a/examples/tests.yaml +++ b/examples/tests.yaml @@ -801,6 +801,13 @@ anta.tests.snmp: pdus: - outTrapPdus - inGetNextPdus + - VerifySnmpSourceInterface: + # Verifies SNMP source interfaces. + interfaces: + - interface: Ethernet1 + vrf: default + - interface: Management0 + vrf: MGMT - VerifySnmpStatus: # Verifies if the SNMP agent is enabled. vrf: default diff --git a/tests/units/anta_tests/test_snmp.py b/tests/units/anta_tests/test_snmp.py index ebbf94897..fc30ad6ce 100644 --- a/tests/units/anta_tests/test_snmp.py +++ b/tests/units/anta_tests/test_snmp.py @@ -15,6 +15,7 @@ VerifySnmpIPv6Acl, VerifySnmpLocation, VerifySnmpPDUCounters, + VerifySnmpSourceInterface, VerifySnmpStatus, VerifySnmpUser, ) @@ -536,4 +537,47 @@ ], }, }, + { + "name": "success", + "test": VerifySnmpSourceInterface, + "eos_data": [ + { + "srcIntf": {"sourceInterfaces": {"default": "Ethernet1", "MGMT": "Management0"}}, + } + ], + "inputs": {"interfaces": [{"interface": "Ethernet1", "vrf": "default"}, {"interface": "Management0", "vrf": "MGMT"}]}, + "expected": {"result": "success"}, + }, + { + "name": "failure-not-configured", + "test": VerifySnmpSourceInterface, + "eos_data": [ + { + "srcIntf": {}, + } + ], + "inputs": {"interfaces": [{"interface": "Ethernet1", "vrf": "default"}, {"interface": "Management0", "vrf": "MGMT"}]}, + "expected": {"result": "failure", "messages": ["SNMP source interface(s) not configured"]}, + }, + { + "name": "failure-incorrect-interfaces", + "test": VerifySnmpSourceInterface, + "eos_data": [ + { + "srcIntf": { + "sourceInterfaces": { + "default": "Management0", + } + }, + } + ], + "inputs": {"interfaces": [{"interface": "Ethernet1", "vrf": "default"}, {"interface": "Management0", "vrf": "MGMT"}]}, + "expected": { + "result": "failure", + "messages": [ + "Source Interface: Ethernet1 VRF: default - Incorrect source interface - Actual: Management0", + "Source Interface: Management0 VRF: MGMT - Not configured", + ], + }, + }, ]