Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(anta.tests): Cleaning up Security tests module (VerifySpecificIPSecConn) #933

Merged
merged 10 commits into from
Dec 4, 2024
61 changes: 61 additions & 0 deletions anta/input_models/security.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Copyright (c) 2023-2024 Arista Networks, Inc.
# Use of this source code is governed by the Apache License 2.0
# that can be found in the LICENSE file.
"""Module containing input models for security tests."""

from __future__ import annotations

from ipaddress import IPv4Address
from typing import Any
from warnings import warn

from pydantic import BaseModel, ConfigDict


class IPSecPeer(BaseModel):
"""IPSec (Internet Protocol Security) model represents the details of an IPv4 security peer."""

model_config = ConfigDict(extra="forbid")
peer: IPv4Address
"""The IPv4 address of the security peer."""
vrf: str = "default"
"""VRF context. Defaults to `default`."""
connections: list[IPSecConn] | None = None
"""A list of IPv4 security connections associated with the peer. Defaults to None."""

def __str__(self) -> str:
"""Return a string representation of the IPSecPeer model. Used in failure messages.

Examples
--------
- Peer: 1.1.1.1 VRF: default
"""
return f"Peer: {self.peer} VRF: {self.vrf}"


class IPSecConn(BaseModel):
"""Details of an IPv4 security connection for a peer."""

model_config = ConfigDict(extra="forbid")
source_address: IPv4Address
"""The IPv4 address of the source in the security connection."""
destination_address: IPv4Address
"""The IPv4 address of the destination in the security connection."""


class IPSecPeers(IPSecPeer): # pragma: no cover
"""Alias for the IPSecPeers model to maintain backward compatibility.

When initialized, it will emit a deprecation warning and call the IPSecPeer model.

TODO: Remove this class in ANTA v2.0.0.
"""

def __init__(self, **data: Any) -> None: # noqa: ANN401
"""Initialize the IPSecPeer class, emitting a deprecation warning."""
warn(
message="IPSecPeers model is deprecated and will be removed in ANTA v2.0.0. Use the IPSecPeer model instead.",
category=DeprecationWarning,
stacklevel=2,
)
super().__init__(**data)
66 changes: 24 additions & 42 deletions anta/tests/security.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@
# Mypy does not understand AntaTest.Input typing
# mypy: disable-error-code=attr-defined
from datetime import datetime, timezone
from ipaddress import IPv4Address
from typing import TYPE_CHECKING, ClassVar, get_args

from pydantic import BaseModel, Field, model_validator

from anta.custom_types import EcdsaKeySize, EncryptionAlgorithm, PositiveInteger, RsaKeySize
from anta.input_models.security import IPSecPeer, IPSecPeers
from anta.models import AntaCommand, AntaTemplate, AntaTest
from anta.tools import get_failed_logs, get_item, get_value

Expand Down Expand Up @@ -692,15 +692,22 @@ def test(self) -> None:


class VerifySpecificIPSecConn(AntaTest):
"""Verifies the state of IPv4 security connections for a specified peer.
"""Verifies the IPv4 security connections.

It optionally allows for the verification of a specific path for a peer by providing source and destination addresses.
If these addresses are not provided, it will verify all paths for the specified peer.
This test performs the following checks for each peer:

1. Validates that the VRF is configured.
2. Checks for the presence of IPv4 security connections for the specified peer.
3. For each relevant peer:
- If source and destination addresses are provided, verifies the security connection for the specific path exists and is `Established`.
- If no addresses are provided, verifies that all security connections associated with the peer are `Established`.

Expected Results
----------------
* Success: The test passes if the IPv4 security connection for a peer is established in the specified VRF.
* 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.
* Success: If all checks pass for all specified IPv4 security connections.
* Failure: If any of the following occur:
- No IPv4 security connections are found for the peer
- The security connection is not established for the specified path or any of the peer connections is not established when no path is specified.

Examples
--------
Expand All @@ -719,35 +726,16 @@ class VerifySpecificIPSecConn(AntaTest):
```
"""

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

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

ip_security_connections: list[IPSecPeers]
ip_security_connections: list[IPSecPeer]
"""List of IP4v security peers."""

class IPSecPeers(BaseModel):
"""Details of IPv4 security peers."""

peer: IPv4Address
"""IPv4 address of the peer."""

vrf: str = "default"
"""Optional VRF for the IP security peer."""

connections: list[IPSecConn] | None = None
"""Optional list of IPv4 security connections of a peer."""

class IPSecConn(BaseModel):
"""Details of IPv4 security connections for a peer."""

source_address: IPv4Address
"""Source IPv4 address of the connection."""
destination_address: IPv4Address
"""Destination IPv4 address of the connection."""
IPSecPeers: ClassVar[type[IPSecPeers]] = IPSecPeers
gmuloc marked this conversation as resolved.
Show resolved Hide resolved
"""To maintain backward compatibility."""

def render(self, template: AntaTemplate) -> list[AntaCommand]:
"""Render the template for each input IP Sec connection."""
Expand All @@ -757,15 +745,15 @@ def render(self, template: AntaTemplate) -> list[AntaCommand]:
def test(self) -> None:
"""Main test function for VerifySpecificIPSecConn."""
self.result.is_success()

for command_output, input_peer in zip(self.instance_commands, self.inputs.ip_security_connections):
conn_output = command_output.json_output["connections"]
peer = command_output.params.peer
vrf = command_output.params.vrf
conn_input = input_peer.connections
vrf = input_peer.vrf

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

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

Expand All @@ -794,14 +780,10 @@ def test(self) -> None:
if (source_input, destination_input, vrf) in existing_connections:
existing_state = existing_connections[(source_input, destination_input, vrf)]
if existing_state != "Established":
self.result.is_failure(
f"Expected state of IPv4 security connection `source:{source_input} destination:{destination_input} vrf:{vrf}` "
f"for peer `{peer}` is `Established` but found `{existing_state}` instead."
)
failure = f"Expected: Established, Actual: {existing_state}"
self.result.is_failure(f"{input_peer} Source: {source_input} Destination: {destination_input} - Connection down - {failure}")
else:
self.result.is_failure(
f"IPv4 security connection `source:{source_input} destination:{destination_input} vrf:{vrf}` for peer `{peer}` is not found."
)
self.result.is_failure(f"{input_peer} Source: {source_input} Destination: {destination_input} - Connection not found.")


class VerifyHardwareEntropy(AntaTest):
Expand Down
18 changes: 18 additions & 0 deletions docs/api/tests.security.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ anta_title: ANTA catalog for security tests
~ that can be found in the LICENSE file.
-->

# Tests

::: anta.tests.security

options:
show_root_heading: false
show_root_toc_entry: false
Expand All @@ -18,3 +21,18 @@ anta_title: ANTA catalog for security tests
filters:
- "!test"
- "!render"

# Input models

::: anta.input_models.security

options:
show_root_heading: false
show_root_toc_entry: false
show_bases: false
merge_init_into_class: false
anta_hide_test_module_description: true
show_labels: true
filters:
- "!^__init__"
- "!^__str__"
2 changes: 1 addition & 1 deletion examples/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -640,7 +640,7 @@ anta.tests.security:
- VerifySSHStatus:
# Verifies if the SSHD agent is disabled in the default VRF.
- VerifySpecificIPSecConn:
# Verifies IPv4 security connections for a peer.
# Verifies the IPv4 security connections.
ip_security_connections:
- peer: 10.255.0.1
- peer: 10.255.0.2
Expand Down
24 changes: 9 additions & 15 deletions tests/units/anta_tests/test_security.py
Original file line number Diff line number Diff line change
Expand Up @@ -1079,7 +1079,7 @@
},
]
},
"expected": {"result": "failure", "messages": ["No IPv4 security connection configured for peer `10.255.0.1`."]},
"expected": {"result": "failure", "messages": ["Peer: 10.255.0.1 VRF: default - Not configured"]},
},
{
"name": "failure-not-established",
Expand Down Expand Up @@ -1127,14 +1127,10 @@
"expected": {
"result": "failure",
"messages": [
"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` "
"but found `Idle` instead.",
"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` "
"but found `Idle` instead.",
"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` "
"but found `Idle` instead.",
"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` "
"but found `Idle` instead.",
"Peer: 10.255.0.1 VRF: default Source: 172.18.3.2 Destination: 172.18.2.2 - Connection down - Expected: Established, Actual: Idle",
"Peer: 10.255.0.1 VRF: default Source: 100.64.2.2 Destination: 100.64.1.2 - Connection down - Expected: Established, Actual: Idle",
"Peer: 10.255.0.2 VRF: MGMT Source: 100.64.2.2 Destination: 100.64.1.2 - Connection down - Expected: Established, Actual: Idle",
"Peer: 10.255.0.2 VRF: MGMT Source: 172.18.2.2 Destination: 172.18.1.2 - Connection down - Expected: Established, Actual: Idle",
],
},
},
Expand Down Expand Up @@ -1194,12 +1190,10 @@
"expected": {
"result": "failure",
"messages": [
"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` "
"but found `Idle` instead.",
"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` "
"but found `Idle` instead.",
"IPv4 security connection `source:100.64.4.2 destination:100.64.1.2 vrf:default` for peer `10.255.0.2` is not found.",
"IPv4 security connection `source:172.18.4.2 destination:172.18.1.2 vrf:default` for peer `10.255.0.2` is not found.",
"Peer: 10.255.0.1 VRF: default Source: 172.18.3.2 Destination: 172.18.2.2 - Connection down - Expected: Established, Actual: Idle",
"Peer: 10.255.0.1 VRF: default Source: 100.64.3.2 Destination: 100.64.2.2 - Connection down - Expected: Established, Actual: Idle",
"Peer: 10.255.0.2 VRF: default Source: 100.64.4.2 Destination: 100.64.1.2 - Connection not found.",
"Peer: 10.255.0.2 VRF: default Source: 172.18.4.2 Destination: 172.18.1.2 - Connection not found.",
],
},
},
Expand Down
Loading