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

feat(anta): Added the test case to verify SNMP user #877

Merged
merged 11 commits into from
Jan 14, 2025
3 changes: 3 additions & 0 deletions anta/custom_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,3 +208,6 @@ def validate_regex(value: str) -> str:
SnmpErrorCounter = Literal[
"inVersionErrs", "inBadCommunityNames", "inBadCommunityUses", "inParseErrs", "outTooBigErrs", "outNoSuchNameErrs", "outBadValueErrs", "outGeneralErrs"
]
SnmpVersion = Literal["v1", "v2c", "v3"]
HashingAlgorithms = Literal["MD5", "SHA", "SHA-224", "SHA-256", "SHA-384", "SHA-512"]
carl-baillargeon marked this conversation as resolved.
Show resolved Hide resolved
EncryptionAlgorithms = Literal["AES-128", "AES-192", "AES-256", "DES"]
carl-baillargeon marked this conversation as resolved.
Show resolved Hide resolved
106 changes: 104 additions & 2 deletions anta/tests/snmp.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@

from typing import TYPE_CHECKING, ClassVar, get_args

from anta.custom_types import PositiveInteger, SnmpErrorCounter, SnmpPdu
from pydantic import BaseModel, model_validator

from anta.custom_types import EncryptionAlgorithms, HashingAlgorithms, PositiveInteger, SnmpErrorCounter, SnmpPdu, SnmpVersion
from anta.models import AntaCommand, AntaTest
from anta.tools import get_value
from anta.tools import get_failed_logs, get_value

if TYPE_CHECKING:
from anta.models import AntaTemplate
Expand Down Expand Up @@ -350,3 +352,103 @@ def test(self) -> None:
self.result.is_success()
else:
self.result.is_failure(f"The following SNMP error counters are not found or have non-zero error counters:\n{error_counters_not_ok}")


class VerifySnmpUser(AntaTest):
"""Verifies the SNMP user configurations for specified version(s).

- Verifies that the valid user name and group name.
- Ensures that the SNMP v3 security model, the user authentication and privacy settings aligning with version-specific requirements.

Expected Results
----------------
* Success: The test will pass if the provided SNMP user and all specified parameters are correctly configured.
* Failure: The test will fail if the provided SNMP user is not configured or specified parameters are not correctly configured.

Examples
--------
```yaml
anta.tests.snmp:
- VerifySnmpUser:
users:
- username: test
group_name: test_group
security_model: v3
authentication_type: MD5
priv_type: AES-128
```
"""

name = "VerifySnmpUser"
description = "Verifies the SNMP user configurations for specified version(s)."
categories: ClassVar[list[str]] = ["snmp"]
commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show snmp user", revision=1)]

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

users: list[SnmpUser]
MaheshGSLAB marked this conversation as resolved.
Show resolved Hide resolved
"""List of SNMP users."""

class SnmpUser(BaseModel):
"""Model for a SNMP User."""

username: str
"""SNMP user name."""
group_name: str
"""SNMP group for the user."""
security_model: SnmpVersion
"""SNMP protocol version.."""
authentication_type: HashingAlgorithms | None = None
"""User authentication settings."""
MaheshGSLAB marked this conversation as resolved.
Show resolved Hide resolved
priv_type: EncryptionAlgorithms | None = None
MaheshGSLAB marked this conversation as resolved.
Show resolved Hide resolved
"""User privacy settings."""

@model_validator(mode="after")
def validate_inputs(self: BaseModel) -> BaseModel:
"""Validate the inputs provided to the SnmpUser class."""
if self.security_model in ["v1", "v2c"] and (self.authentication_type or self.priv_type) is not None:
msg = "SNMP versions 1 and 2c, do not support encryption or advanced authentication."
MaheshGSLAB marked this conversation as resolved.
Show resolved Hide resolved
raise ValueError(msg)
return self

@AntaTest.anta_test
def test(self) -> None:
"""Main test function for VerifySnmpUser."""
self.result.is_success()
failures: str = ""

for user in self.inputs.users:
username = user.username
group_name = user.group_name
security_model = user.security_model
authentication_type = user.authentication_type
priv_type = user.priv_type

# Verify SNMP host details.
if not (user_details := get_value(self.instance_commands[0].json_output, f"usersByVersion.{security_model}.users.{username}")):
failures += f"SNMP user '{username}' is not configured with security model '{security_model}'.\n"
continue

# Update expected host details.
expected_user_details = {"user group": group_name}

# Update actual host details.
actual_user_details = {"user group": user_details.get("groupName", "Not Found")}

if authentication_type:
expected_user_details["authentication type"] = authentication_type
MaheshGSLAB marked this conversation as resolved.
Show resolved Hide resolved
actual_user_details["authentication type"] = user_details.get("v3Params", {}).get("authType", "Not Found")

if priv_type:
expected_user_details["privacy type"] = priv_type
actual_user_details["privacy type"] = user_details.get("v3Params", {}).get("privType", "Not Found")

# Collecting failures logs if any.
failure_logs = get_failed_logs(expected_user_details, actual_user_details)
if failure_logs:
failures += f"For SNMP user {username}:{failure_logs}\n"

# Check if there are any failures.
if failures:
self.result.is_failure(failures)
11 changes: 11 additions & 0 deletions examples/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,17 @@ anta.tests.snmp:
error_counters:
- inVersionErrs
- inBadCommunityNames
- VerifySnmpUser:
users:
- username: Test1
group_name: TestGroup1
security_model: v3
authentication_type: MD5
priv_type: AES-128
- username: Test2
group_name: TestGroup2
security_model: v3
authentication_type: SHA-256

anta.tests.software:
- VerifyEOSVersion:
Expand Down
133 changes: 133 additions & 0 deletions tests/units/anta_tests/test_snmp.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
VerifySnmpLocation,
VerifySnmpPDUCounters,
VerifySnmpStatus,
VerifySnmpUser,
)
from tests.units.anta_tests import test

Expand Down Expand Up @@ -319,4 +320,136 @@
],
},
},
{
"name": "success",
"test": VerifySnmpUser,
"eos_data": [
{
"usersByVersion": {
"v1": {
"users": {
"Test1": {
"groupName": "TestGroup1",
},
}
},
"v2c": {
"users": {
"Test2": {
"groupName": "TestGroup2",
},
}
},
"v3": {
"users": {
"Test3": {
"groupName": "TestGroup3",
"v3Params": {"authType": "SHA-384", "privType": "AES-128"},
},
"Test4": {"groupName": "TestGroup3", "v3Params": {"authType": "SHA-512", "privType": "AES-192"}},
}
},
}
}
],
"inputs": {
"users": [
{"username": "Test1", "group_name": "TestGroup1", "security_model": "v1"},
{"username": "Test2", "group_name": "TestGroup2", "security_model": "v2c"},
{"username": "Test3", "group_name": "TestGroup3", "security_model": "v3", "authentication_type": "SHA-384", "priv_type": "AES-128"},
{"username": "Test4", "group_name": "TestGroup3", "security_model": "v3", "authentication_type": "SHA-512", "priv_type": "AES-192"},
]
},
"expected": {"result": "success"},
},
{
"name": "failure-not-configured",
"test": VerifySnmpUser,
"eos_data": [
{
"usersByVersion": {
"v3": {
"users": {
"Test3": {
"groupName": "TestGroup3",
"v3Params": {"authType": "SHA-384", "privType": "AES-128"},
},
}
},
}
}
],
"inputs": {
"users": [
{"username": "Test1", "group_name": "TestGroup1", "security_model": "v1"},
{"username": "Test2", "group_name": "TestGroup2", "security_model": "v2c"},
{"username": "Test3", "group_name": "TestGroup3", "security_model": "v3", "authentication_type": "SHA-384", "priv_type": "AES-128"},
{"username": "Test4", "group_name": "TestGroup3", "security_model": "v3", "authentication_type": "SHA-512", "priv_type": "AES-192"},
]
},
"expected": {
"result": "failure",
"messages": [
"SNMP user 'Test1' is not configured with security model 'v1'.\n"
"SNMP user 'Test2' is not configured with security model 'v2c'.\n"
"SNMP user 'Test4' is not configured with security model 'v3'."
],
},
},
{
"name": "failure-incorrect-configure",
"test": VerifySnmpUser,
"eos_data": [
{
"usersByVersion": {
"v1": {
"users": {
"Test1": {
"groupName": "TestGroup2",
},
}
},
"v2c": {
"users": {
"Test2": {
"groupName": "TestGroup1",
},
}
},
"v3": {
"users": {
"Test3": {
"groupName": "TestGroup4",
"v3Params": {"authType": "SHA-512", "privType": "AES-192"},
},
"Test4": {"groupName": "TestGroup4", "v3Params": {"authType": "SHA-384", "privType": "AES-128"}},
}
},
}
}
],
"inputs": {
"users": [
{"username": "Test1", "group_name": "TestGroup1", "security_model": "v1"},
{"username": "Test2", "group_name": "TestGroup2", "security_model": "v2c"},
{"username": "Test3", "group_name": "TestGroup3", "security_model": "v3", "authentication_type": "SHA-384", "priv_type": "AES-128"},
{"username": "Test4", "group_name": "TestGroup3", "security_model": "v3", "authentication_type": "SHA-512", "priv_type": "AES-192"},
]
},
"expected": {
"result": "failure",
"messages": [
"For SNMP user Test1:\nExpected `TestGroup1` as the user group, but found `TestGroup2` instead.\n"
"For SNMP user Test2:\nExpected `TestGroup2` as the user group, but found `TestGroup1` instead.\n"
"For SNMP user Test3:\n"
"Expected `TestGroup3` as the user group, but found `TestGroup4` instead.\n"
"Expected `SHA-384` as the authentication type, but found `SHA-512` instead.\n"
"Expected `AES-128` as the privacy type, but found `AES-192` instead.\n"
"For SNMP user Test4:\n"
"Expected `TestGroup3` as the user group, but found `TestGroup4` instead.\n"
"Expected `SHA-512` as the authentication type, but found `SHA-384` instead.\n"
"Expected `AES-192` as the privacy type, but found `AES-128` instead."
],
},
},
]
Loading