Skip to content

Commit cc10fb0

Browse files
fix(anta.tests): Cleaning up Security tests module (VerifySpecificIPSecConn) (#933)
--------- Co-authored-by: Guillaume Mulocher <gmulocher@arista.com>
1 parent 0283a43 commit cc10fb0

File tree

5 files changed

+113
-58
lines changed

5 files changed

+113
-58
lines changed

anta/input_models/security.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# Copyright (c) 2023-2024 Arista Networks, Inc.
2+
# Use of this source code is governed by the Apache License 2.0
3+
# that can be found in the LICENSE file.
4+
"""Module containing input models for security tests."""
5+
6+
from __future__ import annotations
7+
8+
from ipaddress import IPv4Address
9+
from typing import Any
10+
from warnings import warn
11+
12+
from pydantic import BaseModel, ConfigDict
13+
14+
15+
class IPSecPeer(BaseModel):
16+
"""IPSec (Internet Protocol Security) model represents the details of an IPv4 security peer."""
17+
18+
model_config = ConfigDict(extra="forbid")
19+
peer: IPv4Address
20+
"""The IPv4 address of the security peer."""
21+
vrf: str = "default"
22+
"""VRF context. Defaults to `default`."""
23+
connections: list[IPSecConn] | None = None
24+
"""A list of IPv4 security connections associated with the peer. Defaults to None."""
25+
26+
def __str__(self) -> str:
27+
"""Return a string representation of the IPSecPeer model. Used in failure messages.
28+
29+
Examples
30+
--------
31+
- Peer: 1.1.1.1 VRF: default
32+
"""
33+
return f"Peer: {self.peer} VRF: {self.vrf}"
34+
35+
36+
class IPSecConn(BaseModel):
37+
"""Details of an IPv4 security connection for a peer."""
38+
39+
model_config = ConfigDict(extra="forbid")
40+
source_address: IPv4Address
41+
"""The IPv4 address of the source in the security connection."""
42+
destination_address: IPv4Address
43+
"""The IPv4 address of the destination in the security connection."""
44+
45+
46+
class IPSecPeers(IPSecPeer): # pragma: no cover
47+
"""Alias for the IPSecPeers model to maintain backward compatibility.
48+
49+
When initialized, it will emit a deprecation warning and call the IPSecPeer model.
50+
51+
TODO: Remove this class in ANTA v2.0.0.
52+
"""
53+
54+
def __init__(self, **data: Any) -> None: # noqa: ANN401
55+
"""Initialize the IPSecPeer class, emitting a deprecation warning."""
56+
warn(
57+
message="IPSecPeers model is deprecated and will be removed in ANTA v2.0.0. Use the IPSecPeer model instead.",
58+
category=DeprecationWarning,
59+
stacklevel=2,
60+
)
61+
super().__init__(**data)

anta/tests/security.py

Lines changed: 24 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@
88
# Mypy does not understand AntaTest.Input typing
99
# mypy: disable-error-code=attr-defined
1010
from datetime import datetime, timezone
11-
from ipaddress import IPv4Address
1211
from typing import TYPE_CHECKING, ClassVar, get_args
1312

1413
from pydantic import BaseModel, Field, model_validator
1514

1615
from anta.custom_types import EcdsaKeySize, EncryptionAlgorithm, PositiveInteger, RsaKeySize
16+
from anta.input_models.security import IPSecPeer, IPSecPeers
1717
from anta.models import AntaCommand, AntaTemplate, AntaTest
1818
from anta.tools import get_failed_logs, get_item, get_value
1919

@@ -692,15 +692,22 @@ def test(self) -> None:
692692

693693

694694
class VerifySpecificIPSecConn(AntaTest):
695-
"""Verifies the state of IPv4 security connections for a specified peer.
695+
"""Verifies the IPv4 security connections.
696696
697-
It optionally allows for the verification of a specific path for a peer by providing source and destination addresses.
698-
If these addresses are not provided, it will verify all paths for the specified peer.
697+
This test performs the following checks for each peer:
698+
699+
1. Validates that the VRF is configured.
700+
2. Checks for the presence of IPv4 security connections for the specified peer.
701+
3. For each relevant peer:
702+
- If source and destination addresses are provided, verifies the security connection for the specific path exists and is `Established`.
703+
- If no addresses are provided, verifies that all security connections associated with the peer are `Established`.
699704
700705
Expected Results
701706
----------------
702-
* Success: The test passes if the IPv4 security connection for a peer is established in the specified VRF.
703-
* Failure: The test fails if IPv4 security is not configured, a connection is not found for a peer, or the connection is not established in the specified VRF.
707+
* Success: If all checks pass for all specified IPv4 security connections.
708+
* Failure: If any of the following occur:
709+
- No IPv4 security connections are found for the peer
710+
- The security connection is not established for the specified path or any of the peer connections is not established when no path is specified.
704711
705712
Examples
706713
--------
@@ -719,35 +726,16 @@ class VerifySpecificIPSecConn(AntaTest):
719726
```
720727
"""
721728

722-
description = "Verifies IPv4 security connections for a peer."
723729
categories: ClassVar[list[str]] = ["security"]
724-
commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaTemplate(template="show ip security connection vrf {vrf} path peer {peer}")]
730+
commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaTemplate(template="show ip security connection vrf {vrf} path peer {peer}", revision=2)]
725731

726732
class Input(AntaTest.Input):
727733
"""Input model for the VerifySpecificIPSecConn test."""
728734

729-
ip_security_connections: list[IPSecPeers]
735+
ip_security_connections: list[IPSecPeer]
730736
"""List of IP4v security peers."""
731-
732-
class IPSecPeers(BaseModel):
733-
"""Details of IPv4 security peers."""
734-
735-
peer: IPv4Address
736-
"""IPv4 address of the peer."""
737-
738-
vrf: str = "default"
739-
"""Optional VRF for the IP security peer."""
740-
741-
connections: list[IPSecConn] | None = None
742-
"""Optional list of IPv4 security connections of a peer."""
743-
744-
class IPSecConn(BaseModel):
745-
"""Details of IPv4 security connections for a peer."""
746-
747-
source_address: IPv4Address
748-
"""Source IPv4 address of the connection."""
749-
destination_address: IPv4Address
750-
"""Destination IPv4 address of the connection."""
737+
IPSecPeers: ClassVar[type[IPSecPeers]] = IPSecPeers
738+
"""To maintain backward compatibility."""
751739

752740
def render(self, template: AntaTemplate) -> list[AntaCommand]:
753741
"""Render the template for each input IP Sec connection."""
@@ -757,15 +745,15 @@ def render(self, template: AntaTemplate) -> list[AntaCommand]:
757745
def test(self) -> None:
758746
"""Main test function for VerifySpecificIPSecConn."""
759747
self.result.is_success()
748+
760749
for command_output, input_peer in zip(self.instance_commands, self.inputs.ip_security_connections):
761750
conn_output = command_output.json_output["connections"]
762-
peer = command_output.params.peer
763-
vrf = command_output.params.vrf
764751
conn_input = input_peer.connections
752+
vrf = input_peer.vrf
765753

766754
# Check if IPv4 security connection is configured
767755
if not conn_output:
768-
self.result.is_failure(f"No IPv4 security connection configured for peer `{peer}`.")
756+
self.result.is_failure(f"{input_peer} - Not configured")
769757
continue
770758

771759
# If connection details are not provided then check all connections of a peer
@@ -775,10 +763,8 @@ def test(self) -> None:
775763
if state != "Established":
776764
source = conn_data.get("saddr")
777765
destination = conn_data.get("daddr")
778-
vrf = conn_data.get("tunnelNs")
779766
self.result.is_failure(
780-
f"Expected state of IPv4 security connection `source:{source} destination:{destination} vrf:{vrf}` for peer `{peer}` is `Established` "
781-
f"but found `{state}` instead."
767+
f"{input_peer} Source: {source} Destination: {destination} - Connection down - Expected: Established, Actual: {state}"
782768
)
783769
continue
784770

@@ -794,14 +780,10 @@ def test(self) -> None:
794780
if (source_input, destination_input, vrf) in existing_connections:
795781
existing_state = existing_connections[(source_input, destination_input, vrf)]
796782
if existing_state != "Established":
797-
self.result.is_failure(
798-
f"Expected state of IPv4 security connection `source:{source_input} destination:{destination_input} vrf:{vrf}` "
799-
f"for peer `{peer}` is `Established` but found `{existing_state}` instead."
800-
)
783+
failure = f"Expected: Established, Actual: {existing_state}"
784+
self.result.is_failure(f"{input_peer} Source: {source_input} Destination: {destination_input} - Connection down - {failure}")
801785
else:
802-
self.result.is_failure(
803-
f"IPv4 security connection `source:{source_input} destination:{destination_input} vrf:{vrf}` for peer `{peer}` is not found."
804-
)
786+
self.result.is_failure(f"{input_peer} Source: {source_input} Destination: {destination_input} - Connection not found.")
805787

806788

807789
class VerifyHardwareEntropy(AntaTest):

docs/api/tests.security.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ anta_title: ANTA catalog for security tests
77
~ that can be found in the LICENSE file.
88
-->
99

10+
# Tests
11+
1012
::: anta.tests.security
13+
1114
options:
1215
show_root_heading: false
1316
show_root_toc_entry: false
@@ -18,3 +21,18 @@ anta_title: ANTA catalog for security tests
1821
filters:
1922
- "!test"
2023
- "!render"
24+
25+
# Input models
26+
27+
::: anta.input_models.security
28+
29+
options:
30+
show_root_heading: false
31+
show_root_toc_entry: false
32+
show_bases: false
33+
merge_init_into_class: false
34+
anta_hide_test_module_description: true
35+
show_labels: true
36+
filters:
37+
- "!^__init__"
38+
- "!^__str__"

examples/tests.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -640,7 +640,7 @@ anta.tests.security:
640640
- VerifySSHStatus:
641641
# Verifies if the SSHD agent is disabled in the default VRF.
642642
- VerifySpecificIPSecConn:
643-
# Verifies IPv4 security connections for a peer.
643+
# Verifies the IPv4 security connections.
644644
ip_security_connections:
645645
- peer: 10.255.0.1
646646
- peer: 10.255.0.2

tests/units/anta_tests/test_security.py

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1079,7 +1079,7 @@
10791079
},
10801080
]
10811081
},
1082-
"expected": {"result": "failure", "messages": ["No IPv4 security connection configured for peer `10.255.0.1`."]},
1082+
"expected": {"result": "failure", "messages": ["Peer: 10.255.0.1 VRF: default - Not configured"]},
10831083
},
10841084
{
10851085
"name": "failure-not-established",
@@ -1127,14 +1127,10 @@
11271127
"expected": {
11281128
"result": "failure",
11291129
"messages": [
1130-
"Expected state of IPv4 security connection `source:172.18.3.2 destination:172.18.2.2 vrf:default` for peer `10.255.0.1` is `Established` "
1131-
"but found `Idle` instead.",
1132-
"Expected state of IPv4 security connection `source:100.64.2.2 destination:100.64.1.2 vrf:default` for peer `10.255.0.1` is `Established` "
1133-
"but found `Idle` instead.",
1134-
"Expected state of IPv4 security connection `source:100.64.2.2 destination:100.64.1.2 vrf:MGMT` for peer `10.255.0.2` is `Established` "
1135-
"but found `Idle` instead.",
1136-
"Expected state of IPv4 security connection `source:172.18.2.2 destination:172.18.1.2 vrf:MGMT` for peer `10.255.0.2` is `Established` "
1137-
"but found `Idle` instead.",
1130+
"Peer: 10.255.0.1 VRF: default Source: 172.18.3.2 Destination: 172.18.2.2 - Connection down - Expected: Established, Actual: Idle",
1131+
"Peer: 10.255.0.1 VRF: default Source: 100.64.2.2 Destination: 100.64.1.2 - Connection down - Expected: Established, Actual: Idle",
1132+
"Peer: 10.255.0.2 VRF: MGMT Source: 100.64.2.2 Destination: 100.64.1.2 - Connection down - Expected: Established, Actual: Idle",
1133+
"Peer: 10.255.0.2 VRF: MGMT Source: 172.18.2.2 Destination: 172.18.1.2 - Connection down - Expected: Established, Actual: Idle",
11381134
],
11391135
},
11401136
},
@@ -1194,12 +1190,10 @@
11941190
"expected": {
11951191
"result": "failure",
11961192
"messages": [
1197-
"Expected state of IPv4 security connection `source:172.18.3.2 destination:172.18.2.2 vrf:default` for peer `10.255.0.1` is `Established` "
1198-
"but found `Idle` instead.",
1199-
"Expected state of IPv4 security connection `source:100.64.3.2 destination:100.64.2.2 vrf:default` for peer `10.255.0.1` is `Established` "
1200-
"but found `Idle` instead.",
1201-
"IPv4 security connection `source:100.64.4.2 destination:100.64.1.2 vrf:default` for peer `10.255.0.2` is not found.",
1202-
"IPv4 security connection `source:172.18.4.2 destination:172.18.1.2 vrf:default` for peer `10.255.0.2` is not found.",
1193+
"Peer: 10.255.0.1 VRF: default Source: 172.18.3.2 Destination: 172.18.2.2 - Connection down - Expected: Established, Actual: Idle",
1194+
"Peer: 10.255.0.1 VRF: default Source: 100.64.3.2 Destination: 100.64.2.2 - Connection down - Expected: Established, Actual: Idle",
1195+
"Peer: 10.255.0.2 VRF: default Source: 100.64.4.2 Destination: 100.64.1.2 - Connection not found.",
1196+
"Peer: 10.255.0.2 VRF: default Source: 172.18.4.2 Destination: 172.18.1.2 - Connection not found.",
12031197
],
12041198
},
12051199
},

0 commit comments

Comments
 (0)