Files
prowler/tests/lib/outputs/compliance/cis/cis_table_test.py
T

163 lines
5.5 KiB
Python

import re
from types import SimpleNamespace
from prowler.lib.outputs.compliance.cis.cis import get_cis_table
def _strip_ansi(text):
return re.sub(r"\x1b\[[0-9;]*m", "", text)
def _make_finding(check_id, status="PASS", muted=False):
return SimpleNamespace(
check_metadata=SimpleNamespace(CheckID=check_id),
status=status,
muted=muted,
)
def _attr(section, profile="Level 1"):
return SimpleNamespace(Section=section, Profile=profile)
def _make_compliance(provider, attributes, version="1.4", framework="CIS"):
"""Build a per-check CIS compliance with the given (section, profile) attrs."""
return SimpleNamespace(
Framework=framework,
Version=version,
Provider=provider,
Requirements=[SimpleNamespace(Attributes=attributes)],
)
def _make_compliance_multi_req(provider, attributes, version="1.4", framework="CIS"):
"""Build a per-check CIS compliance where each attr is its own requirement,
simulating a check that appears in several requirements."""
return SimpleNamespace(
Framework=framework,
Version=version,
Provider=provider,
Requirements=[SimpleNamespace(Attributes=[attr]) for attr in attributes],
)
class TestCISTable:
"""Verify multi-section counting and provider-column attribution for the CIS compliance table."""
def test_muted_multi_section_not_undercounted(self, capsys, tmp_path):
"""A single MUTED finding mapped to several sections must increment the
per-section Muted column for every section, not only the first seen.
CIS counts FAIL/PASS through Level 1/Level 2 buckets, so only the Muted
per-section count was affected by the undercount bug.
"""
bulk_metadata = {
# check_a is muted and belongs to two sections at once.
"check_a": SimpleNamespace(
Compliance=[
_make_compliance("aws", [_attr("1 IAM"), _attr("2 Logging")])
]
),
# A real (non-muted) finding so the table is rendered.
"check_b": SimpleNamespace(
Compliance=[_make_compliance("aws", [_attr("1 IAM")])]
),
}
findings = [
_make_finding("check_a", "FAIL", muted=True),
_make_finding("check_b", "PASS"),
]
get_cis_table(
findings,
bulk_metadata,
"cis_1.4_aws",
"output",
str(tmp_path),
False,
)
captured = capsys.readouterr()
plain = _strip_ansi(captured.out)
# Both section rows must carry a Muted count of 1 in their last cell.
# Before the fix only the first section seen got incremented.
muted_one_rows = re.findall(r"│\s*1\s*│\s*$", plain, flags=re.MULTILINE)
assert len(muted_one_rows) == 2
def test_same_section_level_not_double_counted(self, capsys, tmp_path):
"""A single finding whose check maps to several requirements that share
the same section and profile must count once for that section/level,
not once per requirement (FAIL(1), never FAIL(2))."""
bulk_metadata = {
# check_a is a single FAIL mapped to two requirements, both in the
# same section "1 IAM" and the same profile "Level 1".
"check_a": SimpleNamespace(
Compliance=[
_make_compliance_multi_req("aws", [_attr("1 IAM"), _attr("1 IAM")])
]
),
# A second finding in another section so the table renders.
"check_b": SimpleNamespace(
Compliance=[_make_compliance("aws", [_attr("2 Logging")])]
),
}
findings = [
_make_finding("check_a", "FAIL"),
_make_finding("check_b", "PASS"),
]
get_cis_table(
findings,
bulk_metadata,
"cis_1.4_aws",
"output",
str(tmp_path),
False,
)
captured = capsys.readouterr()
plain = _strip_ansi(captured.out)
# The "1 IAM" row must show FAIL(1) for Level 1, never FAIL(2).
assert "FAIL(1)" in plain
assert "FAIL(2)" not in plain
def test_provider_column_not_leaked_from_other_framework(self, capsys, tmp_path):
"""The Provider column must come from the matched CIS compliance, not
from a different framework that trails it in the compliance list."""
bulk_metadata = {
"check_a": SimpleNamespace(
Compliance=[
_make_compliance("aws", [_attr("1 IAM")]),
_make_compliance(
"gcp", [_attr("Other")], framework="OtherFramework"
),
]
),
"check_b": SimpleNamespace(
Compliance=[
_make_compliance("aws", [_attr("1 IAM")]),
_make_compliance(
"gcp", [_attr("Other")], framework="OtherFramework"
),
]
),
}
findings = [
_make_finding("check_a", "FAIL"),
_make_finding("check_b", "PASS"),
]
get_cis_table(
findings,
bulk_metadata,
"cis_1.4_aws",
"output",
str(tmp_path),
False,
)
captured = capsys.readouterr()
assert "aws" in captured.out
# The trailing unrelated framework's provider must not leak in.
assert "gcp" not in captured.out