mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-24 13:01:56 +00:00
170 lines
6.4 KiB
Python
170 lines
6.4 KiB
Python
"""Validation coverage for the ConfigRequirements schema.
|
|
|
|
``Compliance_Requirement_ConfigConstraint`` is the model behind every
|
|
``ConfigRequirements`` entry in the compliance framework JSONs. These tests pin
|
|
the operator vocabulary, the value-typing rules (notably that booleans are not
|
|
coerced to integers), and that constraints survive the legacy → universal
|
|
adaptation used by the App backend and the OCSF/table outputs.
|
|
"""
|
|
|
|
import json
|
|
import pathlib
|
|
|
|
import pytest
|
|
from pydantic.v1 import ValidationError
|
|
|
|
from prowler.lib.check.compliance_models import (
|
|
Compliance,
|
|
Compliance_Requirement_ConfigConstraint,
|
|
adapt_legacy_to_universal,
|
|
)
|
|
|
|
_REPO_ROOT = pathlib.Path(__file__).resolve().parents[3]
|
|
_CIS_6_0 = _REPO_ROOT / "prowler" / "compliance" / "aws" / "cis_6.0_aws.json"
|
|
|
|
|
|
def _load_cis():
|
|
"""Load the CIS 6.0 AWS framework JSON via a context manager."""
|
|
with open(_CIS_6_0, encoding="utf-8") as f:
|
|
return json.load(f)
|
|
|
|
|
|
class Test_Compliance_Requirement_ConfigConstraint:
|
|
@pytest.mark.parametrize(
|
|
"operator,value",
|
|
[
|
|
("lte", 45),
|
|
("gte", 365),
|
|
("eq", False),
|
|
("in", [1, 2, 3]),
|
|
("subset", ["1.2", "1.3"]),
|
|
("superset", ["RSA-1024", "P-192"]),
|
|
],
|
|
)
|
|
def test_valid_operators(self, operator, value):
|
|
c = Compliance_Requirement_ConfigConstraint(
|
|
Check="some_check", ConfigKey="some_key", Operator=operator, Value=value
|
|
)
|
|
assert c.Operator == operator
|
|
assert c.Value == value
|
|
|
|
def test_invalid_operator_rejected(self):
|
|
with pytest.raises(ValidationError):
|
|
Compliance_Requirement_ConfigConstraint(
|
|
Check="c", ConfigKey="k", Operator="between", Value=1
|
|
)
|
|
|
|
@pytest.mark.parametrize(
|
|
"operator,value",
|
|
[
|
|
# numeric operators reject non-numeric / boolean values
|
|
("gte", [1, 2]),
|
|
("lte", ["45"]),
|
|
("gte", True),
|
|
# set/list operators reject scalars
|
|
("subset", 5),
|
|
("superset", "x"),
|
|
("in", 1),
|
|
# eq rejects lists
|
|
("eq", [1, 2]),
|
|
],
|
|
)
|
|
def test_value_type_inconsistent_with_operator_rejected(self, operator, value):
|
|
# A mistyped Value would otherwise be silently treated as "not satisfied"
|
|
# at runtime, forcing a spurious config-not-valid FAIL.
|
|
with pytest.raises(ValidationError):
|
|
Compliance_Requirement_ConfigConstraint(
|
|
Check="c", ConfigKey="k", Operator=operator, Value=value
|
|
)
|
|
|
|
def test_boolean_value_not_coerced_to_int(self):
|
|
# ``mute_non_default_regions == false`` must stay a bool, not become 0.
|
|
c = Compliance_Requirement_ConfigConstraint(
|
|
Check="securityhub_enabled",
|
|
ConfigKey="mute_non_default_regions",
|
|
Operator="eq",
|
|
Value=False,
|
|
)
|
|
assert c.Value is False
|
|
assert isinstance(c.Value, bool)
|
|
|
|
def test_list_value_preserved_for_set_operators(self):
|
|
c = Compliance_Requirement_ConfigConstraint(
|
|
Check="c", ConfigKey="k", Operator="subset", Value=["1.2", "1.3"]
|
|
)
|
|
assert isinstance(c.Value, list)
|
|
assert c.Value == ["1.2", "1.3"]
|
|
|
|
def test_missing_required_fields_rejected(self):
|
|
with pytest.raises(ValidationError):
|
|
Compliance_Requirement_ConfigConstraint(Check="c", ConfigKey="k")
|
|
|
|
def test_provider_defaults_to_none(self):
|
|
# Single-provider frameworks omit Provider; it is optional.
|
|
c = Compliance_Requirement_ConfigConstraint(
|
|
Check="c", ConfigKey="k", Operator="eq", Value=False
|
|
)
|
|
assert c.Provider is None
|
|
|
|
def test_provider_scopes_constraint(self):
|
|
# Universal frameworks tag each constraint with the provider it applies to.
|
|
c = Compliance_Requirement_ConfigConstraint(
|
|
Check="securityhub_enabled",
|
|
Provider="aws",
|
|
ConfigKey="mute_non_default_regions",
|
|
Operator="eq",
|
|
Value=False,
|
|
)
|
|
assert c.Provider == "aws"
|
|
|
|
|
|
class Test_ConfigRequirements_On_Compliance:
|
|
def test_requirements_without_constraints_default_to_none(self):
|
|
compliance = Compliance(**_load_cis())
|
|
# Requirement without configurable checks → ConfigRequirements is None.
|
|
no_constraint = [r for r in compliance.Requirements if not r.ConfigRequirements]
|
|
assert no_constraint
|
|
assert no_constraint[0].ConfigRequirements is None
|
|
|
|
def test_requirement_with_constraints_parses(self):
|
|
compliance = Compliance(**_load_cis())
|
|
with_constraint = [r for r in compliance.Requirements if r.ConfigRequirements]
|
|
assert with_constraint, "cis_6.0_aws should declare ConfigRequirements"
|
|
constraint = with_constraint[0].ConfigRequirements[0]
|
|
assert isinstance(constraint, Compliance_Requirement_ConfigConstraint)
|
|
assert constraint.Check
|
|
assert constraint.Operator in {"lte", "gte", "eq", "in", "subset", "superset"}
|
|
|
|
|
|
class Test_Adapt_Legacy_To_Universal:
|
|
def test_config_requirements_carried_to_universal(self):
|
|
legacy = Compliance(**_load_cis())
|
|
universal = adapt_legacy_to_universal(legacy)
|
|
|
|
legacy_with = {r.Id for r in legacy.Requirements if r.ConfigRequirements}
|
|
universal_with = {r.id for r in universal.requirements if r.config_requirements}
|
|
assert legacy_with == universal_with
|
|
assert universal_with, "expected at least one requirement with constraints"
|
|
|
|
# The constraint payload survives as the typed constraint model with the
|
|
# same fields (``Provider`` is carried through too, ``None`` for
|
|
# single-provider frameworks like CIS AWS).
|
|
sample = next(r for r in universal.requirements if r.config_requirements)
|
|
entry = sample.config_requirements[0]
|
|
assert isinstance(entry, Compliance_Requirement_ConfigConstraint)
|
|
assert set(entry.dict()) == {
|
|
"Check",
|
|
"Provider",
|
|
"ConfigKey",
|
|
"Operator",
|
|
"Value",
|
|
}
|
|
assert entry.Provider is None
|
|
|
|
def test_requirements_without_constraints_are_none_in_universal(self):
|
|
legacy = Compliance(**_load_cis())
|
|
universal = adapt_legacy_to_universal(legacy)
|
|
without = [r for r in universal.requirements if not r.config_requirements]
|
|
assert without
|
|
assert without[0].config_requirements is None
|