summaryrefslogtreecommitdiffstats
path: root/anta/tests/aaa.py
blob: d6d0689e46698156147e79bf039223c3a093d993 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
# 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 related to the EOS various AAA tests."""

# Mypy does not understand AntaTest.Input typing
# mypy: disable-error-code=attr-defined
from __future__ import annotations

from ipaddress import IPv4Address
from typing import TYPE_CHECKING, ClassVar, Literal

from anta.custom_types import AAAAuthMethod
from anta.models import AntaCommand, AntaTest

if TYPE_CHECKING:
    from anta.models import AntaTemplate


class VerifyTacacsSourceIntf(AntaTest):
    """Verifies TACACS source-interface for a specified VRF.

    Expected Results
    ----------------
    * Success: The test will pass if the provided TACACS source-interface is configured in the specified VRF.
    * Failure: The test will fail if the provided TACACS source-interface is NOT configured in the specified VRF.

    Examples
    --------
    ```yaml
    anta.tests.aaa:
      - VerifyTacacsSourceIntf:
          intf: Management0
          vrf: MGMT
    ```
    """

    name = "VerifyTacacsSourceIntf"
    description = "Verifies TACACS source-interface for a specified VRF."
    categories: ClassVar[list[str]] = ["aaa"]
    commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show tacacs", revision=1)]

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

        intf: str
        """Source-interface to use as source IP of TACACS messages."""
        vrf: str = "default"
        """The name of the VRF to transport TACACS messages. Defaults to `default`."""

    @AntaTest.anta_test
    def test(self) -> None:
        """Main test function for VerifyTacacsSourceIntf."""
        command_output = self.instance_commands[0].json_output
        try:
            if command_output["srcIntf"][self.inputs.vrf] == self.inputs.intf:
                self.result.is_success()
            else:
                self.result.is_failure(f"Wrong source-interface configured in VRF {self.inputs.vrf}")
        except KeyError:
            self.result.is_failure(f"Source-interface {self.inputs.intf} is not configured in VRF {self.inputs.vrf}")


class VerifyTacacsServers(AntaTest):
    """Verifies TACACS servers are configured for a specified VRF.

    Expected Results
    ----------------
    * Success: The test will pass if the provided TACACS servers are configured in the specified VRF.
    * Failure: The test will fail if the provided TACACS servers are NOT configured in the specified VRF.

    Examples
    --------
    ```yaml
    anta.tests.aaa:
      - VerifyTacacsServers:
          servers:
            - 10.10.10.21
            - 10.10.10.22
          vrf: MGMT
    ```
    """

    name = "VerifyTacacsServers"
    description = "Verifies TACACS servers are configured for a specified VRF."
    categories: ClassVar[list[str]] = ["aaa"]
    commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show tacacs", revision=1)]

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

        servers: list[IPv4Address]
        """List of TACACS servers."""
        vrf: str = "default"
        """The name of the VRF to transport TACACS messages. Defaults to `default`."""

    @AntaTest.anta_test
    def test(self) -> None:
        """Main test function for VerifyTacacsServers."""
        command_output = self.instance_commands[0].json_output
        tacacs_servers = command_output["tacacsServers"]
        if not tacacs_servers:
            self.result.is_failure("No TACACS servers are configured")
            return
        not_configured = [
            str(server)
            for server in self.inputs.servers
            if not any(
                str(server) == tacacs_server["serverInfo"]["hostname"] and self.inputs.vrf == tacacs_server["serverInfo"]["vrf"] for tacacs_server in tacacs_servers
            )
        ]
        if not not_configured:
            self.result.is_success()
        else:
            self.result.is_failure(f"TACACS servers {not_configured} are not configured in VRF {self.inputs.vrf}")


class VerifyTacacsServerGroups(AntaTest):
    """Verifies if the provided TACACS server group(s) are configured.

    Expected Results
    ----------------
    * Success: The test will pass if the provided TACACS server group(s) are configured.
    * Failure: The test will fail if one or all the provided TACACS server group(s) are NOT configured.

    Examples
    --------
    ```yaml
    anta.tests.aaa:
      - VerifyTacacsServerGroups:
          groups:
            - TACACS-GROUP1
            - TACACS-GROUP2
    ```
    """

    name = "VerifyTacacsServerGroups"
    description = "Verifies if the provided TACACS server group(s) are configured."
    categories: ClassVar[list[str]] = ["aaa"]
    commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show tacacs", revision=1)]

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

        groups: list[str]
        """List of TACACS server groups."""

    @AntaTest.anta_test
    def test(self) -> None:
        """Main test function for VerifyTacacsServerGroups."""
        command_output = self.instance_commands[0].json_output
        tacacs_groups = command_output["groups"]
        if not tacacs_groups:
            self.result.is_failure("No TACACS server group(s) are configured")
            return
        not_configured = [group for group in self.inputs.groups if group not in tacacs_groups]
        if not not_configured:
            self.result.is_success()
        else:
            self.result.is_failure(f"TACACS server group(s) {not_configured} are not configured")


class VerifyAuthenMethods(AntaTest):
    """Verifies the AAA authentication method lists for different authentication types (login, enable, dot1x).

    Expected Results
    ----------------
    * Success: The test will pass if the provided AAA authentication method list is matching in the configured authentication types.
    * Failure: The test will fail if the provided AAA authentication method list is NOT matching in the configured authentication types.

    Examples
    --------
    ```yaml
    anta.tests.aaa:
      - VerifyAuthenMethods:
        methods:
          - local
          - none
          - logging
        types:
          - login
          - enable
          - dot1x
    ```
    """

    name = "VerifyAuthenMethods"
    description = "Verifies the AAA authentication method lists for different authentication types (login, enable, dot1x)."
    categories: ClassVar[list[str]] = ["aaa"]
    commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show aaa methods authentication", revision=1)]

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

        methods: list[AAAAuthMethod]
        """List of AAA authentication methods. Methods should be in the right order."""
        types: set[Literal["login", "enable", "dot1x"]]
        """List of authentication types to verify."""

    @AntaTest.anta_test
    def test(self) -> None:
        """Main test function for VerifyAuthenMethods."""
        command_output = self.instance_commands[0].json_output
        not_matching: list[str] = []
        for k, v in command_output.items():
            auth_type = k.replace("AuthenMethods", "")
            if auth_type not in self.inputs.types:
                # We do not need to verify this accounting type
                continue
            if auth_type == "login":
                if "login" not in v:
                    self.result.is_failure("AAA authentication methods are not configured for login console")
                    return
                if v["login"]["methods"] != self.inputs.methods:
                    self.result.is_failure(f"AAA authentication methods {self.inputs.methods} are not matching for login console")
                    return
            not_matching.extend(auth_type for methods in v.values() if methods["methods"] != self.inputs.methods)

        if not not_matching:
            self.result.is_success()
        else:
            self.result.is_failure(f"AAA authentication methods {self.inputs.methods} are not matching for {not_matching}")


class VerifyAuthzMethods(AntaTest):
    """Verifies the AAA authorization method lists for different authorization types (commands, exec).

    Expected Results
    ----------------
    * Success: The test will pass if the provided AAA authorization method list is matching in the configured authorization types.
    * Failure: The test will fail if the provided AAA authorization method list is NOT matching in the configured authorization types.

    Examples
    --------
    ```yaml
    anta.tests.aaa:
      - VerifyAuthzMethods:
          methods:
            - local
            - none
            - logging
          types:
            - commands
            - exec
    ```
    """

    name = "VerifyAuthzMethods"
    description = "Verifies the AAA authorization method lists for different authorization types (commands, exec)."
    categories: ClassVar[list[str]] = ["aaa"]
    commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show aaa methods authorization", revision=1)]

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

        methods: list[AAAAuthMethod]
        """List of AAA authorization methods. Methods should be in the right order."""
        types: set[Literal["commands", "exec"]]
        """List of authorization types to verify."""

    @AntaTest.anta_test
    def test(self) -> None:
        """Main test function for VerifyAuthzMethods."""
        command_output = self.instance_commands[0].json_output
        not_matching: list[str] = []
        for k, v in command_output.items():
            authz_type = k.replace("AuthzMethods", "")
            if authz_type not in self.inputs.types:
                # We do not need to verify this accounting type
                continue
            not_matching.extend(authz_type for methods in v.values() if methods["methods"] != self.inputs.methods)

        if not not_matching:
            self.result.is_success()
        else:
            self.result.is_failure(f"AAA authorization methods {self.inputs.methods} are not matching for {not_matching}")


class VerifyAcctDefaultMethods(AntaTest):
    """Verifies the AAA accounting default method lists for different accounting types (system, exec, commands, dot1x).

    Expected Results
    ----------------
    * Success: The test will pass if the provided AAA accounting default method list is matching in the configured accounting types.
    * Failure: The test will fail if the provided AAA accounting default method list is NOT matching in the configured accounting types.

    Examples
    --------
    ```yaml
    anta.tests.aaa:
      - VerifyAcctDefaultMethods:
          methods:
            - local
            - none
            - logging
          types:
            - system
            - exec
            - commands
            - dot1x
    ```
    """

    name = "VerifyAcctDefaultMethods"
    description = "Verifies the AAA accounting default method lists for different accounting types (system, exec, commands, dot1x)."
    categories: ClassVar[list[str]] = ["aaa"]
    commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show aaa methods accounting", revision=1)]

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

        methods: list[AAAAuthMethod]
        """List of AAA accounting methods. Methods should be in the right order."""
        types: set[Literal["commands", "exec", "system", "dot1x"]]
        """List of accounting types to verify."""

    @AntaTest.anta_test
    def test(self) -> None:
        """Main test function for VerifyAcctDefaultMethods."""
        command_output = self.instance_commands[0].json_output
        not_matching = []
        not_configured = []
        for k, v in command_output.items():
            acct_type = k.replace("AcctMethods", "")
            if acct_type not in self.inputs.types:
                # We do not need to verify this accounting type
                continue
            for methods in v.values():
                if "defaultAction" not in methods:
                    not_configured.append(acct_type)
                if methods["defaultMethods"] != self.inputs.methods:
                    not_matching.append(acct_type)
        if not_configured:
            self.result.is_failure(f"AAA default accounting is not configured for {not_configured}")
            return
        if not not_matching:
            self.result.is_success()
        else:
            self.result.is_failure(f"AAA accounting default methods {self.inputs.methods} are not matching for {not_matching}")


class VerifyAcctConsoleMethods(AntaTest):
    """Verifies the AAA accounting console method lists for different accounting types (system, exec, commands, dot1x).

    Expected Results
    ----------------
    * Success: The test will pass if the provided AAA accounting console method list is matching in the configured accounting types.
    * Failure: The test will fail if the provided AAA accounting console method list is NOT matching in the configured accounting types.

    Examples
    --------
    ```yaml
    anta.tests.aaa:
      - VerifyAcctConsoleMethods:
          methods:
            - local
            - none
            - logging
          types:
            - system
            - exec
            - commands
            - dot1x
    ```
    """

    name = "VerifyAcctConsoleMethods"
    description = "Verifies the AAA accounting console method lists for different accounting types (system, exec, commands, dot1x)."
    categories: ClassVar[list[str]] = ["aaa"]
    commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show aaa methods accounting", revision=1)]

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

        methods: list[AAAAuthMethod]
        """List of AAA accounting console methods. Methods should be in the right order."""
        types: set[Literal["commands", "exec", "system", "dot1x"]]
        """List of accounting console types to verify."""

    @AntaTest.anta_test
    def test(self) -> None:
        """Main test function for VerifyAcctConsoleMethods."""
        command_output = self.instance_commands[0].json_output
        not_matching = []
        not_configured = []
        for k, v in command_output.items():
            acct_type = k.replace("AcctMethods", "")
            if acct_type not in self.inputs.types:
                # We do not need to verify this accounting type
                continue
            for methods in v.values():
                if "consoleAction" not in methods:
                    not_configured.append(acct_type)
                if methods["consoleMethods"] != self.inputs.methods:
                    not_matching.append(acct_type)
        if not_configured:
            self.result.is_failure(f"AAA console accounting is not configured for {not_configured}")
            return
        if not not_matching:
            self.result.is_success()
        else:
            self.result.is_failure(f"AAA accounting console methods {self.inputs.methods} are not matching for {not_matching}")