mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-04 19:21:51 +00:00
265 lines
9.8 KiB
Python
265 lines
9.8 KiB
Python
"""Smaller-provider schema coverage. One happy path + one invalid path
|
|
per field is enough to lock in the contract; the validator behaviour
|
|
itself is covered exhaustively in validator_test.py."""
|
|
|
|
import pytest
|
|
|
|
from prowler.config.schema.registry import SCHEMAS
|
|
from prowler.config.schema.validator import validate_provider_config
|
|
|
|
|
|
def _validate(provider, raw):
|
|
return validate_provider_config(provider, raw, SCHEMAS[provider])
|
|
|
|
|
|
class Test_Azure_Schema:
|
|
@pytest.mark.parametrize("level", ["Low", "Medium", "High", "Critical"])
|
|
def test_defender_risk_level_valid_values(self, level):
|
|
assert _validate(
|
|
"azure", {"defender_attack_path_minimal_risk_level": level}
|
|
) == {"defender_attack_path_minimal_risk_level": level}
|
|
|
|
def test_defender_risk_level_lowercase_dropped(self):
|
|
# Case matters: the matching check uses Title-case comparison.
|
|
assert (
|
|
_validate("azure", {"defender_attack_path_minimal_risk_level": "high"})
|
|
== {}
|
|
)
|
|
|
|
def test_apim_threshold_in_range(self):
|
|
out = _validate("azure", {"apim_threat_detection_llm_jacking_threshold": 0.1})
|
|
assert out == {"apim_threat_detection_llm_jacking_threshold": 0.1}
|
|
|
|
def test_apim_threshold_out_of_range(self):
|
|
out = _validate("azure", {"apim_threat_detection_llm_jacking_threshold": 1.5})
|
|
assert out == {}
|
|
|
|
def test_vm_backup_retention_must_be_positive(self):
|
|
assert _validate("azure", {"vm_backup_min_daily_retention_days": 7}) == {
|
|
"vm_backup_min_daily_retention_days": 7
|
|
}
|
|
assert _validate("azure", {"vm_backup_min_daily_retention_days": 0}) == {}
|
|
assert _validate("azure", {"vm_backup_min_daily_retention_days": -1}) == {}
|
|
|
|
|
|
class Test_GCP_Schema:
|
|
def test_valid_values_round_trip(self):
|
|
raw = {
|
|
"mig_min_zones": 2,
|
|
"max_snapshot_age_days": 90,
|
|
"max_unused_account_days": 180,
|
|
"storage_min_retention_days": 90,
|
|
}
|
|
assert _validate("gcp", raw) == raw
|
|
|
|
def test_zero_zone_count_dropped(self):
|
|
assert _validate("gcp", {"mig_min_zones": 0}) == {}
|
|
|
|
|
|
class Test_Kubernetes_Schema:
|
|
def test_valid_values_round_trip(self):
|
|
raw = {
|
|
"audit_log_maxbackup": 10,
|
|
"audit_log_maxsize": 100,
|
|
"audit_log_maxage": 30,
|
|
}
|
|
assert _validate("kubernetes", raw) == raw
|
|
|
|
def test_negative_audit_log_dropped(self):
|
|
assert _validate("kubernetes", {"audit_log_maxage": -1}) == {}
|
|
|
|
|
|
class Test_M365_Schema:
|
|
def test_valid_values_round_trip(self):
|
|
raw = {
|
|
"sign_in_frequency": 4,
|
|
"recommended_mailtips_large_audience_threshold": 25,
|
|
"audit_log_age": 90,
|
|
}
|
|
assert _validate("m365", raw) == raw
|
|
|
|
def test_negative_audit_log_age_dropped(self):
|
|
assert _validate("m365", {"audit_log_age": -10}) == {}
|
|
|
|
|
|
class Test_GitHub_Schema:
|
|
def test_valid_threshold(self):
|
|
assert _validate("github", {"inactive_not_archived_days_threshold": 180}) == {
|
|
"inactive_not_archived_days_threshold": 180
|
|
}
|
|
|
|
def test_zero_threshold_dropped(self):
|
|
assert _validate("github", {"inactive_not_archived_days_threshold": 0}) == {}
|
|
|
|
|
|
class Test_MongoDBAtlas_Schema:
|
|
def test_valid(self):
|
|
assert _validate(
|
|
"mongodbatlas", {"max_service_account_secret_validity_hours": 8}
|
|
) == {"max_service_account_secret_validity_hours": 8}
|
|
|
|
def test_invalid_negative(self):
|
|
assert (
|
|
_validate("mongodbatlas", {"max_service_account_secret_validity_hours": -1})
|
|
== {}
|
|
)
|
|
|
|
|
|
class Test_Cloudflare_Schema:
|
|
def test_zero_retries_allowed(self):
|
|
# 0 is explicitly documented as "disable retries" in config.yaml.
|
|
assert _validate("cloudflare", {"max_retries": 0}) == {"max_retries": 0}
|
|
|
|
def test_positive_retries_allowed(self):
|
|
assert _validate("cloudflare", {"max_retries": 3}) == {"max_retries": 3}
|
|
|
|
def test_negative_retries_dropped(self):
|
|
assert _validate("cloudflare", {"max_retries": -1}) == {}
|
|
|
|
|
|
class Test_Okta_Schema:
|
|
def test_valid_values_round_trip(self):
|
|
raw = {
|
|
"okta_max_session_idle_minutes": 15,
|
|
"okta_max_session_lifetime_minutes": 18 * 60,
|
|
"okta_admin_console_idle_timeout_max_minutes": 15,
|
|
"okta_user_inactivity_max_days": 35,
|
|
"okta_dod_approved_ca_issuer_patterns": [r"\bOU=DoD\b", r"\bOU=ECA\b"],
|
|
}
|
|
assert _validate("okta", raw) == raw
|
|
|
|
def test_zero_idle_minutes_dropped(self):
|
|
assert _validate("okta", {"okta_max_session_idle_minutes": 0}) == {}
|
|
|
|
def test_negative_inactivity_days_dropped(self):
|
|
assert _validate("okta", {"okta_user_inactivity_max_days": -1}) == {}
|
|
|
|
def test_full_rate_limit_config_round_trip(self):
|
|
raw = {
|
|
"okta_requests_per_second": 4.0,
|
|
"okta_max_retries": 5,
|
|
"okta_request_timeout": 300,
|
|
}
|
|
assert _validate("okta", raw) == raw
|
|
|
|
def test_requests_per_second_zero_allowed(self):
|
|
# 0 is documented as "disable throttling" in config.yaml.
|
|
assert _validate("okta", {"okta_requests_per_second": 0}) == {
|
|
"okta_requests_per_second": 0
|
|
}
|
|
|
|
def test_requests_per_second_sub_one_allowed(self):
|
|
assert _validate("okta", {"okta_requests_per_second": 0.5}) == {
|
|
"okta_requests_per_second": 0.5
|
|
}
|
|
|
|
def test_requests_per_second_floor_allowed(self):
|
|
# 0.1 is the lowest non-zero rate accepted.
|
|
assert _validate("okta", {"okta_requests_per_second": 0.1}) == {
|
|
"okta_requests_per_second": 0.1
|
|
}
|
|
|
|
def test_requests_per_second_below_floor_dropped(self):
|
|
# A tiny rate (e.g. 0.001 -> ~1000s/request) would make scans take
|
|
# days or years; it must be rejected, not silently honoured.
|
|
assert _validate("okta", {"okta_requests_per_second": 0.001}) == {}
|
|
assert _validate("okta", {"okta_requests_per_second": 0.05}) == {}
|
|
|
|
def test_requests_per_second_above_max_dropped(self):
|
|
assert _validate("okta", {"okta_requests_per_second": 101}) == {}
|
|
|
|
def test_requests_per_second_negative_dropped(self):
|
|
assert _validate("okta", {"okta_requests_per_second": -1}) == {}
|
|
|
|
def test_max_retries_zero_allowed(self):
|
|
assert _validate("okta", {"okta_max_retries": 0}) == {"okta_max_retries": 0}
|
|
|
|
def test_max_retries_out_of_range_dropped(self):
|
|
assert _validate("okta", {"okta_max_retries": -1}) == {}
|
|
assert _validate("okta", {"okta_max_retries": 11}) == {}
|
|
|
|
def test_request_timeout_zero_allowed(self):
|
|
# 0 is documented as "disable the timeout" in config.yaml.
|
|
assert _validate("okta", {"okta_request_timeout": 0}) == {
|
|
"okta_request_timeout": 0
|
|
}
|
|
|
|
def test_request_timeout_in_range_allowed(self):
|
|
assert _validate("okta", {"okta_request_timeout": 300}) == {
|
|
"okta_request_timeout": 300
|
|
}
|
|
|
|
def test_request_timeout_out_of_range_dropped(self):
|
|
assert _validate("okta", {"okta_request_timeout": -1}) == {}
|
|
assert _validate("okta", {"okta_request_timeout": 3601}) == {}
|
|
|
|
def test_non_numeric_value_dropped(self):
|
|
# A typo'd string must not flow through to the limiter (it would crash
|
|
# the `> 0` comparison during provider init).
|
|
assert _validate("okta", {"okta_requests_per_second": "fast"}) == {}
|
|
|
|
|
|
class Test_Vercel_Schema:
|
|
def test_owner_percentage_in_range(self):
|
|
assert _validate("vercel", {"max_owner_percentage": 20}) == {
|
|
"max_owner_percentage": 20
|
|
}
|
|
assert _validate("vercel", {"max_owner_percentage": 1}) == {
|
|
"max_owner_percentage": 1
|
|
}
|
|
assert _validate("vercel", {"max_owner_percentage": 50}) == {
|
|
"max_owner_percentage": 50
|
|
}
|
|
|
|
def test_owner_percentage_over_max_dropped(self):
|
|
# Tightened to 1..50 — anything above (incl. previous 100) is dropped.
|
|
assert _validate("vercel", {"max_owner_percentage": 51}) == {}
|
|
assert _validate("vercel", {"max_owner_percentage": 150}) == {}
|
|
|
|
def test_owner_percentage_zero_or_negative_dropped(self):
|
|
# 0 is no longer a valid configuration (defeats PoLP signal).
|
|
assert _validate("vercel", {"max_owner_percentage": 0}) == {}
|
|
assert _validate("vercel", {"max_owner_percentage": -1}) == {}
|
|
|
|
def test_full_default_config_round_trip(self):
|
|
raw = {
|
|
"stable_branches": ["main", "master"],
|
|
"days_to_expire_threshold": 7,
|
|
"stale_token_threshold_days": 90,
|
|
"stale_invitation_threshold_days": 30,
|
|
"max_owner_percentage": 20,
|
|
"max_owners": 3,
|
|
"secret_suffixes": ["_KEY", "_SECRET", "_TOKEN"],
|
|
}
|
|
assert _validate("vercel", raw) == raw
|
|
|
|
|
|
class Test_AlibabaCloud_Schema:
|
|
def test_valid_values_round_trip(self):
|
|
raw = {
|
|
"max_cluster_check_days": 7,
|
|
"max_console_access_days": 90,
|
|
"min_log_retention_days": 365,
|
|
"min_rds_audit_retention_days": 180,
|
|
}
|
|
assert _validate("alibabacloud", raw) == raw
|
|
|
|
def test_zero_cluster_check_days_dropped(self):
|
|
assert _validate("alibabacloud", {"max_cluster_check_days": 0}) == {}
|
|
|
|
def test_console_access_below_min_dropped(self):
|
|
# 30 is the documented floor; anything below produces false positives.
|
|
assert _validate("alibabacloud", {"max_console_access_days": 29}) == {}
|
|
|
|
|
|
class Test_OpenStack_Schema:
|
|
def test_valid_values_round_trip(self):
|
|
raw = {
|
|
"image_sharing_threshold": 5,
|
|
"secrets_ignore_patterns": ["AKIA[0-9A-Z]{16}"],
|
|
}
|
|
assert _validate("openstack", raw) == raw
|
|
|
|
def test_zero_threshold_dropped(self):
|
|
assert _validate("openstack", {"image_sharing_threshold": 0}) == {}
|