anta 1.10.0.dev2__tar.gz → 1.10.0.dev3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/PKG-INFO +1 -1
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/_advisory/reporter/csv_reporter.py +3 -3
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/_advisory/reporter/md_reporter.py +50 -18
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/advisories/sa_117.py +2 -2
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/advisories/sa_140.py +2 -2
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/advisories/sa_142.py +2 -2
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/advisories/sa_146.py +1 -1
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/advisories/sa_147.py +5 -5
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta.egg-info/PKG-INFO +1 -1
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/pyproject.toml +2 -2
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/LICENSE +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/README.md +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/__init__.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/_advisory/__init__.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/_advisory/base.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/_advisory/eos_versions.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/_advisory/models.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/_advisory/optional_commands.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/_advisory/platforms.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/_advisory/remediation.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/_advisory/reporter/__init__.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/_advisory/reporter/reporting.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/_advisory/results.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/_advisory/status.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/_eos/__init__.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/_eos/version.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/_runner.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/catalog.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/cli/__init__.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/cli/_main.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/cli/check/__init__.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/cli/check/commands.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/cli/console.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/cli/debug/__init__.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/cli/debug/commands.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/cli/debug/utils.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/cli/exec/__init__.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/cli/exec/commands.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/cli/exec/utils.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/cli/get/__init__.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/cli/get/commands.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/cli/get/utils.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/cli/nrfu/__init__.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/cli/nrfu/commands.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/cli/nrfu/utils.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/cli/psirt.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/cli/utils.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/constants.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/custom_types.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/decorators.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/device.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/input_models/__init__.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/input_models/avt.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/input_models/bfd.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/input_models/configuration.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/input_models/connectivity.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/input_models/cvx.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/input_models/evpn.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/input_models/flow_tracking.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/input_models/hardware.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/input_models/interfaces.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/input_models/logging.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/input_models/path_selection.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/input_models/routing/__init__.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/input_models/routing/bgp.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/input_models/routing/generic.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/input_models/routing/isis.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/input_models/routing/ospf.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/input_models/security.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/input_models/services.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/input_models/snmp.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/input_models/stun.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/input_models/system.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/input_models/vlan.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/inventory/__init__.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/inventory/exceptions.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/inventory/models.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/logger.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/models.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/py.typed +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/reporter/__init__.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/reporter/csv_reporter.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/reporter/jinja_reporter.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/reporter/md_reporter.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/reporter/table_reporter.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/result_manager/__init__.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/result_manager/models.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/runner.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/settings.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/__init__.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/aaa.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/advisories/__init__.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/avt.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/bfd.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/configuration.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/connectivity.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/cvx.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/evpn.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/field_notices.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/flow_tracking.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/greent.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/hardware.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/interfaces.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/lanz.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/logging.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/mlag.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/multicast.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/path_selection.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/profiles.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/ptp.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/routing/__init__.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/routing/bgp.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/routing/generic.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/routing/isis.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/routing/ospf.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/security.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/services.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/snmp.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/software.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/stp.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/stun.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/system.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/vlan.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/vxlan.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tools.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta.egg-info/SOURCES.txt +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta.egg-info/dependency_links.txt +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta.egg-info/entry_points.txt +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta.egg-info/requires.txt +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta.egg-info/top_level.txt +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/asynceapi/__init__.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/asynceapi/_auth.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/asynceapi/_constants.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/asynceapi/_errors.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/asynceapi/_models.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/asynceapi/_types.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/asynceapi/aio_portcheck.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/asynceapi/config_session.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/asynceapi/device.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/asynceapi/errors.py +0 -0
- {anta-1.10.0.dev2 → anta-1.10.0.dev3}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: anta
|
|
3
|
-
Version: 1.10.0.
|
|
3
|
+
Version: 1.10.0.dev3
|
|
4
4
|
Summary: Arista Network Test Automation (ANTA) Framework
|
|
5
5
|
Author-email: Arista Networks ANTA maintainers <anta-dev@arista.com>
|
|
6
6
|
Maintainer-email: Arista Networks ANTA maintainers <anta-dev@arista.com>, Khelil Sator <ksator@arista.com>, Matthieu Tâche <mtache@arista.com>, Thomas Grimonet <tgrimonet@arista.com>, Guillaume Mulocher <gmulocher@arista.com>, Carl Baillargeon <carl.baillargeon@arista.com>
|
|
@@ -54,7 +54,7 @@ class SecurityAdvisoryReportCsv(ReportCsv):
|
|
|
54
54
|
def _format_remediations(result: TestResult | AtomicTestResult) -> str:
|
|
55
55
|
"""Flatten advisory remediation entries into a plain-text CSV cell."""
|
|
56
56
|
if isinstance(result, (_AdvisoryTestResult, _AdvisoryAtomicTestResult)):
|
|
57
|
-
return
|
|
57
|
+
return ReportCsv.split_list_to_txt_list(result.remediations, "\\n")
|
|
58
58
|
return ""
|
|
59
59
|
|
|
60
60
|
@classmethod
|
|
@@ -70,9 +70,9 @@ class SecurityAdvisoryReportCsv(ReportCsv):
|
|
|
70
70
|
str(result.name),
|
|
71
71
|
result.test,
|
|
72
72
|
cls._format_result(result),
|
|
73
|
-
|
|
73
|
+
cls.split_list_to_txt_list(result.messages, "\\n"),
|
|
74
74
|
cls._format_result(row_result),
|
|
75
|
-
|
|
75
|
+
cls.split_list_to_txt_list(row_result.messages, "\\n"),
|
|
76
76
|
cls._format_remediations(row_result),
|
|
77
77
|
cls._format_remediations(result),
|
|
78
78
|
f"SA{advisory.sa_number}",
|
|
@@ -34,7 +34,7 @@ SEVERITY_ICONS = {
|
|
|
34
34
|
RESULT_ICONS = {
|
|
35
35
|
AntaTestStatus.SUCCESS: "✅",
|
|
36
36
|
AntaTestStatus.INCONCLUSIVE: "❓",
|
|
37
|
-
AntaTestStatus.FAILURE: "
|
|
37
|
+
AntaTestStatus.FAILURE: "🛑",
|
|
38
38
|
AntaTestStatus.ERROR: "❗",
|
|
39
39
|
AntaTestStatus.SKIPPED: "⏭️",
|
|
40
40
|
AntaTestStatus.UNSET: "-",
|
|
@@ -81,9 +81,47 @@ class SecurityAdvisoryMDReportBase(MDReportBase):
|
|
|
81
81
|
]
|
|
82
82
|
remediations.extend(atomic_remediations)
|
|
83
83
|
unique_remediations = dict.fromkeys(remediations)
|
|
84
|
+
formatted_remediations = []
|
|
85
|
+
for remediation in unique_remediations:
|
|
86
|
+
vulnerability_ids = self._remediation_vulnerability_ids(result, remediation)
|
|
87
|
+
prefix = f"{', '.join(vulnerability_ids)}: " if vulnerability_ids else ""
|
|
88
|
+
formatted_remediations.append(f"{prefix}{remediation}")
|
|
84
89
|
if atomic_remediations:
|
|
85
|
-
return "<br>".join(f"• {self.safe_markdown(remediation)}" for remediation in
|
|
86
|
-
return self.safe_markdown("\n".join(
|
|
90
|
+
return "<br>".join(f"• {self.safe_markdown(remediation)}" for remediation in formatted_remediations)
|
|
91
|
+
return self.safe_markdown("\n".join(formatted_remediations)) or "-"
|
|
92
|
+
|
|
93
|
+
@staticmethod
|
|
94
|
+
def _remediation_vulnerability_ids(result: TestResult | AtomicTestResult, remediation: str) -> tuple[str, ...]:
|
|
95
|
+
"""Return advisory-ordered vulnerability IDs sharing a parent remediation."""
|
|
96
|
+
if not isinstance(result, _AdvisoryTestResult):
|
|
97
|
+
return ()
|
|
98
|
+
|
|
99
|
+
associated_ids: set[str] = set()
|
|
100
|
+
for atomic in result.atomic_results:
|
|
101
|
+
if not isinstance(atomic, _AdvisoryAtomicTestResult) or remediation not in atomic.remediations:
|
|
102
|
+
continue
|
|
103
|
+
if not atomic.vulnerability_ids:
|
|
104
|
+
return ()
|
|
105
|
+
associated_ids.update(atomic.vulnerability_ids)
|
|
106
|
+
return tuple(vulnerability.id for vulnerability in result.advisory.vulnerabilities if vulnerability.id in associated_ids)
|
|
107
|
+
|
|
108
|
+
def format_findings(self, result: TestResult) -> str:
|
|
109
|
+
"""Format findings and label associated atomic findings with vulnerability IDs."""
|
|
110
|
+
messages = list(result.messages)
|
|
111
|
+
if isinstance(result, _AdvisoryTestResult):
|
|
112
|
+
for atomic in result.atomic_results:
|
|
113
|
+
vulnerability_ids = _get_atomic_vulnerability_ids(atomic)
|
|
114
|
+
if not vulnerability_ids:
|
|
115
|
+
continue
|
|
116
|
+
prefix = f"{', '.join(vulnerability_ids)}: "
|
|
117
|
+
for message in atomic.messages:
|
|
118
|
+
inherited_message = f"{atomic.description} - {message}"
|
|
119
|
+
try:
|
|
120
|
+
index = messages.index(inherited_message)
|
|
121
|
+
except ValueError:
|
|
122
|
+
continue
|
|
123
|
+
messages[index] = f"{prefix}{message}"
|
|
124
|
+
return self.safe_markdown("<br>".join(messages)) or "-"
|
|
87
125
|
|
|
88
126
|
|
|
89
127
|
class ANTASecurityAdvisoryReport(SecurityAdvisoryMDReportBase):
|
|
@@ -99,7 +137,7 @@ class ANTASecurityAdvisoryReport(SecurityAdvisoryMDReportBase):
|
|
|
99
137
|
toc += "- [Advisory Assessment Summary](#advisory-assessment-summary)\n- [Security Advisory Details](#security-advisory-details)\n"
|
|
100
138
|
for group in self.groups:
|
|
101
139
|
advisory = group.advisory
|
|
102
|
-
toc += f" - [
|
|
140
|
+
toc += f" - [{self.safe_markdown(advisory.title)}](#sa-{advisory.sa_number.lower()})\n"
|
|
103
141
|
toc += "- [Run Overview](#run-overview)"
|
|
104
142
|
self.mdfile.write(toc + "\n\n")
|
|
105
143
|
|
|
@@ -112,7 +150,7 @@ class AdvisoryAssessmentSummary(SecurityAdvisoryMDReportBase):
|
|
|
112
150
|
"Security Advisory",
|
|
113
151
|
"Severity",
|
|
114
152
|
"Devices",
|
|
115
|
-
"
|
|
153
|
+
"🛑 Affected",
|
|
116
154
|
"❓ Inconclusive",
|
|
117
155
|
"✅ Mitigated",
|
|
118
156
|
"✅ Not Affected",
|
|
@@ -130,7 +168,7 @@ class AdvisoryAssessmentSummary(SecurityAdvisoryMDReportBase):
|
|
|
130
168
|
"""Generate one summary row per security advisory."""
|
|
131
169
|
for group in self.groups:
|
|
132
170
|
advisory = group.advisory
|
|
133
|
-
advisory_link = f"[
|
|
171
|
+
advisory_link = f"[{self.safe_markdown(advisory.title)}](#sa-{advisory.sa_number.lower()})"
|
|
134
172
|
severity = self.format_severity(group.severity)
|
|
135
173
|
devices = len({result.name for result in group.results})
|
|
136
174
|
yield (
|
|
@@ -181,7 +219,7 @@ class SecurityAdvisoryDetails(SecurityAdvisoryMDReportBase):
|
|
|
181
219
|
heading = self.generate_table_heading(["Device", "Result", "Findings", "Remediations"])
|
|
182
220
|
self.mdfile.write("\n".join(heading) + "\n")
|
|
183
221
|
for result in group.results:
|
|
184
|
-
findings = self.
|
|
222
|
+
findings = self.format_findings(result)
|
|
185
223
|
remediation = self.format_remediations(result)
|
|
186
224
|
self.mdfile.write(f"| {self.safe_markdown(result.name)} | {self.format_advisory_result(result)} | {findings} | {remediation} |\n")
|
|
187
225
|
|
|
@@ -203,7 +241,7 @@ class SecurityAdvisoryDetails(SecurityAdvisoryMDReportBase):
|
|
|
203
241
|
|
|
204
242
|
def _write_expanded_findings(self, group: AdvisoryResultGroup) -> None:
|
|
205
243
|
"""Write parent advisory results followed by their actual detailed issue results."""
|
|
206
|
-
heading = self.generate_table_heading(["Device", "
|
|
244
|
+
heading = self.generate_table_heading(["Device", "Vulnerability ID(s)", "Result", "Findings", "Remediations"])
|
|
207
245
|
self.mdfile.write("\n".join(heading) + "\n")
|
|
208
246
|
vulnerability_by_id = {vulnerability.id: vulnerability for vulnerability in group.advisory.vulnerabilities}
|
|
209
247
|
for result in group.results:
|
|
@@ -211,20 +249,14 @@ class SecurityAdvisoryDetails(SecurityAdvisoryMDReportBase):
|
|
|
211
249
|
if has_details:
|
|
212
250
|
findings = f"**Detailed findings:** {self._atomic_summary(result)}"
|
|
213
251
|
if result.messages:
|
|
214
|
-
findings += f"<br>**Overall evidence:** {self.
|
|
252
|
+
findings += f"<br>**Overall evidence:** {self.format_findings(result)}"
|
|
215
253
|
else:
|
|
216
254
|
findings = self.safe_markdown("<br>".join(result.messages)) or "-"
|
|
217
|
-
description = self.safe_markdown(result.description) or "-"
|
|
218
255
|
remediation = self.format_remediations(result)
|
|
219
|
-
self.mdfile.write(f"| {self.safe_markdown(result.name)} |
|
|
256
|
+
self.mdfile.write(f"| {self.safe_markdown(result.name)} | - | {self.format_advisory_result(result)} | {findings} | {remediation} |\n")
|
|
220
257
|
for index, atomic in enumerate(result.atomic_results):
|
|
221
258
|
tree = "└──" if index == len(result.atomic_results) - 1 else "├──"
|
|
222
259
|
vulnerability_ids = _get_atomic_vulnerability_ids(atomic)
|
|
223
|
-
if vulnerability_ids:
|
|
224
|
-
atomic_description = "<br>".join(self.safe_markdown(vulnerability_by_id[vulnerability_id].description) for vulnerability_id in vulnerability_ids)
|
|
225
|
-
else:
|
|
226
|
-
atomic_description = self.safe_markdown(atomic.description) or "-"
|
|
227
|
-
description = f" {tree} {atomic_description}"
|
|
228
260
|
vulnerabilities = (
|
|
229
261
|
"<br>".join(
|
|
230
262
|
f"{SEVERITY_ICONS[vulnerability_by_id[vulnerability_id].severity]} {self.safe_markdown(vulnerability_id)}"
|
|
@@ -235,7 +267,7 @@ class SecurityAdvisoryDetails(SecurityAdvisoryMDReportBase):
|
|
|
235
267
|
)
|
|
236
268
|
atomic_findings = self.safe_markdown("<br>".join(atomic.messages)) or "-"
|
|
237
269
|
remediation = self.format_remediations(atomic)
|
|
238
|
-
self.mdfile.write(f"|
|
|
270
|
+
self.mdfile.write(f"| {tree} | {vulnerabilities} | {self.format_advisory_result(atomic)} | {atomic_findings} | {remediation} |\n")
|
|
239
271
|
|
|
240
272
|
def generate_section(self) -> None:
|
|
241
273
|
"""Generate detailed advisory metadata and findings."""
|
|
@@ -244,7 +276,7 @@ class SecurityAdvisoryDetails(SecurityAdvisoryMDReportBase):
|
|
|
244
276
|
advisory = group.advisory
|
|
245
277
|
anchor = f"sa-{advisory.sa_number.lower()}"
|
|
246
278
|
title = self.safe_markdown(advisory.title)
|
|
247
|
-
self.mdfile.write(f'###
|
|
279
|
+
self.mdfile.write(f'### {title} <a id="{anchor}"></a>\n\n')
|
|
248
280
|
advisory_severity = group.severity
|
|
249
281
|
severity = f"**Severity:** {SEVERITY_ICONS[advisory_severity]} {advisory_severity.value.title()}"
|
|
250
282
|
description = self.safe_markdown(advisory.description)
|
|
@@ -62,7 +62,7 @@ ADVISORY = _AdvisoryMetadata(
|
|
|
62
62
|
_AdvisoryVulnerability(
|
|
63
63
|
id="CVE-2025-0936",
|
|
64
64
|
severity=_AdvisoryVulnerabilitySeverity.MEDIUM,
|
|
65
|
-
description=("
|
|
65
|
+
description=("gNOI TransferToRemote credential exposure through OpenConfig accounting or tracing."),
|
|
66
66
|
),
|
|
67
67
|
),
|
|
68
68
|
url=("https://www.arista.com/en/support/advisories-notices/security-advisory/21394-security-advisory-0117"),
|
|
@@ -289,7 +289,7 @@ class VerifySA117(OptionalCommandsMixin, _AntaAdvisoryTest):
|
|
|
289
289
|
)
|
|
290
290
|
vulnerability = ADVISORY.vulnerabilities[0]
|
|
291
291
|
atomic_result = self.result.add(
|
|
292
|
-
vulnerability.
|
|
292
|
+
f"Verify {vulnerability.id}.",
|
|
293
293
|
vulnerability_ids=(vulnerability.id,),
|
|
294
294
|
)
|
|
295
295
|
project_advisory_status(atomic_result, status, message, remediation)
|
|
@@ -53,7 +53,7 @@ ADVISORY = _AdvisoryMetadata(
|
|
|
53
53
|
_AdvisoryVulnerability(
|
|
54
54
|
id="CVE-2026-10040",
|
|
55
55
|
severity=_AdvisoryVulnerabilitySeverity.MEDIUM,
|
|
56
|
-
description="
|
|
56
|
+
description="Secure Boot Software Image verification bypass.",
|
|
57
57
|
),
|
|
58
58
|
),
|
|
59
59
|
url=("https://www.arista.com/en/support/advisories-notices/security-advisory/24074-security-advisory-0140"),
|
|
@@ -163,7 +163,7 @@ class VerifySA140(_AntaAdvisoryTest):
|
|
|
163
163
|
)
|
|
164
164
|
vulnerability = ADVISORY.vulnerabilities[0]
|
|
165
165
|
atomic_result = self.result.add(
|
|
166
|
-
vulnerability.
|
|
166
|
+
f"Verify {vulnerability.id}.",
|
|
167
167
|
vulnerability_ids=(vulnerability.id,),
|
|
168
168
|
)
|
|
169
169
|
project_advisory_status(atomic_result, status, message, remediation)
|
|
@@ -226,7 +226,7 @@ ADVISORY = _AdvisoryMetadata(
|
|
|
226
226
|
_AdvisoryVulnerability(
|
|
227
227
|
id="CVE-2026-12546",
|
|
228
228
|
severity=_AdvisoryVulnerabilitySeverity.MEDIUM,
|
|
229
|
-
description=("
|
|
229
|
+
description=("Next-hop redirection bypass for packets requiring exception handling."),
|
|
230
230
|
),
|
|
231
231
|
),
|
|
232
232
|
url=("https://www.arista.com/en/support/advisories-notices/security-advisory/24111-security-advisory-0142"),
|
|
@@ -732,7 +732,7 @@ class VerifySA142(OptionalCommandsMixin, _AntaAdvisoryTest):
|
|
|
732
732
|
)
|
|
733
733
|
vulnerability = ADVISORY.vulnerabilities[0]
|
|
734
734
|
atomic_result = self.result.add(
|
|
735
|
-
vulnerability.
|
|
735
|
+
f"Verify {vulnerability.id}.",
|
|
736
736
|
vulnerability_ids=(vulnerability.id,),
|
|
737
737
|
)
|
|
738
738
|
project_advisory_status(atomic_result, status, message, remediation)
|
|
@@ -517,7 +517,7 @@ class VerifySA146(OptionalCommandsMixin, _AntaAdvisoryTest):
|
|
|
517
517
|
)
|
|
518
518
|
vulnerability = ADVISORY.vulnerabilities[0]
|
|
519
519
|
atomic_result = self.result.add(
|
|
520
|
-
vulnerability.
|
|
520
|
+
f"Verify {vulnerability.id}.",
|
|
521
521
|
vulnerability_ids=(vulnerability.id,),
|
|
522
522
|
)
|
|
523
523
|
project_advisory_status(atomic_result, status, message, remediation)
|
|
@@ -61,22 +61,22 @@ ADVISORY = _AdvisoryMetadata(
|
|
|
61
61
|
_AdvisoryVulnerability(
|
|
62
62
|
id="CVE-2026-59995",
|
|
63
63
|
severity=_AdvisoryVulnerabilitySeverity.MEDIUM,
|
|
64
|
-
description="
|
|
64
|
+
description="SFTP client issue when connecting to an untrusted server.",
|
|
65
65
|
),
|
|
66
66
|
_AdvisoryVulnerability(
|
|
67
67
|
id="CVE-2026-59996",
|
|
68
68
|
severity=_AdvisoryVulnerabilitySeverity.MEDIUM,
|
|
69
|
-
description=("
|
|
69
|
+
description=("SCP remote-to-remote client issue involving an untrusted server."),
|
|
70
70
|
),
|
|
71
71
|
_AdvisoryVulnerability(
|
|
72
72
|
id="CVE-2026-60001",
|
|
73
73
|
severity=_AdvisoryVulnerabilitySeverity.MEDIUM,
|
|
74
|
-
description="
|
|
74
|
+
description="OpenSSH server issue affecting accepted SSH connections.",
|
|
75
75
|
),
|
|
76
76
|
_AdvisoryVulnerability(
|
|
77
77
|
id="CVE-2026-60002",
|
|
78
78
|
severity=_AdvisoryVulnerabilitySeverity.CRITICAL,
|
|
79
|
-
description=("
|
|
79
|
+
description=("SSH client issue when connecting to a malicious or compromised server."),
|
|
80
80
|
),
|
|
81
81
|
),
|
|
82
82
|
url=("https://www.arista.com/en/support/advisories-notices/security-advisory/24515-security-advisory-0147"),
|
|
@@ -428,7 +428,7 @@ class VerifySA147(OptionalCommandsMixin, _AntaAdvisoryTest):
|
|
|
428
428
|
)
|
|
429
429
|
for vulnerability, (status, message, remediation) in zip(ADVISORY.vulnerabilities, assessments, strict=True):
|
|
430
430
|
atomic_result = self.result.add(
|
|
431
|
-
vulnerability.
|
|
431
|
+
f"Verify {vulnerability.id}.",
|
|
432
432
|
vulnerability_ids=(vulnerability.id,),
|
|
433
433
|
)
|
|
434
434
|
project_advisory_status(atomic_result, status, message, remediation)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: anta
|
|
3
|
-
Version: 1.10.0.
|
|
3
|
+
Version: 1.10.0.dev3
|
|
4
4
|
Summary: Arista Network Test Automation (ANTA) Framework
|
|
5
5
|
Author-email: Arista Networks ANTA maintainers <anta-dev@arista.com>
|
|
6
6
|
Maintainer-email: Arista Networks ANTA maintainers <anta-dev@arista.com>, Khelil Sator <ksator@arista.com>, Matthieu Tâche <mtache@arista.com>, Thomas Grimonet <tgrimonet@arista.com>, Guillaume Mulocher <gmulocher@arista.com>, Carl Baillargeon <carl.baillargeon@arista.com>
|
|
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
|
|
|
5
5
|
|
|
6
6
|
[project]
|
|
7
7
|
name = "anta"
|
|
8
|
-
version = "v1.10.0.
|
|
8
|
+
version = "v1.10.0.dev3"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [{ name = "Arista Networks ANTA maintainers", email = "anta-dev@arista.com" }]
|
|
11
11
|
maintainers = [
|
|
@@ -121,7 +121,7 @@ namespaces = false
|
|
|
121
121
|
# Version
|
|
122
122
|
################################
|
|
123
123
|
[tool.bumpver]
|
|
124
|
-
current_version = "1.10.0.
|
|
124
|
+
current_version = "1.10.0.dev3"
|
|
125
125
|
version_pattern = "MAJOR.MINOR.PATCH[.PYTAGNUM]"
|
|
126
126
|
commit_message = "bump: Version {old_version} -> {new_version}"
|
|
127
127
|
commit = true
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|