diff --git a/docs/developer-guide/configurable-checks.mdx b/docs/developer-guide/configurable-checks.mdx index 760dc80f586..3f79935b38f 100644 --- a/docs/developer-guide/configurable-checks.mdx +++ b/docs/developer-guide/configurable-checks.mdx @@ -46,6 +46,10 @@ When adding a new configurable check to Prowler, update the following files: For a complete list of checks that already support configuration, see the [Configuration File Tutorial](/user-guide/cli/tutorials/configuration_file). + +Because a configurable check's verdict depends on the `audit_config` value it reads, a compliance requirement can lose meaning if the scan ran with a looser threshold than the control demands. Compliance frameworks can guard against this with **configuration guardrails**: a requirement declares the strictest configuration it tolerates and is forced to FAIL when the scan's config falls short. See [Configuration Guardrails for Requirements](/developer-guide/security-compliance-framework#configuration-guardrails-for-requirements). + + ## Adding a Parameter to the Provider Schema Most providers have a typed Pydantic schema in `prowler/config/schema/`, registered in `prowler/config/schema/registry.py`. When a config is loaded and the provider has a registered schema, `validate_provider_config` checks each user-supplied key against it, logs a warning, and drops any field that fails validation. The consumer's `.get(key, default)` then falls back to the built-in default. Providers without a registered schema are passed through unchanged. diff --git a/docs/developer-guide/security-compliance-framework.mdx b/docs/developer-guide/security-compliance-framework.mdx index cf756c4da07..75392ed3a79 100644 --- a/docs/developer-guide/security-compliance-framework.mdx +++ b/docs/developer-guide/security-compliance-framework.mdx @@ -2,6 +2,8 @@ title: 'Creating a New Security Compliance Framework in Prowler' --- +import { VersionBadge } from "/snippets/version-badge.mdx" + This guide explains how to add a new security compliance framework to Prowler, end to end. It covers directory layout, the two supported JSON schemas (universal and legacy), the Pydantic models that validate each framework, check mapping conventions, output formatting, local validation, testing, and the pull request process. ## Introduction @@ -23,7 +25,7 @@ Requirement coverage feeds the compliance percentage calculations and the metada | **Universal (recommended for new frameworks)** | Multi-provider frameworks, or single-provider frameworks that benefit from declarative table/PDF rendering | `prowler/compliance/.json` (top-level) | Available for **every** provider whose key appears in any `requirement.checks` dict | | **Legacy provider-specific** | Single-provider frameworks with framework-specific attribute classes already declared in the codebase (CIS, ENS, ISO 27001, etc.) | `prowler/compliance//__.json` | Available only under that provider | -Auto-discovery happens in `get_bulk_compliance_frameworks_universal(provider)` (`prowler/lib/check/compliance_models.py:915`), which scans **both** the top-level `prowler/compliance/` directory and every per-provider sub-directory. Legacy frameworks are transparently converted to the universal `ComplianceFramework` model via `adapt_legacy_to_universal()` before being returned, so the rest of Prowler — CLI table rendering, CSV/OCSF outputs, PDF generation — works the same regardless of the source schema. +Auto-discovery happens in `get_bulk_compliance_frameworks_universal(provider)` (`prowler/lib/check/compliance_models.py`), which scans **both** the top-level `prowler/compliance/` directory and every per-provider sub-directory. Legacy frameworks are transparently converted to the universal `ComplianceFramework` model via `adapt_legacy_to_universal()` before being returned, so the rest of Prowler — CLI table rendering, CSV/OCSF outputs, PDF generation — works the same regardless of the source schema. > The legacy entry-point `Compliance.get_bulk(provider)` (used by older code paths) only scans per-provider sub-directories. Universal top-level files are picked up exclusively via the universal loader; this matters if you are wiring a new code path against the legacy API. @@ -70,13 +72,13 @@ The file is auto-discovered — there is **no** need to register it in any `__in } ``` -A `provider` field at the top level is **optional**. The framework's effective provider list is derived by `ComplianceFramework.get_providers()` (`compliance_models.py:739`) from the union of all keys appearing in `requirement.checks` across all requirements; the explicit `provider` field is used **only as a fallback** when no requirement carries any `checks` key. This is what enables a single file (e.g. `dora_2022_2554.json`) to cover AWS today and add Azure / GCP / etc. tomorrow without restructuring. +A `provider` field at the top level is **optional**. The framework's effective provider list is derived by `ComplianceFramework.get_providers()` (`compliance_models.py`) from the union of all keys appearing in `requirement.checks` across all requirements; the explicit `provider` field is used **only as a fallback** when no requirement carries any `checks` key. This is what enables a single file (e.g. `dora_2022_2554.json`) to cover AWS today and add Azure / GCP / etc. tomorrow without restructuring. Provider keys inside `requirement.checks` must match the directory names under `prowler/providers/`. The valid keys at present are: `aws`, `azure`, `gcp`, `m365`, `kubernetes`, `iac`, `github`, `googleworkspace`, `alibabacloud`, `cloudflare`, `mongodbatlas`, `nhn`, `openstack`, `oraclecloud`, `llm`. Comparison in `supports_provider()` is case-insensitive, but lowercase is the convention used everywhere in the repository. ### `attributes_metadata` -Declares the shape of the per-requirement `attributes` dict. When this field is present, the root validator `validate_attributes_against_metadata` (`compliance_models.py:669`) enforces the schema at load time and rejects: +Declares the shape of the per-requirement `attributes` dict. When this field is present, the root validator `validate_attributes_against_metadata` (`compliance_models.py`) enforces the schema at load time and rejects: - Missing keys marked `required: true`. - Keys present in `attributes` but not declared in `attributes_metadata` (typo / drift guard). @@ -192,6 +194,7 @@ Per requirement: - `name`: short title shown alongside the id. - `attributes`: flat dict; keys must conform to `attributes_metadata`. - `checks`: dict keyed by provider name (the same lowercase keys listed in the previous section). Each value is a list of Prowler check names that evidence this requirement for that provider. The list **may be empty** and the dict itself defaults to `{}` if omitted; either way the requirement is still loaded and listed by `--list-compliance-requirements`, it just has zero checks to execute. Note: there is **no automatic check-existence validation** at load time — referencing a non-existent check name will silently produce a requirement with no findings. Validate this yourself (see "Validating Your Framework" below). +- `config_requirements`: optional list of configuration guardrails. Each entry asserts that a configurable check referenced by this requirement ran with a configuration strict enough to actually satisfy the requirement; otherwise the requirement is forced to FAIL. See [Configuration Guardrails for Requirements](#configuration-guardrails-for-requirements) for the full schema and semantics. In the universal schema the field name is lowercase (`config_requirements`); legacy files use `ConfigRequirements`. For MITRE-style frameworks, additional optional fields are available on the requirement: `tactics`, `sub_techniques`, `platforms`, `technique_url` (these are populated automatically when adapting a legacy MITRE JSON to the universal model). @@ -258,7 +261,7 @@ prowler/lib/outputs/compliance// ### JSON schema reference -Every legacy compliance file is a JSON document with the following top-level keys. `Framework`, `Name` and `Provider` are validated non-empty by the root validator `framework_and_provider_must_not_be_empty` (`compliance_models.py:329`). +Every legacy compliance file is a JSON document with the following top-level keys. `Framework`, `Name` and `Provider` are validated non-empty by the root validator `framework_and_provider_must_not_be_empty` (`compliance_models.py`). | Field | Type | Required | Description | |---|---|---|---| @@ -280,10 +283,11 @@ Each entry in `Requirements` describes one control or requirement. | `Description` | string | Yes | Verbatim description from the source framework. | | `Attributes` | array | Yes | List of [attribute objects](#attribute-objects). The shape depends on the framework. | | `Checks` | array of strings | Yes | Prowler check identifiers that automate the requirement. Leave the list empty when the control cannot be automated. | +| `ConfigRequirements` | array of objects | No | Optional [configuration guardrails](#configuration-guardrails-for-requirements). Each entry asserts that a configurable check ran with a configuration strict enough to satisfy the requirement; when it did not, the requirement is forced to FAIL. | #### Attribute Objects -`Attributes` is parsed against the union declared in `Compliance_Requirement.Attributes` (`compliance_models.py:293`). Pydantic v1 tries each member of the union in declaration order and falls back to `Generic_Compliance_Requirement_Attribute` (the last entry) when nothing else matches — so a brand-new shape that doesn't match any existing class will silently be accepted as Generic, losing its specific fields. +`Attributes` is parsed against the union declared in `Compliance_Requirement.Attributes` (`compliance_models.py`). Pydantic v1 tries each member of the union in declaration order and falls back to `Generic_Compliance_Requirement_Attribute` (the last entry) when nothing else matches — so a brand-new shape that doesn't match any existing class will silently be accepted as Generic, losing its specific fields. As of today, the registered attribute classes are: `CIS_Requirement_Attribute`, `ENS_Requirement_Attribute`, `ASDEssentialEight_Requirement_Attribute`, `ISO27001_2013_Requirement_Attribute`, `AWS_Well_Architected_Requirement_Attribute`, `KISA_ISMSP_Requirement_Attribute`, `Prowler_ThreatScore_Requirement_Attribute`, `CCC_Requirement_Attribute`, `C5Germany_Requirement_Attribute`, `CSA_CCM_Requirement_Attribute`, and `Generic_Compliance_Requirement_Attribute` (fallback). MITRE-style frameworks use the separate `Mitre_Requirement` model with `Tactics` / `SubTechniques` / `Platforms` / `TechniqueURL` at the requirement top level. The most common shapes are summarized below. @@ -472,13 +476,188 @@ For NIST-style catalogs that use `Generic_Compliance_Requirement_Attribute`, no ### Legacy-to-universal adapter -At load time, every legacy file is transparently adapted to a `ComplianceFramework` via `adapt_legacy_to_universal()` (`compliance_models.py:819`), which: (a) flattens the first element of `Attributes` into a flat `attributes` dict, (b) wraps `Checks` as `{provider_lower: [...]}`, (c) infers `attributes_metadata` from the matched Pydantic class via `_infer_attribute_metadata()`. The rest of Prowler (CSV/OCSF/PDF output, CLI table) then treats both formats identically. +At load time, every legacy file is transparently adapted to a `ComplianceFramework` via `adapt_legacy_to_universal()` (`compliance_models.py`), which: (a) flattens the first element of `Attributes` into a flat `attributes` dict, (b) wraps `Checks` as `{provider_lower: [...]}`, (c) infers `attributes_metadata` from the matched Pydantic class via `_infer_attribute_metadata()`. The rest of Prowler (CSV/OCSF/PDF output, CLI table) then treats both formats identically. Loader-error behaviour differs between the two entry points: -- `load_compliance_framework()` (legacy) is **fail-fast**: it calls `sys.exit(1)` on any `ValidationError` (`compliance_models.py:464`). +- `load_compliance_framework()` (legacy) is **fail-fast**: it calls `sys.exit(1)` on any `ValidationError` (`compliance_models.py`). - `load_compliance_framework_universal()` is more lenient — it logs the error and returns `None`, so `get_bulk_compliance_frameworks_universal()` simply skips the broken file and keeps loading the rest. +## Configuration Guardrails for Requirements + + + +Some requirements are only truly satisfied when the configurable checks behind them ran with a configuration strict enough to meet the control. A [configurable check](/developer-guide/configurable-checks) reads thresholds from the scan's `audit_config`, so loosening a value can make the check PASS while the requirement it backs is, in fact, not satisfied. + +A worked example: CIS AWS 6.0 requirement 2.11 ("credentials unused for 45 days or more are disabled") maps to `iam_user_accesskey_unused`, which is driven by the `max_unused_access_keys_days` config key. If a user raises that value to `120`, the check passes for a key unused for 90 days — yet the requirement explicitly demands a 45-day threshold, so the PASS is misleading. + +Configuration guardrails close that gap. A requirement declares the configuration it expects, and when the scan ran with a configuration too loose to honor it, the requirement is forced to **FAIL** in every compliance output, with the reason surfaced in the finding's extended status. + + +Guardrails are an **optional** safety net for configurable checks. A requirement that maps only to non-configurable checks does not need them. When the field is absent, behavior is unchanged. + + +### Where guardrails are declared + +The field is attached to each requirement and exists in both schemas: + +- **Legacy** (`prowler/compliance//...`): `ConfigRequirements`, a list of objects, validated against the `Compliance_Requirement_ConfigConstraint` Pydantic model (`prowler/lib/check/compliance_models.py`). +- **Universal** (`prowler/compliance/...`): `config_requirements`, the same list of objects as plain dicts on `UniversalComplianceRequirement`. + +When a legacy file is adapted to the universal model, `adapt_legacy_to_universal()` copies `ConfigRequirements` into `config_requirements` (`compliance_models.py`), so downstream code only ever reads one shape. + +### Constraint schema + +Each entry in the list is a single constraint with the following fields: + +| Field | Type | Required | Description | +|---|---|---|---| +| `Check` | string | Yes | The configurable check this constraint guards. Should be one of the requirement's `Checks`. Used only to build a human-readable reason. | +| `ConfigKey` | string | Yes | The `audit_config` key the check reads (for example `max_unused_access_keys_days`). | +| `Operator` | enum | Yes | How to compare the applied value against `Value`. One of `lte`, `gte`, `eq`, `in`, `subset`, `superset`. | +| `Value` | bool, int, float, string, or list | Yes | The strictest configuration the requirement tolerates. The accepted Python type depends on the operator (see below). | +| `Provider` | string | No | The provider this constraint applies to (e.g. `aws`). **Required for universal (multi-provider) frameworks**, where the same requirement maps checks across providers — the constraint is only evaluated when the scanned provider matches. Single-provider (legacy) frameworks omit it. | + +### Operators + +| Operator | Applied value satisfies the guardrail when… | Typical use | +|---|---|---| +| `lte` | `applied <= Value` | Maximum-age / maximum-count thresholds (e.g. `max_unused_access_keys_days <= 45`). | +| `gte` | `applied >= Value` | Minimum-retention / minimum-count thresholds. | +| `eq` | `applied == Value` | Boolean toggles or an exact required value (e.g. `mute_non_default_regions == false`). | +| `in` | `applied` is one of `Value` (a list) | The applied scalar must belong to an allowed set. | +| `subset` | `set(applied) <= set(Value)` | **Allowlist** configs — every applied value must already be permitted. Widening the allowlist with a weaker value (e.g. adding TLS `1.0` to `recommended_minimal_tls_versions`) breaks the guardrail. | +| `superset` | `set(applied) >= set(Value)` | **Denylist** configs — every forbidden value must remain forbidden. Removing an entry from a denylist (e.g. dropping a weak algorithm from `insecure_key_algorithms`) breaks the guardrail. | + + +`subset` / `superset` require both the applied value and `Value` to be lists; any other type is treated as not satisfied. For `eq` against a boolean, declare `Value` as a JSON boolean (`false`, not `0`) — the model keeps booleans distinct from integers. + + +### How guardrails are evaluated + +All evaluation lives in one shared module, `prowler/lib/check/compliance_config_eval.py`, consumed by every compliance output (CSV, OCSF, and the CLI tables) and reused by the Prowler App backend so the rule is defined exactly once. + +1. The applied configuration is the scan-global `audit_config` (the same mapping for every resource and region), resolved via `get_scan_audit_config()`. +2. For each requirement that declares constraints, `evaluate_config_constraints()` walks the list and returns `(is_compliant, reason)`. The requirement is compliant when **every** explicitly-set key satisfies its constraint. +3. A constraint tagged with a `Provider` that does **not** match the provider being scanned (resolved via `get_scan_provider_type()`) is **skipped**. This scopes a universal framework's constraints to the right provider, so a guardrail authored for an AWS check never affects a GCP or Azure scan of the same requirement. Untagged constraints (legacy single-provider frameworks) always apply. +4. A constraint whose `ConfigKey` is **not present** in `audit_config` is **skipped** — the check's built-in default is assumed to already match what the requirement expects. This is why nothing changes for the default configuration. +5. When a constraint is violated, the finding's status is overridden to `FAIL` and a plain-language explanation is prepended to `status_extended` (via `apply_config_status()`). The message opens with `Configuration not valid for this requirement.` and names the check, the value the scan applied, what the requirement needs and how to fix it. For the table generators, `get_effective_status()` applies the same FAIL roll-up so per-section counts stay consistent. + + +Guardrails only ever make a result **stricter** (they can turn PASS into FAIL); they never relax a real FAIL into PASS. A requirement with no constraints, or whose keys all use defaults, is reported exactly as before. + + +### Example: legacy framework + +From `prowler/compliance/aws/cis_6.0_aws.json`, requirement 2.11 declares two guardrails — one per configurable check it maps to: + +```json title="prowler/compliance/aws/cis_6.0_aws.json" +{ + "Id": "2.11", + "Description": "Ensure credentials unused for 45 days or more are disabled.", + "Checks": [ + "iam_user_accesskey_unused", + "iam_user_console_access_unused" + ], + "ConfigRequirements": [ + { + "Check": "iam_user_accesskey_unused", + "ConfigKey": "max_unused_access_keys_days", + "Operator": "lte", + "Value": 45 + }, + { + "Check": "iam_user_console_access_unused", + "ConfigKey": "max_console_access_days", + "Operator": "lte", + "Value": 45 + } + ], + "Attributes": [ /* ... */ ] +} +``` + +A boolean guardrail from the same file: requirement 2.5 (IAM Access Analyzer) only holds when regions are not muted, so a scan with `mute_non_default_regions: true` cannot be trusted for it: + +```json +"ConfigRequirements": [ + { + "Check": "accessanalyzer_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } +] +``` + +### Example: universal framework + +The universal schema uses the lowercase `config_requirements` key with the identical object shape: + +```json +{ + "id": "MF-2.1", + "name": "Restrict TLS to modern versions", + "description": "Endpoints must negotiate only TLS 1.2 or higher.", + "checks": { + "aws": ["elbv2_listener_ssl_listeners"] + }, + "config_requirements": [ + { + "Check": "elbv2_listener_ssl_listeners", + "Provider": "aws", + "ConfigKey": "recommended_minimal_tls_versions", + "Operator": "subset", + "Value": ["TLS 1.2", "TLS 1.3"] + } + ] +} +``` + +Each constraint declares the `Provider` it targets so the guardrail is only evaluated on scans of that provider — essential for universal frameworks like CSA CCM and DORA, where one requirement maps checks across `aws`, `azure`, `gcp` and more. Because the operator is `subset`, adding `"TLS 1.0"` to `recommended_minimal_tls_versions` widens the allowlist beyond `["TLS 1.2", "TLS 1.3"]` and the requirement is forced to FAIL. + +### What the user sees + +With a loosened config, the affected requirement's findings report: + +```text +Status: FAIL +StatusExtended: Configuration not valid for this requirement. The check + iam_user_accesskey_unused has max_unused_access_keys_days set + to 120, but the requirement needs a value of 45 or lower. + Update it to 45 or lower. +``` + +The same `Configuration not valid for this requirement.` message appears identically across the CSV, OCSF, and console-table outputs. + +### Authoring guidelines + +- Declare a guardrail only for keys whose value actually changes whether the requirement is met. Most configurable checks do not need one. +- Set `Value` to the **strictest** configuration the control tolerates — the same number the control text cites (CIS 45 days, NIST ≤90, and so on). +- Keep `ConfigKey` spelled exactly as the check reads it from `audit_config`; an unknown key is never present in the config and the constraint is silently skipped. +- In a **universal (multi-provider) framework**, always set `Provider` to the provider that owns `Check` — otherwise the guardrail would leak onto scans of the other providers the requirement maps. Legacy single-provider files omit it. +- Pick the operator from the value's role: a max threshold is `lte`, a min threshold is `gte`, a toggle is `eq`, an allowlist is `subset`, a denylist is `superset`. +- An unrecognized operator does **not** block the requirement — a malformed constraint is treated as satisfied rather than failing the whole framework. Validate your JSON with the tests below. + +### Testing guardrails + +The shared evaluator and the per-output integration are covered by: + +- `tests/lib/check/compliance_config_eval_test.py` — operator semantics, skipped-key behavior, and the FAIL override. +- `tests/lib/check/compliance_config_constraint_model_test.py` — model validation (types, operator enum, bool-vs-int). +- `tests/lib/check/compliance_config_requirements_data_test.py` — sanity-checks the guardrails shipped in the JSON catalog. +- Per-output tests under `tests/lib/outputs/compliance/` (CIS AWS/Azure, ENS AWS, OCSF, universal table) confirm the override reaches each format. + +Run them with: + +```bash +uv run pytest -n auto \ + tests/lib/check/compliance_config_eval_test.py \ + tests/lib/check/compliance_config_constraint_model_test.py \ + tests/lib/check/compliance_config_requirements_data_test.py \ + tests/lib/outputs/compliance/ +``` + ## Version handling Prowler matches frameworks by concatenating `Framework` and `Version`. A missing or empty `Version` collapses several frameworks to the same key and breaks CLI filtering with `--compliance`. @@ -609,7 +788,7 @@ The following issues are the most common when contributing a compliance framewor - **`ValidationError: field required` during scan (legacy).** The JSON is missing a required attribute field. Re-check the matching Pydantic model in `prowler/lib/check/compliance_models.py`. - **All attributes collapse to `Generic_Compliance_Requirement_Attribute` values (legacy).** The Pydantic `Union` is ordered incorrectly, or the JSON matches only the generic shape. Keep the generic model in the last Union position and ensure every required field is present in the JSON. -- **`attributes_metadata validation failed` (universal).** The root validator in `compliance_models.py:669` rejected the file. The error message lists each offending requirement; common causes are unknown attribute keys (typo or missing entry in `attributes_metadata`), enum violations, or missing required keys. +- **`attributes_metadata validation failed` (universal).** The root validator in `compliance_models.py` rejected the file. The error message lists each offending requirement; common causes are unknown attribute keys (typo or missing entry in `attributes_metadata`), enum violations, or missing required keys. - **`--compliance` filter does not find the framework.** For legacy: the filename does not match `__.json`, the version is empty, or the file lives outside `prowler/compliance//`. For universal: the file is not at the top level of `prowler/compliance/` or it loaded as `None` (check logs for the validation error). - **CLI summary table is empty but the CSV is populated (legacy).** The dispatcher branch in `prowler/lib/outputs/compliance/compliance.py` is missing or its substring match does not catch the framework key. - **CSV file is missing after the scan (legacy).** The transformer class is not registered in `prowler/lib/outputs/compliance/compliance_output.py`, or `transform()` raises silently. Run the scan with `--log-level DEBUG`. diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index cb018a0542b..20783f18834 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -6,6 +6,7 @@ All notable changes to the **Prowler SDK** are documented in this file. ### 🚀 Added +- Per-requirement configuration validation for compliance frameworks via `ConfigRequirements`, so a requirement is reported as FAIL when its configurable checks ran with a configuration too loose to satisfy it (applied across all compliance outputs: CSV, OCSF, and console tables) [(#11669)](https://github.com/prowler-cloud/prowler/pull/11669) - `entra_conditional_access_policy_explicitly_targets_azure_devops` check for M365 provider, verifying at least one enabled Conditional Access policy explicitly includes the Azure DevOps cloud application instead of relying on a broad "All cloud apps" policy [(#11182)](https://github.com/prowler-cloud/prowler/pull/11182) - `entra_conditional_access_policy_no_exclusion_gaps` check for M365 provider, verifying every user, group, role, or application excluded from an enabled Conditional Access policy stays in scope of another enabled policy [(#11577)](https://github.com/prowler-cloud/prowler/pull/11577) diff --git a/prowler/compliance/alibabacloud/cis_2.0_alibabacloud.json b/prowler/compliance/alibabacloud/cis_2.0_alibabacloud.json index 7cda08efbb8..9a05c1997f4 100644 --- a/prowler/compliance/alibabacloud/cis_2.0_alibabacloud.json +++ b/prowler/compliance/alibabacloud/cis_2.0_alibabacloud.json @@ -109,6 +109,14 @@ ], "Checks": [ "ram_user_console_access_unused" + ], + "ConfigRequirements": [ + { + "Check": "ram_user_console_access_unused", + "ConfigKey": "max_console_access_days", + "Operator": "lte", + "Value": 90 + } ] }, { @@ -841,6 +849,14 @@ ], "Checks": [ "sls_logstore_retention_period" + ], + "ConfigRequirements": [ + { + "Check": "sls_logstore_retention_period", + "ConfigKey": "min_log_retention_days", + "Operator": "gte", + "Value": 365 + } ] }, { @@ -1353,6 +1369,14 @@ ], "Checks": [ "rds_instance_sql_audit_retention" + ], + "ConfigRequirements": [ + { + "Check": "rds_instance_sql_audit_retention", + "ConfigKey": "min_rds_audit_retention_days", + "Operator": "gte", + "Value": 180 + } ] }, { @@ -1551,6 +1575,14 @@ ], "Checks": [ "cs_kubernetes_cluster_check_recent" + ], + "ConfigRequirements": [ + { + "Check": "cs_kubernetes_cluster_check_recent", + "ConfigKey": "max_cluster_check_days", + "Operator": "lte", + "Value": 7 + } ] }, { diff --git a/prowler/compliance/alibabacloud/prowler_threatscore_alibabacloud.json b/prowler/compliance/alibabacloud/prowler_threatscore_alibabacloud.json index ca7a030dc20..0bfd47df7e9 100644 --- a/prowler/compliance/alibabacloud/prowler_threatscore_alibabacloud.json +++ b/prowler/compliance/alibabacloud/prowler_threatscore_alibabacloud.json @@ -47,6 +47,14 @@ "Checks": [ "ram_user_console_access_unused" ], + "ConfigRequirements": [ + { + "Check": "ram_user_console_access_unused", + "ConfigKey": "max_console_access_days", + "Operator": "lte", + "Value": 90 + } + ], "Attributes": [ { "Title": "Inactive users disabled for console access", @@ -399,6 +407,14 @@ "LevelOfRisk": 3, "Weight": 10 } + ], + "ConfigRequirements": [ + { + "Check": "cs_kubernetes_cluster_check_weekly", + "ConfigKey": "max_cluster_check_days", + "Operator": "lte", + "Value": 7 + } ] }, { @@ -695,6 +711,14 @@ "Checks": [ "rds_instance_sql_audit_retention" ], + "ConfigRequirements": [ + { + "Check": "rds_instance_sql_audit_retention", + "ConfigKey": "min_rds_audit_retention_days", + "Operator": "gte", + "Value": 180 + } + ], "Attributes": [ { "Title": "RDS SQL audit retention configured", diff --git a/prowler/compliance/aws/asd_essential_eight_aws.json b/prowler/compliance/aws/asd_essential_eight_aws.json index 00b39817e35..dd39c442681 100644 --- a/prowler/compliance/aws/asd_essential_eight_aws.json +++ b/prowler/compliance/aws/asd_essential_eight_aws.json @@ -13,6 +13,14 @@ "config_recorder_all_regions_enabled", "inspector2_is_enabled" ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "Section": "1 Patch applications", @@ -260,6 +268,14 @@ "config_recorder_all_regions_enabled", "inspector2_is_enabled" ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "Section": "2 Patch operating systems", @@ -742,6 +758,14 @@ "accessanalyzer_enabled", "accessanalyzer_enabled_without_findings" ], + "ConfigRequirements": [ + { + "Check": "accessanalyzer_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "Section": "4 Restrict administrative privileges", diff --git a/prowler/compliance/aws/aws_account_security_onboarding_aws.json b/prowler/compliance/aws/aws_account_security_onboarding_aws.json index 1d038537f00..1910bac2236 100644 --- a/prowler/compliance/aws/aws_account_security_onboarding_aws.json +++ b/prowler/compliance/aws/aws_account_security_onboarding_aws.json @@ -37,6 +37,26 @@ "guardduty_is_enabled", "accessanalyzer_enabled", "macie_is_enabled" + ], + "ConfigRequirements": [ + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "accessanalyzer_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -259,6 +279,20 @@ "Checks": [ "guardduty_is_enabled", "guardduty_is_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -514,6 +548,14 @@ "Checks": [ "accessanalyzer_enabled", "accessanalyzer_enabled_without_findings" + ], + "ConfigRequirements": [ + { + "Check": "accessanalyzer_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -530,6 +572,20 @@ "securityhub_enabled", "accessanalyzer_enabled", "accessanalyzer_enabled_without_findings" + ], + "ConfigRequirements": [ + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "accessanalyzer_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -666,6 +722,14 @@ ], "Checks": [ "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -680,6 +744,14 @@ ], "Checks": [ "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -694,6 +766,14 @@ ], "Checks": [ "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -708,6 +788,14 @@ ], "Checks": [ "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -722,6 +810,14 @@ ], "Checks": [ "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -736,6 +832,14 @@ ], "Checks": [ "config_recorder_all_regions_enabled" + ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -762,6 +866,14 @@ ], "Checks": [ "config_recorder_all_regions_enabled" + ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -777,6 +889,14 @@ "Checks": [ "guardduty_is_enabled", "guardduty_centrally_managed" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -792,6 +912,14 @@ "Checks": [ "guardduty_is_enabled", "guardduty_no_high_severity_findings" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -807,6 +935,14 @@ "Checks": [ "guardduty_is_enabled", "guardduty_no_high_severity_findings" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -822,6 +958,14 @@ "Checks": [ "guardduty_is_enabled", "guardduty_no_high_severity_findings" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -837,6 +981,14 @@ "Checks": [ "guardduty_is_enabled", "guardduty_no_high_severity_findings" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -852,6 +1004,14 @@ "Checks": [ "guardduty_is_enabled", "guardduty_no_high_severity_findings" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -867,6 +1027,14 @@ "Checks": [ "guardduty_is_enabled", "guardduty_no_high_severity_findings" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -882,6 +1050,14 @@ "Checks": [ "guardduty_is_enabled", "guardduty_no_high_severity_findings" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -897,6 +1073,14 @@ "Checks": [ "guardduty_is_enabled", "guardduty_no_high_severity_findings" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -912,6 +1096,14 @@ "Checks": [ "guardduty_is_enabled", "guardduty_no_high_severity_findings" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { diff --git a/prowler/compliance/aws/aws_ai_security_framework_aws.json b/prowler/compliance/aws/aws_ai_security_framework_aws.json index c6a0a8504bd..9b87f7464d4 100644 --- a/prowler/compliance/aws/aws_ai_security_framework_aws.json +++ b/prowler/compliance/aws/aws_ai_security_framework_aws.json @@ -404,6 +404,14 @@ "Checks": [ "accessanalyzer_enabled", "accessanalyzer_enabled_without_findings" + ], + "ConfigRequirements": [ + { + "Check": "accessanalyzer_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -860,6 +868,20 @@ "guardduty_lambda_protection_enabled", "guardduty_rds_protection_enabled", "guardduty_ec2_malware_protection_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "guardduty_delegated_admin_enabled_all_regions", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -894,6 +916,14 @@ ], "Checks": [ "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -964,6 +994,14 @@ "Checks": [ "config_recorder_all_regions_enabled", "config_recorder_using_aws_service_role" + ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -1157,4 +1195,4 @@ "Checks": [] } ] -} \ No newline at end of file +} diff --git a/prowler/compliance/aws/aws_foundational_security_best_practices_aws.json b/prowler/compliance/aws/aws_foundational_security_best_practices_aws.json index cea7ad16558..b58a74bcaad 100644 --- a/prowler/compliance/aws/aws_foundational_security_best_practices_aws.json +++ b/prowler/compliance/aws/aws_foundational_security_best_practices_aws.json @@ -20,6 +20,14 @@ "SectionDescription": "This section contains recommendations for configuring ACM resources.", "Service": "ACM" } + ], + "ConfigRequirements": [ + { + "Check": "acm_certificates_expiration_check", + "ConfigKey": "days_to_expire_threshold", + "Operator": "gte", + "Value": 30 + } ] }, { @@ -29,6 +37,17 @@ "Checks": [ "acm_certificates_with_secure_key_algorithms" ], + "ConfigRequirements": [ + { + "Check": "acm_certificates_with_secure_key_algorithms", + "ConfigKey": "insecure_key_algorithms", + "Operator": "superset", + "Value": [ + "RSA-1024", + "P-192" + ] + } + ], "Attributes": [ { "ItemId": "ACM.2", @@ -777,6 +796,14 @@ "Checks": [ "config_recorder_all_regions_enabled" ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "ItemId": "Config.1", @@ -892,6 +919,14 @@ "Checks": [ "documentdb_cluster_backup_enabled" ], + "ConfigRequirements": [ + { + "Check": "documentdb_cluster_backup_enabled", + "ConfigKey": "minimum_backup_retention_period", + "Operator": "gte", + "Value": 7 + } + ], "Attributes": [ { "ItemId": "DocumentDB.2", @@ -1959,6 +1994,14 @@ "SectionDescription": "This section contains recommendations for configuring ELB resources.", "Service": "ELB" } + ], + "ConfigRequirements": [ + { + "Check": "elb_is_in_multiple_az", + "ConfigKey": "elb_min_azs", + "Operator": "gte", + "Value": 2 + } ] }, { @@ -1993,6 +2036,14 @@ "SectionDescription": "This section contains recommendations for configuring ELB resources.", "Service": "ELB" } + ], + "ConfigRequirements": [ + { + "Check": "elbv2_is_in_multiple_az", + "ConfigKey": "elbv2_min_azs", + "Operator": "gte", + "Value": 2 + } ] }, { @@ -2370,6 +2421,14 @@ "Checks": [ "guardduty_is_enabled" ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "ItemId": "GuardDuty.1", @@ -2547,6 +2606,20 @@ "iam_user_accesskey_unused", "iam_user_console_access_unused" ], + "ConfigRequirements": [ + { + "Check": "iam_user_accesskey_unused", + "ConfigKey": "max_unused_access_keys_days", + "Operator": "lte", + "Value": 90 + }, + { + "Check": "iam_user_console_access_unused", + "ConfigKey": "max_console_access_days", + "Operator": "lte", + "Value": 90 + } + ], "Attributes": [ { "ItemId": "IAM.8", @@ -2635,6 +2708,20 @@ "iam_user_accesskey_unused", "iam_user_console_access_unused" ], + "ConfigRequirements": [ + { + "Check": "iam_user_accesskey_unused", + "ConfigKey": "max_unused_access_keys_days", + "Operator": "lte", + "Value": 45 + }, + { + "Check": "iam_user_console_access_unused", + "ConfigKey": "max_console_access_days", + "Operator": "lte", + "Value": 45 + } + ], "Attributes": [ { "ItemId": "IAM.22", @@ -2791,6 +2878,40 @@ "SectionDescription": "This section contains recommendations for configuring Lambda resources.", "Service": "Lambda" } + ], + "ConfigRequirements": [ + { + "Check": "awslambda_function_using_supported_runtimes", + "ConfigKey": "obsolete_lambda_runtimes", + "Operator": "superset", + "Value": [ + "java8", + "go1.x", + "provided", + "python3.6", + "python2.7", + "python3.7", + "python3.8", + "nodejs4.3", + "nodejs4.3-edge", + "nodejs6.10", + "nodejs", + "nodejs8.10", + "nodejs10.x", + "nodejs12.x", + "nodejs14.x", + "nodejs16.x", + "dotnet5.0", + "dotnet6", + "dotnet7", + "dotnetcore1.0", + "dotnetcore2.0", + "dotnetcore2.1", + "dotnetcore3.1", + "ruby2.5", + "ruby2.7" + ] + } ] }, { @@ -2951,6 +3072,14 @@ "Checks": [ "neptune_cluster_backup_enabled" ], + "ConfigRequirements": [ + { + "Check": "neptune_cluster_backup_enabled", + "ConfigKey": "minimum_backup_retention_period", + "Operator": "gte", + "Value": 7 + } + ], "Attributes": [ { "ItemId": "Neptune.5", diff --git a/prowler/compliance/aws/aws_foundational_technical_review_aws.json b/prowler/compliance/aws/aws_foundational_technical_review_aws.json index 691a8ba7f13..9d8e3cfdc81 100644 --- a/prowler/compliance/aws/aws_foundational_technical_review_aws.json +++ b/prowler/compliance/aws/aws_foundational_technical_review_aws.json @@ -176,6 +176,14 @@ "iam_user_with_temporary_credentials", "guardduty_is_enabled", "guardduty_no_high_severity_findings" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { diff --git a/prowler/compliance/aws/aws_well_architected_framework_security_pillar_aws.json b/prowler/compliance/aws/aws_well_architected_framework_security_pillar_aws.json index 4c4eaee2520..a025bb3a3c3 100644 --- a/prowler/compliance/aws/aws_well_architected_framework_security_pillar_aws.json +++ b/prowler/compliance/aws/aws_well_architected_framework_security_pillar_aws.json @@ -585,6 +585,14 @@ "cloudtrail_multi_region_enabled", "vpc_flow_logs_enabled", "config_recorder_all_regions_enabled" + ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -646,6 +654,20 @@ "guardduty_no_high_severity_findings", "macie_is_enabled", "guardduty_is_enabled" + ], + "ConfigRequirements": [ + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -778,6 +800,14 @@ "guardduty_is_enabled", "vpc_flow_logs_enabled", "apigateway_restapi_authorizers_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { diff --git a/prowler/compliance/aws/c5_aws.json b/prowler/compliance/aws/c5_aws.json index 88474691549..269a5ce3082 100644 --- a/prowler/compliance/aws/c5_aws.json +++ b/prowler/compliance/aws/c5_aws.json @@ -382,6 +382,14 @@ "cloudtrail_multi_region_enabled", "config_recorder_all_regions_enabled", "s3_multi_region_access_point_public_access_block" + ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -2234,6 +2242,14 @@ "vpc_different_regions", "autoscaling_group_multiple_az", "storagegateway_gateway_fault_tolerant" + ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -2261,6 +2277,14 @@ "organizations_scp_check_deny_regions", "s3_multi_region_access_point_public_access_block", "vpc_different_regions" + ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -2308,6 +2332,14 @@ "organizations_scp_check_deny_regions", "s3_multi_region_access_point_public_access_block", "vpc_different_regions" + ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -2978,6 +3010,14 @@ "guardduty_is_enabled", "athena_workgroup_enforce_configuration", "shield_advanced_protection_in_global_accelerators" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -3481,6 +3521,14 @@ "cloudtrail_cloudwatch_logging_enabled", "guardduty_is_enabled", "guardduty_no_high_severity_findings" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -4299,6 +4347,14 @@ "guardduty_no_high_severity_findings", "guardduty_rds_protection_enabled", "guardduty_s3_protection_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -4920,6 +4976,17 @@ "elbv2_nlb_tls_termination_enabled", "transfer_server_in_transit_encryption_enabled", "kafka_cluster_mutual_tls_authentication_enabled" + ], + "ConfigRequirements": [ + { + "Check": "acm_certificates_with_secure_key_algorithms", + "ConfigKey": "insecure_key_algorithms", + "Operator": "superset", + "Value": [ + "RSA-1024", + "P-192" + ] + } ] }, { @@ -4946,6 +5013,17 @@ "elbv2_nlb_tls_termination_enabled", "transfer_server_in_transit_encryption_enabled", "kafka_cluster_mutual_tls_authentication_enabled" + ], + "ConfigRequirements": [ + { + "Check": "acm_certificates_with_secure_key_algorithms", + "ConfigKey": "insecure_key_algorithms", + "Operator": "superset", + "Value": [ + "RSA-1024", + "P-192" + ] + } ] }, { @@ -5220,6 +5298,14 @@ "rds_instance_default_admin", "accessanalyzer_enabled", "efs_access_point_enforce_user_identity" + ], + "ConfigRequirements": [ + { + "Check": "accessanalyzer_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -5737,6 +5823,14 @@ "Checks": [ "accessanalyzer_enabled", "accessanalyzer_enabled_without_findings" + ], + "ConfigRequirements": [ + { + "Check": "accessanalyzer_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -6100,6 +6194,17 @@ "cloudfront_distributions_origin_traffic_encrypted", "glue_development_endpoints_job_bookmark_encryption_enabled", "cloudtrail_kms_encryption_enabled" + ], + "ConfigRequirements": [ + { + "Check": "acm_certificates_with_secure_key_algorithms", + "ConfigKey": "insecure_key_algorithms", + "Operator": "superset", + "Value": [ + "RSA-1024", + "P-192" + ] + } ] }, { @@ -6196,6 +6301,17 @@ "elb_ssl_listeners_use_acm_certificate", "iam_no_expired_server_certificates_stored", "rds_instance_certificate_expiration" + ], + "ConfigRequirements": [ + { + "Check": "acm_certificates_with_secure_key_algorithms", + "ConfigKey": "insecure_key_algorithms", + "Operator": "superset", + "Value": [ + "RSA-1024", + "P-192" + ] + } ] }, { @@ -6307,6 +6423,17 @@ "elb_ssl_listeners_use_acm_certificate", "iam_no_expired_server_certificates_stored", "rds_instance_certificate_expiration" + ], + "ConfigRequirements": [ + { + "Check": "acm_certificates_with_secure_key_algorithms", + "ConfigKey": "insecure_key_algorithms", + "Operator": "superset", + "Value": [ + "RSA-1024", + "P-192" + ] + } ] }, { @@ -6393,6 +6520,14 @@ "sns_topics_not_publicly_accessible", "sqs_queues_not_publicly_accessible", "vpc_peering_routing_tables_with_least_privilege" + ], + "ConfigRequirements": [ + { + "Check": "accessanalyzer_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -6412,6 +6547,14 @@ "ec2_instance_profile_attached", "accessanalyzer_enabled", "accessanalyzer_enabled_without_findings" + ], + "ConfigRequirements": [ + { + "Check": "accessanalyzer_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -6587,6 +6730,17 @@ "kms_cmk_not_multi_region", "kms_key_not_publicly_accessible", "ec2_ebs_volume_encryption" + ], + "ConfigRequirements": [ + { + "Check": "acm_certificates_with_secure_key_algorithms", + "ConfigKey": "insecure_key_algorithms", + "Operator": "superset", + "Value": [ + "RSA-1024", + "P-192" + ] + } ] }, { @@ -6809,6 +6963,17 @@ "secretsmanager_not_publicly_accessible", "secretsmanager_secret_rotated_periodically", "secretsmanager_secret_unused" + ], + "ConfigRequirements": [ + { + "Check": "acm_certificates_with_secure_key_algorithms", + "ConfigKey": "insecure_key_algorithms", + "Operator": "superset", + "Value": [ + "RSA-1024", + "P-192" + ] + } ] }, { @@ -6842,6 +7007,17 @@ ], "Checks": [ "acm_certificates_with_secure_key_algorithms" + ], + "ConfigRequirements": [ + { + "Check": "acm_certificates_with_secure_key_algorithms", + "ConfigKey": "insecure_key_algorithms", + "Operator": "superset", + "Value": [ + "RSA-1024", + "P-192" + ] + } ] }, { @@ -6915,6 +7091,17 @@ "secretsmanager_secret_rotated_periodically", "secretsmanager_secret_unused", "acm_certificates_with_secure_key_algorithms" + ], + "ConfigRequirements": [ + { + "Check": "acm_certificates_with_secure_key_algorithms", + "ConfigKey": "insecure_key_algorithms", + "Operator": "superset", + "Value": [ + "RSA-1024", + "P-192" + ] + } ] }, { @@ -6937,6 +7124,17 @@ "secretsmanager_secret_rotated_periodically", "secretsmanager_secret_unused", "acm_certificates_with_secure_key_algorithms" + ], + "ConfigRequirements": [ + { + "Check": "acm_certificates_with_secure_key_algorithms", + "ConfigKey": "insecure_key_algorithms", + "Operator": "superset", + "Value": [ + "RSA-1024", + "P-192" + ] + } ] }, { @@ -8042,6 +8240,14 @@ "cloudtrail_multi_region_enabled", "cloudtrail_multi_region_enabled_logging_management_events", "cloudtrail_log_file_validation_enabled" + ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -8810,6 +9016,14 @@ "guardduty_is_enabled", "cloudtrail_log_file_validation_enabled", "ssmincidents_enabled_with_plans" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -9732,6 +9946,14 @@ "accessanalyzer_enabled_without_findings", "cloudfront_distributions_s3_origin_access_control", "cloudtrail_logs_s3_bucket_access_logging_enabled" + ], + "ConfigRequirements": [ + { + "Check": "accessanalyzer_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -10367,6 +10589,14 @@ "Checks": [ "accessanalyzer_enabled", "accessanalyzer_enabled_without_findings" + ], + "ConfigRequirements": [ + { + "Check": "accessanalyzer_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -10457,6 +10687,14 @@ "ec2_instance_profile_attached", "iam_role_cross_account_readonlyaccess_policy", "iam_securityaudit_role_created" + ], + "ConfigRequirements": [ + { + "Check": "accessanalyzer_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { diff --git a/prowler/compliance/aws/ccc_aws.json b/prowler/compliance/aws/ccc_aws.json index 003e9e17a02..7935424193d 100644 --- a/prowler/compliance/aws/ccc_aws.json +++ b/prowler/compliance/aws/ccc_aws.json @@ -275,6 +275,17 @@ "acm_certificates_expiration_check", "acm_certificates_with_secure_key_algorithms", "acm_certificates_transparency_logs_enabled" + ], + "ConfigRequirements": [ + { + "Check": "acm_certificates_with_secure_key_algorithms", + "ConfigKey": "insecure_key_algorithms", + "Operator": "superset", + "Value": [ + "RSA-1024", + "P-192" + ] + } ] }, { @@ -794,6 +805,17 @@ ], "Checks": [ "acm_certificates_with_secure_key_algorithms" + ], + "ConfigRequirements": [ + { + "Check": "acm_certificates_with_secure_key_algorithms", + "ConfigKey": "insecure_key_algorithms", + "Operator": "superset", + "Value": [ + "RSA-1024", + "P-192" + ] + } ] }, { @@ -1504,6 +1526,14 @@ "iam_policy_no_full_access_to_kms", "iam_policy_no_full_access_to_cloudtrail", "iam_policy_attached_only_to_group_or_roles" + ], + "ConfigRequirements": [ + { + "Check": "accessanalyzer_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -1666,6 +1696,14 @@ "cloudwatch_changes_to_network_route_tables_alarm_configured", "cloudwatch_changes_to_vpcs_alarm_configured", "config_recorder_all_regions_enabled" + ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -1791,6 +1829,14 @@ "cloudtrail_threat_detection_enumeration", "guardduty_is_enabled", "guardduty_no_high_severity_findings" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -4311,6 +4357,14 @@ ], "Checks": [ "acm_certificates_expiration_check" + ], + "ConfigRequirements": [ + { + "Check": "acm_certificates_expiration_check", + "ConfigKey": "days_to_expire_threshold", + "Operator": "gte", + "Value": 30 + } ] }, { @@ -6176,6 +6230,20 @@ "Checks": [ "iam_user_accesskey_unused", "iam_user_console_access_unused" + ], + "ConfigRequirements": [ + { + "Check": "iam_user_accesskey_unused", + "ConfigKey": "max_unused_access_keys_days", + "Operator": "lte", + "Value": 90 + }, + { + "Check": "iam_user_console_access_unused", + "ConfigKey": "max_console_access_days", + "Operator": "lte", + "Value": 90 + } ] }, { @@ -6272,6 +6340,14 @@ "cloudwatch_log_metric_filter_root_usage", "cloudwatch_log_metric_filter_sign_in_without_mfa", "guardduty_is_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -6374,6 +6450,14 @@ "Checks": [ "accessanalyzer_enabled", "accessanalyzer_enabled_without_findings" + ], + "ConfigRequirements": [ + { + "Check": "accessanalyzer_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { diff --git a/prowler/compliance/aws/cis_1.4_aws.json b/prowler/compliance/aws/cis_1.4_aws.json index 3efc29fd5b1..b373a04665c 100644 --- a/prowler/compliance/aws/cis_1.4_aws.json +++ b/prowler/compliance/aws/cis_1.4_aws.json @@ -75,6 +75,20 @@ "iam_user_accesskey_unused", "iam_user_console_access_unused" ], + "ConfigRequirements": [ + { + "Check": "iam_user_accesskey_unused", + "ConfigKey": "max_unused_access_keys_days", + "Operator": "lte", + "Value": 45 + }, + { + "Check": "iam_user_console_access_unused", + "ConfigKey": "max_console_access_days", + "Operator": "lte", + "Value": 45 + } + ], "Attributes": [ { "Section": "1 Identity and Access Management", @@ -265,6 +279,14 @@ "Checks": [ "accessanalyzer_enabled" ], + "ConfigRequirements": [ + { + "Check": "accessanalyzer_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "Section": "1 Identity and Access Management", @@ -736,6 +758,14 @@ "Checks": [ "config_recorder_all_regions_enabled" ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "Section": "3 Logging", diff --git a/prowler/compliance/aws/cis_1.5_aws.json b/prowler/compliance/aws/cis_1.5_aws.json index f7307bb7f4e..6a60d786a22 100644 --- a/prowler/compliance/aws/cis_1.5_aws.json +++ b/prowler/compliance/aws/cis_1.5_aws.json @@ -75,6 +75,20 @@ "iam_user_accesskey_unused", "iam_user_console_access_unused" ], + "ConfigRequirements": [ + { + "Check": "iam_user_accesskey_unused", + "ConfigKey": "max_unused_access_keys_days", + "Operator": "lte", + "Value": 45 + }, + { + "Check": "iam_user_console_access_unused", + "ConfigKey": "max_console_access_days", + "Operator": "lte", + "Value": 45 + } + ], "Attributes": [ { "Section": "1 Identity and Access Management", @@ -265,6 +279,14 @@ "Checks": [ "accessanalyzer_enabled" ], + "ConfigRequirements": [ + { + "Check": "accessanalyzer_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "Section": "1 Identity and Access Management", @@ -802,6 +824,14 @@ "Checks": [ "config_recorder_all_regions_enabled" ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "Section": "3 Logging", @@ -1054,6 +1084,14 @@ "Checks": [ "securityhub_enabled" ], + "ConfigRequirements": [ + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "Section": "4 Monitoring", diff --git a/prowler/compliance/aws/cis_2.0_aws.json b/prowler/compliance/aws/cis_2.0_aws.json index 4f8c4b2c23a..e255bd43e1d 100644 --- a/prowler/compliance/aws/cis_2.0_aws.json +++ b/prowler/compliance/aws/cis_2.0_aws.json @@ -75,6 +75,20 @@ "iam_user_accesskey_unused", "iam_user_console_access_unused" ], + "ConfigRequirements": [ + { + "Check": "iam_user_accesskey_unused", + "ConfigKey": "max_unused_access_keys_days", + "Operator": "lte", + "Value": 45 + }, + { + "Check": "iam_user_console_access_unused", + "ConfigKey": "max_console_access_days", + "Operator": "lte", + "Value": 45 + } + ], "Attributes": [ { "Section": "1 Identity and Access Management", @@ -265,6 +279,14 @@ "Checks": [ "accessanalyzer_enabled" ], + "ConfigRequirements": [ + { + "Check": "accessanalyzer_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "Section": "1 Identity and Access Management", @@ -802,6 +824,14 @@ "Checks": [ "config_recorder_all_regions_enabled" ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "Section": "3 Logging", @@ -1054,6 +1084,14 @@ "Checks": [ "securityhub_enabled" ], + "ConfigRequirements": [ + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "Section": "4 Monitoring", diff --git a/prowler/compliance/aws/cis_3.0_aws.json b/prowler/compliance/aws/cis_3.0_aws.json index dd4c75a2f79..5540bc40cf3 100644 --- a/prowler/compliance/aws/cis_3.0_aws.json +++ b/prowler/compliance/aws/cis_3.0_aws.json @@ -75,6 +75,20 @@ "iam_user_accesskey_unused", "iam_user_console_access_unused" ], + "ConfigRequirements": [ + { + "Check": "iam_user_accesskey_unused", + "ConfigKey": "max_unused_access_keys_days", + "Operator": "lte", + "Value": 45 + }, + { + "Check": "iam_user_console_access_unused", + "ConfigKey": "max_console_access_days", + "Operator": "lte", + "Value": 45 + } + ], "Attributes": [ { "Section": "1 Identity and Access Management", @@ -265,6 +279,14 @@ "Checks": [ "accessanalyzer_enabled" ], + "ConfigRequirements": [ + { + "Check": "accessanalyzer_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "Section": "1 Identity and Access Management", @@ -756,6 +778,14 @@ "Checks": [ "config_recorder_all_regions_enabled" ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "Section": "3 Logging", @@ -1008,6 +1038,14 @@ "Checks": [ "securityhub_enabled" ], + "ConfigRequirements": [ + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "Section": "4 Monitoring", diff --git a/prowler/compliance/aws/cis_4.0_aws.json b/prowler/compliance/aws/cis_4.0_aws.json index 0c40f8ac09f..c8787ef4096 100644 --- a/prowler/compliance/aws/cis_4.0_aws.json +++ b/prowler/compliance/aws/cis_4.0_aws.json @@ -254,6 +254,20 @@ "iam_user_accesskey_unused", "iam_user_console_access_unused" ], + "ConfigRequirements": [ + { + "Check": "iam_user_accesskey_unused", + "ConfigKey": "max_unused_access_keys_days", + "Operator": "lte", + "Value": 45 + }, + { + "Check": "iam_user_console_access_unused", + "ConfigKey": "max_console_access_days", + "Operator": "lte", + "Value": 45 + } + ], "Attributes": [ { "Section": "1 Identity and Access Management", @@ -431,6 +445,14 @@ "Checks": [ "accessanalyzer_enabled" ], + "ConfigRequirements": [ + { + "Check": "accessanalyzer_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "Section": "1 Identity and Access Management", @@ -750,6 +772,14 @@ "Checks": [ "config_recorder_all_regions_enabled" ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "Section": "3 Logging", @@ -1234,6 +1264,14 @@ "Checks": [ "securityhub_enabled" ], + "ConfigRequirements": [ + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "Section": "4 Monitoring", diff --git a/prowler/compliance/aws/cis_5.0_aws.json b/prowler/compliance/aws/cis_5.0_aws.json index e870878c6b3..0c8d46e170a 100644 --- a/prowler/compliance/aws/cis_5.0_aws.json +++ b/prowler/compliance/aws/cis_5.0_aws.json @@ -232,6 +232,20 @@ "iam_user_accesskey_unused", "iam_user_console_access_unused" ], + "ConfigRequirements": [ + { + "Check": "iam_user_accesskey_unused", + "ConfigKey": "max_unused_access_keys_days", + "Operator": "lte", + "Value": 45 + }, + { + "Check": "iam_user_console_access_unused", + "ConfigKey": "max_console_access_days", + "Operator": "lte", + "Value": 45 + } + ], "Attributes": [ { "Section": "1 Identity and Access Management", @@ -409,6 +423,14 @@ "Checks": [ "accessanalyzer_enabled" ], + "ConfigRequirements": [ + { + "Check": "accessanalyzer_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "Section": "1 Identity and Access Management", @@ -728,6 +750,14 @@ "Checks": [ "config_recorder_all_regions_enabled" ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "Section": "3 Logging", @@ -1212,6 +1242,14 @@ "Checks": [ "securityhub_enabled" ], + "ConfigRequirements": [ + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "Section": "4 Monitoring", diff --git a/prowler/compliance/aws/cis_6.0_aws.json b/prowler/compliance/aws/cis_6.0_aws.json index 643c192b7bf..7ad62a4b658 100644 --- a/prowler/compliance/aws/cis_6.0_aws.json +++ b/prowler/compliance/aws/cis_6.0_aws.json @@ -232,6 +232,20 @@ "iam_user_accesskey_unused", "iam_user_console_access_unused" ], + "ConfigRequirements": [ + { + "Check": "iam_user_accesskey_unused", + "ConfigKey": "max_unused_access_keys_days", + "Operator": "lte", + "Value": 45 + }, + { + "Check": "iam_user_console_access_unused", + "ConfigKey": "max_console_access_days", + "Operator": "lte", + "Value": 45 + } + ], "Attributes": [ { "Section": "2 Identity and Access Management", @@ -409,6 +423,14 @@ "Checks": [ "accessanalyzer_enabled" ], + "ConfigRequirements": [ + { + "Check": "accessanalyzer_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "Section": "2 Identity and Access Management", @@ -728,6 +750,14 @@ "Checks": [ "config_recorder_all_regions_enabled" ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "Section": "4 Logging", @@ -1212,6 +1242,14 @@ "Checks": [ "securityhub_enabled" ], + "ConfigRequirements": [ + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "Section": "5 Monitoring", diff --git a/prowler/compliance/aws/cisa_aws.json b/prowler/compliance/aws/cisa_aws.json index fa8e27a0611..27b06a1ea48 100644 --- a/prowler/compliance/aws/cisa_aws.json +++ b/prowler/compliance/aws/cisa_aws.json @@ -136,6 +136,20 @@ "ec2_securitygroup_default_restrict_traffic", "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_22", "ec2_securitygroup_allow_ingress_from_internet_to_all_ports" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -367,6 +381,20 @@ "Checks": [ "guardduty_is_enabled", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { diff --git a/prowler/compliance/aws/ens_rd2022_aws.json b/prowler/compliance/aws/ens_rd2022_aws.json index 8fcd3263e9e..144437ce527 100644 --- a/prowler/compliance/aws/ens_rd2022_aws.json +++ b/prowler/compliance/aws/ens_rd2022_aws.json @@ -598,6 +598,14 @@ ], "Checks": [ "config_recorder_all_regions_enabled" + ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -624,6 +632,14 @@ ], "Checks": [ "config_recorder_all_regions_enabled" + ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -755,6 +771,14 @@ ], "Checks": [ "config_recorder_all_regions_enabled" + ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -781,6 +805,14 @@ ], "Checks": [ "config_recorder_all_regions_enabled" + ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -913,6 +945,14 @@ ], "Checks": [ "guardduty_is_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -940,6 +980,14 @@ "Checks": [ "guardduty_is_enabled", "guardduty_no_high_severity_findings" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -966,6 +1014,14 @@ ], "Checks": [ "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -1743,6 +1799,14 @@ ], "Checks": [ "guardduty_is_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -1821,6 +1885,14 @@ ], "Checks": [ "guardduty_is_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -1873,6 +1945,14 @@ ], "Checks": [ "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -1925,6 +2005,14 @@ ], "Checks": [ "guardduty_is_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -1951,6 +2039,14 @@ ], "Checks": [ "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -1977,6 +2073,14 @@ ], "Checks": [ "config_recorder_all_regions_enabled" + ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -2003,6 +2107,14 @@ ], "Checks": [ "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -2056,6 +2168,14 @@ ], "Checks": [ "guardduty_is_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -2082,6 +2202,14 @@ ], "Checks": [ "config_recorder_all_regions_enabled" + ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -4310,6 +4438,14 @@ ], "Checks": [ "drs_job_exist" + ], + "ConfigRequirements": [ + { + "Check": "drs_job_exist", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { diff --git a/prowler/compliance/aws/fedramp_20x_ksi_low_aws.json b/prowler/compliance/aws/fedramp_20x_ksi_low_aws.json index 6c6c5005826..15763ef48ed 100644 --- a/prowler/compliance/aws/fedramp_20x_ksi_low_aws.json +++ b/prowler/compliance/aws/fedramp_20x_ksi_low_aws.json @@ -37,6 +37,14 @@ "ssm_managed_compliant_patching", "ssm_managed_instance_compliance_association_compliant", "ssm_managed_instance_compliance_patch_compliant" + ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -146,6 +154,20 @@ "inspector2_active_findings_exist", "securityhub_enabled", "sns_topics_kms_encryption_at_rest_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -205,6 +227,14 @@ "resourceexplorer_indexes_found", "ssm_managed_instance_compliance_association_compliant", "trustedadvisor_premium_support_plan_subscribed" + ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -349,6 +379,14 @@ "config_recorder_all_regions_enabled", "inspector2_is_enabled", "resourceexplorer_indexes_found" + ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] } ] diff --git a/prowler/compliance/aws/fedramp_low_revision_4_aws.json b/prowler/compliance/aws/fedramp_low_revision_4_aws.json index 9824798552c..059de696754 100644 --- a/prowler/compliance/aws/fedramp_low_revision_4_aws.json +++ b/prowler/compliance/aws/fedramp_low_revision_4_aws.json @@ -46,6 +46,20 @@ "redshift_cluster_audit_logging", "s3_bucket_server_access_logging_enabled", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -115,6 +129,20 @@ "ec2_networkacl_allow_ingress_any_port", "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_22", "ec2_networkacl_allow_ingress_any_port" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -173,6 +201,14 @@ ], "Checks": [ "cloudwatch_log_group_retention_policy_specific_days_enabled" + ], + "ConfigRequirements": [ + { + "Check": "cloudwatch_log_group_retention_policy_specific_days_enabled", + "ConfigKey": "log_group_retention_days", + "Operator": "gte", + "Value": 90 + } ] }, { @@ -198,6 +234,20 @@ "rds_instance_enhanced_monitoring_enabled", "redshift_cluster_audit_logging", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -251,6 +301,14 @@ "guardduty_is_enabled", "ssm_managed_compliant_patching", "ssm_managed_compliant_patching" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -336,6 +394,20 @@ "guardduty_is_enabled", "guardduty_no_high_severity_findings", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -373,6 +445,14 @@ "rds_instance_multi_az", "redshift_cluster_automated_snapshot", "s3_bucket_object_versioning" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { diff --git a/prowler/compliance/aws/fedramp_moderate_revision_4_aws.json b/prowler/compliance/aws/fedramp_moderate_revision_4_aws.json index 11f66ada6b3..eaa3ea25dce 100644 --- a/prowler/compliance/aws/fedramp_moderate_revision_4_aws.json +++ b/prowler/compliance/aws/fedramp_moderate_revision_4_aws.json @@ -36,6 +36,20 @@ "iam_user_accesskey_unused", "iam_user_console_access_unused", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -65,6 +79,20 @@ "redshift_cluster_audit_logging", "s3_bucket_server_access_logging_enabled", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -82,6 +110,20 @@ "Checks": [ "guardduty_is_enabled", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -140,6 +182,20 @@ "redshift_cluster_audit_logging", "s3_bucket_server_access_logging_enabled", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -191,6 +247,38 @@ "iam_user_access_not_stale_to_sagemaker", "iam_user_accesskey_unused", "iam_user_console_access_unused" + ], + "ConfigRequirements": [ + { + "Check": "iam_user_accesskey_unused", + "ConfigKey": "max_unused_access_keys_days", + "Operator": "lte", + "Value": 90 + }, + { + "Check": "iam_user_console_access_unused", + "ConfigKey": "max_console_access_days", + "Operator": "lte", + "Value": 90 + }, + { + "Check": "iam_user_access_not_stale_to_bedrock", + "ConfigKey": "max_unused_bedrock_access_days", + "Operator": "lte", + "Value": 90 + }, + { + "Check": "iam_role_access_not_stale_to_bedrock", + "ConfigKey": "max_unused_bedrock_access_days", + "Operator": "lte", + "Value": 90 + }, + { + "Check": "iam_user_access_not_stale_to_sagemaker", + "ConfigKey": "max_unused_sagemaker_access_days", + "Operator": "lte", + "Value": 90 + } ] }, { @@ -371,6 +459,20 @@ "ec2_networkacl_allow_ingress_any_port", "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_22", "ec2_networkacl_allow_ingress_any_port" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -507,6 +609,20 @@ "s3_bucket_server_access_logging_enabled", "securityhub_enabled", "vpc_flow_logs_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -575,6 +691,14 @@ ], "Checks": [ "cloudwatch_log_group_retention_policy_specific_days_enabled" + ], + "ConfigRequirements": [ + { + "Check": "cloudwatch_log_group_retention_policy_specific_days_enabled", + "ConfigKey": "log_group_retention_days", + "Operator": "gte", + "Value": 90 + } ] }, { @@ -631,6 +755,20 @@ "rds_instance_enhanced_monitoring_enabled", "redshift_cluster_audit_logging", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -720,6 +858,14 @@ "guardduty_is_enabled", "ssm_managed_compliant_patching", "ssm_managed_compliant_patching" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -887,6 +1033,20 @@ "guardduty_is_enabled", "guardduty_no_high_severity_findings", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -909,6 +1069,20 @@ "guardduty_is_enabled", "guardduty_no_high_severity_findings", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -927,6 +1101,20 @@ "guardduty_is_enabled", "guardduty_no_high_severity_findings", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -945,6 +1133,20 @@ "guardduty_is_enabled", "guardduty_no_high_severity_findings", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -961,6 +1163,14 @@ "Checks": [ "guardduty_is_enabled", "guardduty_no_high_severity_findings" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -995,6 +1205,20 @@ "guardduty_is_enabled", "guardduty_no_high_severity_findings", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -1061,6 +1285,14 @@ "guardduty_is_enabled", "rds_instance_multi_az", "s3_bucket_object_versioning" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -1285,6 +1517,14 @@ ], "Checks": [ "guardduty_is_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -1307,6 +1547,20 @@ "guardduty_is_enabled", "redshift_cluster_audit_logging", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -1334,6 +1588,20 @@ "guardduty_is_enabled", "redshift_cluster_audit_logging", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -1361,6 +1629,20 @@ "guardduty_is_enabled", "redshift_cluster_audit_logging", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -1388,6 +1670,20 @@ "guardduty_is_enabled", "redshift_cluster_audit_logging", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -1414,6 +1710,20 @@ "guardduty_is_enabled", "guardduty_no_high_severity_findings", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { diff --git a/prowler/compliance/aws/ffiec_aws.json b/prowler/compliance/aws/ffiec_aws.json index 53f7275a85d..8a50b799259 100644 --- a/prowler/compliance/aws/ffiec_aws.json +++ b/prowler/compliance/aws/ffiec_aws.json @@ -37,6 +37,14 @@ ], "Checks": [ "guardduty_is_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -74,6 +82,20 @@ "cloudtrail_cloudwatch_logging_enabled", "guardduty_is_enabled", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -148,6 +170,20 @@ "Checks": [ "guardduty_is_enabled", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -166,6 +202,20 @@ "guardduty_is_enabled", "securityhub_enabled", "ssm_managed_compliant_patching" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -183,6 +233,20 @@ "Checks": [ "guardduty_is_enabled", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -237,6 +301,20 @@ "guardduty_is_enabled", "guardduty_no_high_severity_findings", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -254,6 +332,20 @@ "Checks": [ "guardduty_is_enabled", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -367,6 +459,14 @@ ], "Checks": [ "guardduty_is_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -386,6 +486,20 @@ "guardduty_is_enabled", "securityhub_enabled", "vpc_flow_logs_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -404,6 +518,20 @@ "guardduty_is_enabled", "securityhub_enabled", "ssm_managed_compliant_patching" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -826,6 +954,20 @@ "cloudwatch_changes_to_vpcs_alarm_configured", "guardduty_is_enabled", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -871,6 +1013,20 @@ "redshift_cluster_audit_logging", "s3_bucket_server_access_logging_enabled", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { diff --git a/prowler/compliance/aws/gdpr_aws.json b/prowler/compliance/aws/gdpr_aws.json index 930af1db587..a97a11e3dcd 100644 --- a/prowler/compliance/aws/gdpr_aws.json +++ b/prowler/compliance/aws/gdpr_aws.json @@ -59,6 +59,14 @@ "cloudwatch_log_metric_filter_security_group_changes", "cloudwatch_log_metric_filter_unauthorized_api_calls", "vpc_flow_logs_enabled" + ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -85,6 +93,14 @@ "kms_cmk_rotation_enabled", "redshift_cluster_audit_logging", "vpc_flow_logs_enabled" + ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { diff --git a/prowler/compliance/aws/gxp_21_cfr_part_11_aws.json b/prowler/compliance/aws/gxp_21_cfr_part_11_aws.json index bcf8f19c3b2..871af9e726e 100644 --- a/prowler/compliance/aws/gxp_21_cfr_part_11_aws.json +++ b/prowler/compliance/aws/gxp_21_cfr_part_11_aws.json @@ -350,6 +350,20 @@ "cloudtrail_cloudwatch_logging_enabled", "guardduty_is_enabled", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] } ] diff --git a/prowler/compliance/aws/gxp_eu_annex_11_aws.json b/prowler/compliance/aws/gxp_eu_annex_11_aws.json index 1ceb3f817bb..fdca6d17474 100644 --- a/prowler/compliance/aws/gxp_eu_annex_11_aws.json +++ b/prowler/compliance/aws/gxp_eu_annex_11_aws.json @@ -19,6 +19,14 @@ "Checks": [ "cloudtrail_multi_region_enabled", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -146,6 +154,14 @@ ], "Checks": [ "config_recorder_all_regions_enabled" + ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -238,6 +254,14 @@ ], "Checks": [ "config_recorder_all_regions_enabled" + ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -253,6 +277,14 @@ ], "Checks": [ "config_recorder_all_regions_enabled" + ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { diff --git a/prowler/compliance/aws/hipaa_aws.json b/prowler/compliance/aws/hipaa_aws.json index 036489d7123..9eb243e6ccf 100644 --- a/prowler/compliance/aws/hipaa_aws.json +++ b/prowler/compliance/aws/hipaa_aws.json @@ -19,6 +19,20 @@ "Checks": [ "config_recorder_all_regions_enabled", "guardduty_is_enabled" + ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -102,6 +116,20 @@ "s3_bucket_server_access_logging_enabled", "securityhub_enabled", "vpc_flow_logs_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -161,6 +189,20 @@ "s3_bucket_server_access_logging_enabled", "securityhub_enabled", "vpc_flow_logs_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -328,6 +370,20 @@ "guardduty_is_enabled", "cloudwatch_log_metric_filter_authentication_failures", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -373,6 +429,20 @@ "cloudwatch_log_metric_filter_authentication_failures", "cloudwatch_log_metric_filter_root_usage", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -402,6 +472,20 @@ "s3_bucket_server_access_logging_enabled", "securityhub_enabled", "vpc_flow_logs_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -514,6 +598,20 @@ "Checks": [ "guardduty_is_enabled", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -649,6 +747,20 @@ "s3_bucket_server_access_logging_enabled", "securityhub_enabled", "vpc_flow_logs_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -756,6 +868,20 @@ "s3_bucket_secure_transport_policy", "s3_bucket_server_access_logging_enabled", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { diff --git a/prowler/compliance/aws/iso27001_2013_aws.json b/prowler/compliance/aws/iso27001_2013_aws.json index 190693d1218..1de8c23db8b 100644 --- a/prowler/compliance/aws/iso27001_2013_aws.json +++ b/prowler/compliance/aws/iso27001_2013_aws.json @@ -311,6 +311,14 @@ ], "Checks": [ "config_recorder_all_regions_enabled" + ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -875,6 +883,38 @@ "iam_user_access_not_stale_to_sagemaker", "iam_user_accesskey_unused", "iam_user_console_access_unused" + ], + "ConfigRequirements": [ + { + "Check": "iam_user_accesskey_unused", + "ConfigKey": "max_unused_access_keys_days", + "Operator": "lte", + "Value": 90 + }, + { + "Check": "iam_user_console_access_unused", + "ConfigKey": "max_console_access_days", + "Operator": "lte", + "Value": 90 + }, + { + "Check": "iam_user_access_not_stale_to_bedrock", + "ConfigKey": "max_unused_bedrock_access_days", + "Operator": "lte", + "Value": 90 + }, + { + "Check": "iam_role_access_not_stale_to_bedrock", + "ConfigKey": "max_unused_bedrock_access_days", + "Operator": "lte", + "Value": 90 + }, + { + "Check": "iam_user_access_not_stale_to_sagemaker", + "ConfigKey": "max_unused_sagemaker_access_days", + "Operator": "lte", + "Value": 90 + } ] }, { @@ -1052,6 +1092,20 @@ "Checks": [ "iam_user_accesskey_unused", "iam_user_console_access_unused" + ], + "ConfigRequirements": [ + { + "Check": "iam_user_accesskey_unused", + "ConfigKey": "max_unused_access_keys_days", + "Operator": "lte", + "Value": 90 + }, + { + "Check": "iam_user_console_access_unused", + "ConfigKey": "max_console_access_days", + "Operator": "lte", + "Value": 90 + } ] }, { @@ -1261,6 +1315,20 @@ "Checks": [ "iam_user_accesskey_unused", "iam_user_console_access_unused" + ], + "ConfigRequirements": [ + { + "Check": "iam_user_accesskey_unused", + "ConfigKey": "max_unused_access_keys_days", + "Operator": "lte", + "Value": 90 + }, + { + "Check": "iam_user_console_access_unused", + "ConfigKey": "max_console_access_days", + "Operator": "lte", + "Value": 90 + } ] }, { diff --git a/prowler/compliance/aws/iso27001_2022_aws.json b/prowler/compliance/aws/iso27001_2022_aws.json index d47bfcf1d12..563b856317a 100644 --- a/prowler/compliance/aws/iso27001_2022_aws.json +++ b/prowler/compliance/aws/iso27001_2022_aws.json @@ -20,6 +20,14 @@ "Checks": [ "securityhub_enabled", "wellarchitected_workload_no_high_or_medium_risks" + ], + "ConfigRequirements": [ + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -277,6 +285,14 @@ ], "Checks": [ "config_recorder_all_regions_enabled" + ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -331,6 +347,14 @@ "Checks": [ "guardduty_is_enabled", "guardduty_no_high_severity_findings" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -362,6 +386,14 @@ "Checks": [ "guardduty_is_enabled", "guardduty_no_high_severity_findings" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -378,6 +410,14 @@ ], "Checks": [ "config_recorder_all_regions_enabled" + ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -424,6 +464,14 @@ "guardduty_is_enabled", "guardduty_no_high_severity_findings", "guardduty_centrally_managed" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -472,6 +520,14 @@ "guardduty_is_enabled", "guardduty_no_high_severity_findings", "guardduty_centrally_managed" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -490,6 +546,14 @@ "guardduty_is_enabled", "guardduty_no_high_severity_findings", "guardduty_centrally_managed" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -1004,6 +1068,14 @@ "organizations_account_part_of_organizations", "accessanalyzer_enabled", "accessanalyzer_enabled_without_findings" + ], + "ConfigRequirements": [ + { + "Check": "accessanalyzer_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -1080,6 +1152,14 @@ "Checks": [ "guardduty_is_enabled", "guardduty_no_high_severity_findings" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -1111,6 +1191,14 @@ "Checks": [ "guardduty_is_enabled", "guardduty_no_high_severity_findings" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -1749,6 +1837,14 @@ "vpc_default_security_group_closed", "vpc_flow_logs_enabled", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { diff --git a/prowler/compliance/aws/kisa_isms_p_2023_aws.json b/prowler/compliance/aws/kisa_isms_p_2023_aws.json index c24b5a23e5c..7b0446ac3f0 100644 --- a/prowler/compliance/aws/kisa_isms_p_2023_aws.json +++ b/prowler/compliance/aws/kisa_isms_p_2023_aws.json @@ -1211,6 +1211,14 @@ "rds_instance_default_admin", "redshift_cluster_non_default_database_name" ], + "ConfigRequirements": [ + { + "Check": "accessanalyzer_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "Domain": "2. Control Measures Requirements", @@ -1416,6 +1424,14 @@ "iam_user_administrator_access_policy", "organizations_delegated_administrators" ], + "ConfigRequirements": [ + { + "Check": "accessanalyzer_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "Domain": "2. Control Measures Requirements", @@ -1486,6 +1502,14 @@ "ssm_documents_set_as_public", "vpc_endpoint_services_allowed_principals_trust_boundaries" ], + "ConfigRequirements": [ + { + "Check": "accessanalyzer_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "Domain": "2. Control Measures Requirements", @@ -2082,6 +2106,17 @@ "transfer_server_in_transit_encryption_enabled", "workspaces_volume_encryption_enabled" ], + "ConfigRequirements": [ + { + "Check": "acm_certificates_with_secure_key_algorithms", + "ConfigKey": "insecure_key_algorithms", + "Operator": "superset", + "Value": [ + "RSA-1024", + "P-192" + ] + } + ], "Attributes": [ { "Domain": "2. Control Measures Requirements", @@ -2819,6 +2854,20 @@ "wafv2_webacl_rule_logging_enabled", "wafv2_webacl_with_rules" ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "Domain": "2. Control Measures Requirements", @@ -3319,6 +3368,47 @@ "workspaces_volume_encryption_enabled", "workspaces_vpc_2private_1public_subnets_nat" ], + "ConfigRequirements": [ + { + "Check": "accessanalyzer_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "drs_job_exist", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "acm_certificates_with_secure_key_algorithms", + "ConfigKey": "insecure_key_algorithms", + "Operator": "superset", + "Value": [ + "RSA-1024", + "P-192" + ] + } + ], "Attributes": [ { "Domain": "2. Control Measures Requirements", @@ -3711,6 +3801,14 @@ "s3_bucket_event_notifications_enabled", "trustedadvisor_errors_and_warnings" ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "Domain": "2. Control Measures Requirements", @@ -3829,6 +3927,14 @@ "s3_bucket_object_lock", "s3_bucket_object_versioning" ], + "ConfigRequirements": [ + { + "Check": "drs_job_exist", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "Domain": "2. Control Measures Requirements", @@ -3866,6 +3972,14 @@ "Checks": [ "drs_job_exist" ], + "ConfigRequirements": [ + { + "Check": "drs_job_exist", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "Domain": "2. Control Measures Requirements", diff --git a/prowler/compliance/aws/kisa_isms_p_2023_korean_aws.json b/prowler/compliance/aws/kisa_isms_p_2023_korean_aws.json index 50e2a149a5b..40b338ce41f 100644 --- a/prowler/compliance/aws/kisa_isms_p_2023_korean_aws.json +++ b/prowler/compliance/aws/kisa_isms_p_2023_korean_aws.json @@ -1211,6 +1211,14 @@ "rds_instance_default_admin", "redshift_cluster_non_default_database_name" ], + "ConfigRequirements": [ + { + "Check": "accessanalyzer_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "Domain": "2. 보호대책 요구사항", @@ -1416,6 +1424,14 @@ "iam_user_administrator_access_policy", "organizations_delegated_administrators" ], + "ConfigRequirements": [ + { + "Check": "accessanalyzer_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "Domain": "2. 보호대책 요구사항", @@ -1485,6 +1501,14 @@ "ssm_documents_set_as_public", "vpc_endpoint_services_allowed_principals_trust_boundaries" ], + "ConfigRequirements": [ + { + "Check": "accessanalyzer_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "Domain": "2. 보호대책 요구사항", @@ -2084,6 +2108,17 @@ "transfer_server_in_transit_encryption_enabled", "workspaces_volume_encryption_enabled" ], + "ConfigRequirements": [ + { + "Check": "acm_certificates_with_secure_key_algorithms", + "ConfigKey": "insecure_key_algorithms", + "Operator": "superset", + "Value": [ + "RSA-1024", + "P-192" + ] + } + ], "Attributes": [ { "Domain": "2. 보호대책 요구사항", @@ -2822,6 +2857,20 @@ "wafv2_webacl_rule_logging_enabled", "wafv2_webacl_with_rules" ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "Domain": "2. 보호대책 요구사항", @@ -3322,6 +3371,47 @@ "workspaces_volume_encryption_enabled", "workspaces_vpc_2private_1public_subnets_nat" ], + "ConfigRequirements": [ + { + "Check": "accessanalyzer_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "drs_job_exist", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "acm_certificates_with_secure_key_algorithms", + "ConfigKey": "insecure_key_algorithms", + "Operator": "superset", + "Value": [ + "RSA-1024", + "P-192" + ] + } + ], "Attributes": [ { "Domain": "2. 보호대책 요구사항", @@ -3714,6 +3804,14 @@ "s3_bucket_event_notifications_enabled", "trustedadvisor_errors_and_warnings" ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "Domain": "2. 보호대책 요구사항", @@ -3832,6 +3930,14 @@ "s3_bucket_object_lock", "s3_bucket_object_versioning" ], + "ConfigRequirements": [ + { + "Check": "drs_job_exist", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "Domain": "2. 보호대책 요구사항", @@ -3869,6 +3975,14 @@ "Checks": [ "drs_job_exist" ], + "ConfigRequirements": [ + { + "Check": "drs_job_exist", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "Domain": "2. 보호대책 요구사항", diff --git a/prowler/compliance/aws/mitre_attack_aws.json b/prowler/compliance/aws/mitre_attack_aws.json index 3d1d5fd378f..3ac8cf04323 100644 --- a/prowler/compliance/aws/mitre_attack_aws.json +++ b/prowler/compliance/aws/mitre_attack_aws.json @@ -35,6 +35,32 @@ "awslambda_function_not_publicly_accessible", "ec2_instance_public_ip" ], + "ConfigRequirements": [ + { + "Check": "drs_job_exist", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "AWSService": "AWS CloudEndure Disaster Recovery", @@ -200,6 +226,26 @@ "organizations_scp_check_deny_regions", "securityhub_enabled" ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "AWSService": "Amazon GuardDuty", @@ -348,6 +394,14 @@ "Checks": [ "config_recorder_all_regions_enabled" ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "AWSService": "AWS Config", @@ -393,6 +447,26 @@ "guardduty_is_enabled", "securityhub_enabled" ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "AWSService": "AWS Config", @@ -444,6 +518,14 @@ "Checks": [ "config_recorder_all_regions_enabled" ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "AWSService": "AWS Config", @@ -557,6 +639,14 @@ "Checks": [ "config_recorder_all_regions_enabled" ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "AWSService": "AWS Config", @@ -634,6 +724,26 @@ "inspector2_is_enabled", "inspector2_active_findings_exist" ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "AWSService": "AWS Config", @@ -821,6 +931,26 @@ "inspector2_is_enabled", "inspector2_active_findings_exist" ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "AWSService": "AWS Config", @@ -984,6 +1114,14 @@ "cloudfront_distributions_https_enabled", "s3_bucket_secure_transport_policy" ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "AWSService": "AWS CloudWatch", @@ -1057,6 +1195,14 @@ "ssm_document_secrets", "secretsmanager_automatic_rotation_enabled" ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "AWSService": "AWS CloudHSM", @@ -1143,6 +1289,14 @@ "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_sql_server_1433_1434", "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_telnet_23" ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "AWSService": "AWS Network Firewall", @@ -1218,6 +1372,14 @@ "s3_bucket_default_encryption", "rds_instance_storage_encrypted" ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "AWSService": "AWS Config", @@ -1264,6 +1426,20 @@ "securityhub_enabled", "macie_is_enabled" ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "AWSService": "AWS Config", @@ -1441,6 +1617,20 @@ "s3_bucket_object_versioning", "config_recorder_all_regions_enabled" ], + "ConfigRequirements": [ + { + "Check": "drs_job_exist", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "AWSService": "AWS CloudEndure Disaster Recovery", @@ -1518,6 +1708,20 @@ "efs_have_backup_enabled", "config_recorder_all_regions_enabled" ], + "ConfigRequirements": [ + { + "Check": "drs_job_exist", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "AWSService": "AWS CloudEndure Disaster Recovery", @@ -1566,6 +1770,20 @@ "drs_job_exist", "config_recorder_all_regions_enabled" ], + "ConfigRequirements": [ + { + "Check": "drs_job_exist", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "AWSService": "AWS CloudEndure Disaster Recovery", @@ -1639,6 +1857,14 @@ "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_sql_server_1433_1434", "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_telnet_23" ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "AWSService": "AWS Shield", @@ -1686,6 +1912,14 @@ "drs_job_exist", "rds_instance_backup_enabled" ], + "ConfigRequirements": [ + { + "Check": "drs_job_exist", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "AWSService": "AWS CloudEndure Disaster Recovery", @@ -1743,6 +1977,20 @@ "cloudwatch_log_metric_filter_sign_in_without_mfa", "cloudwatch_log_metric_filter_unauthorized_api_calls" ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "AWSService": "AWS CloudWatch", @@ -1819,6 +2067,20 @@ "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_sql_server_1433_1434", "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_telnet_23" ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "AWSService": "AWS Config", @@ -1910,6 +2172,20 @@ "iam_policy_no_full_access_to_cloudtrail", "iam_policy_no_full_access_to_kms" ], + "ConfigRequirements": [ + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "AWSService": "AWS Organizations", @@ -1993,6 +2269,14 @@ "Checks": [ "guardduty_is_enabled" ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "AWSService": "Amazon GuardDuty", @@ -2071,6 +2355,14 @@ "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_sql_server_1433_1434", "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_telnet_23" ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "AWSService": "AWS IoT Device Defender", diff --git a/prowler/compliance/aws/nis2_aws.json b/prowler/compliance/aws/nis2_aws.json index d7f193c6c08..3a4d567d256 100644 --- a/prowler/compliance/aws/nis2_aws.json +++ b/prowler/compliance/aws/nis2_aws.json @@ -597,6 +597,14 @@ "accessanalyzer_enabled", "cloudwatch_log_metric_filter_root_usage" ], + "ConfigRequirements": [ + { + "Check": "accessanalyzer_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "Section": "3 INCIDENT HANDLING (ARTICLE 21(2), POINT (B), OF DIRECTIVE (EU) 2022/2555)", @@ -1511,6 +1519,17 @@ "Checks": [ "acm_certificates_with_secure_key_algorithms" ], + "ConfigRequirements": [ + { + "Check": "acm_certificates_with_secure_key_algorithms", + "ConfigKey": "insecure_key_algorithms", + "Operator": "superset", + "Value": [ + "RSA-1024", + "P-192" + ] + } + ], "Attributes": [ { "Section": "9 CRYPTOGRAPHY (ARTICLE 21(2), POINT (H), OF DIRECTIVE (EU) 2022/2555)", @@ -1528,6 +1547,17 @@ "route53_domains_privacy_protection_enabled", "iam_no_expired_server_certificates_stored" ], + "ConfigRequirements": [ + { + "Check": "acm_certificates_with_secure_key_algorithms", + "ConfigKey": "insecure_key_algorithms", + "Operator": "superset", + "Value": [ + "RSA-1024", + "P-192" + ] + } + ], "Attributes": [ { "Section": "9 CRYPTOGRAPHY (ARTICLE 21(2), POINT (H), OF DIRECTIVE (EU) 2022/2555)", @@ -1645,6 +1675,14 @@ "efs_access_point_enforce_user_identity", "efs_not_publicly_accessible" ], + "ConfigRequirements": [ + { + "Check": "accessanalyzer_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "Section": "11 ACCESS CONTROL (ARTICLE 21(2), POINTS (I) AND (J), OF DIRECTIVE (EU) 2022/2555)", @@ -1676,6 +1714,14 @@ "Checks": [ "accessanalyzer_enabled" ], + "ConfigRequirements": [ + { + "Check": "accessanalyzer_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "Section": "11 ACCESS CONTROL (ARTICLE 21(2), POINTS (I) AND (J), OF DIRECTIVE (EU) 2022/2555)", @@ -1726,6 +1772,14 @@ "Checks": [ "accessanalyzer_enabled" ], + "ConfigRequirements": [ + { + "Check": "accessanalyzer_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "Section": "11 ACCESS CONTROL (ARTICLE 21(2), POINTS (I) AND (J), OF DIRECTIVE (EU) 2022/2555)", diff --git a/prowler/compliance/aws/nist_800_171_revision_2_aws.json b/prowler/compliance/aws/nist_800_171_revision_2_aws.json index e5a456bbb3e..921bd33a537 100644 --- a/prowler/compliance/aws/nist_800_171_revision_2_aws.json +++ b/prowler/compliance/aws/nist_800_171_revision_2_aws.json @@ -230,6 +230,20 @@ "rds_instance_integration_cloudwatch_logs", "s3_bucket_server_access_logging_enabled", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -321,6 +335,20 @@ "s3_bucket_server_access_logging_enabled", "securityhub_enabled", "vpc_flow_logs_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -344,6 +372,14 @@ "guardduty_is_enabled", "rds_instance_integration_cloudwatch_logs", "s3_bucket_server_access_logging_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -383,6 +419,20 @@ "Checks": [ "guardduty_is_enabled", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -400,6 +450,20 @@ "cloudtrail_cloudwatch_logging_enabled", "guardduty_is_enabled", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -687,6 +751,20 @@ "s3_bucket_server_access_logging_enabled", "securityhub_enabled", "vpc_flow_logs_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -715,6 +793,20 @@ "s3_bucket_server_access_logging_enabled", "securityhub_enabled", "vpc_flow_logs_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -732,6 +824,20 @@ "guardduty_is_enabled", "guardduty_no_high_severity_findings", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -749,6 +855,20 @@ "guardduty_is_enabled", "guardduty_no_high_severity_findings", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -772,6 +892,20 @@ "guardduty_is_enabled", "rds_instance_enhanced_monitoring_enabled", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -809,6 +943,20 @@ "ec2_networkacl_allow_ingress_any_port", "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_22", "ec2_networkacl_allow_ingress_any_port" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -1028,6 +1176,20 @@ "Checks": [ "guardduty_is_enabled", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -1047,6 +1209,20 @@ "securityhub_enabled", "ssm_managed_compliant_patching", "ssm_managed_compliant_patching" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -1064,6 +1240,20 @@ "guardduty_is_enabled", "securityhub_enabled", "ssm_managed_compliant_patching" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -1079,6 +1269,14 @@ ], "Checks": [ "guardduty_is_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -1105,6 +1303,20 @@ "s3_bucket_server_access_logging_enabled", "securityhub_enabled", "vpc_flow_logs_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -1131,6 +1343,20 @@ "s3_bucket_server_access_logging_enabled", "securityhub_enabled", "vpc_flow_logs_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] } ] diff --git a/prowler/compliance/aws/nist_800_53_revision_4_aws.json b/prowler/compliance/aws/nist_800_53_revision_4_aws.json index deb2a3cc254..8bd36a3910e 100644 --- a/prowler/compliance/aws/nist_800_53_revision_4_aws.json +++ b/prowler/compliance/aws/nist_800_53_revision_4_aws.json @@ -27,6 +27,20 @@ "iam_user_accesskey_unused", "iam_user_console_access_unused", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -47,6 +61,38 @@ "iam_user_access_not_stale_to_sagemaker", "iam_user_accesskey_unused", "iam_user_console_access_unused" + ], + "ConfigRequirements": [ + { + "Check": "iam_user_accesskey_unused", + "ConfigKey": "max_unused_access_keys_days", + "Operator": "lte", + "Value": 90 + }, + { + "Check": "iam_user_console_access_unused", + "ConfigKey": "max_console_access_days", + "Operator": "lte", + "Value": 90 + }, + { + "Check": "iam_user_access_not_stale_to_bedrock", + "ConfigKey": "max_unused_bedrock_access_days", + "Operator": "lte", + "Value": 90 + }, + { + "Check": "iam_role_access_not_stale_to_bedrock", + "ConfigKey": "max_unused_bedrock_access_days", + "Operator": "lte", + "Value": 90 + }, + { + "Check": "iam_user_access_not_stale_to_sagemaker", + "ConfigKey": "max_unused_sagemaker_access_days", + "Operator": "lte", + "Value": 90 + } ] }, { @@ -73,6 +119,20 @@ "rds_instance_integration_cloudwatch_logs", "redshift_cluster_audit_logging", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -90,6 +150,20 @@ "Checks": [ "guardduty_is_enabled", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -125,6 +199,20 @@ "redshift_cluster_audit_logging", "s3_bucket_server_access_logging_enabled", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -270,6 +358,20 @@ "Checks": [ "guardduty_is_enabled", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -399,6 +501,20 @@ "cloudwatch_changes_to_vpcs_alarm_configured", "guardduty_is_enabled", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -421,6 +537,20 @@ "cloudwatch_changes_to_vpcs_alarm_configured", "guardduty_is_enabled", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -534,6 +664,20 @@ "guardduty_is_enabled", "rds_instance_enhanced_monitoring_enabled", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -827,6 +971,14 @@ "Checks": [ "guardduty_is_enabled", "guardduty_no_high_severity_findings" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -860,6 +1012,20 @@ "guardduty_is_enabled", "guardduty_no_high_severity_findings", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -1110,6 +1276,14 @@ ], "Checks": [ "guardduty_is_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -1133,6 +1307,20 @@ "ec2_instance_imdsv2_enabled", "guardduty_is_enabled", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -1155,6 +1343,20 @@ "cloudwatch_changes_to_vpcs_alarm_configured", "guardduty_is_enabled", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -1177,6 +1379,20 @@ "cloudwatch_changes_to_vpcs_alarm_configured", "guardduty_is_enabled", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -1194,6 +1410,20 @@ "Checks": [ "guardduty_is_enabled", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -1218,6 +1448,20 @@ "guardduty_is_enabled", "guardduty_no_high_severity_findings", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { diff --git a/prowler/compliance/aws/nist_800_53_revision_5_aws.json b/prowler/compliance/aws/nist_800_53_revision_5_aws.json index c9eb755e49e..e0ef9362292 100644 --- a/prowler/compliance/aws/nist_800_53_revision_5_aws.json +++ b/prowler/compliance/aws/nist_800_53_revision_5_aws.json @@ -220,6 +220,14 @@ ], "Checks": [ "guardduty_is_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -944,6 +952,14 @@ ], "Checks": [ "guardduty_is_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -1629,6 +1645,14 @@ "Checks": [ "cloudtrail_multi_region_enabled", "guardduty_is_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -1828,6 +1852,20 @@ "cloudwatch_changes_to_vpcs_alarm_configured", "guardduty_is_enabled", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -1906,6 +1944,20 @@ "cloudwatch_changes_to_vpcs_alarm_configured", "guardduty_is_enabled", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -2290,6 +2342,20 @@ "s3_bucket_server_access_logging_enabled", "securityhub_enabled", "vpc_flow_logs_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -2352,6 +2418,20 @@ "s3_bucket_server_access_logging_enabled", "securityhub_enabled", "vpc_flow_logs_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -2387,6 +2467,20 @@ "s3_bucket_server_access_logging_enabled", "securityhub_enabled", "vpc_flow_logs_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -2466,6 +2560,20 @@ "Checks": [ "guardduty_is_enabled", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -2487,6 +2595,20 @@ "guardduty_is_enabled", "rds_instance_enhanced_monitoring_enabled", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -2522,6 +2644,20 @@ "s3_bucket_server_access_logging_enabled", "securityhub_enabled", "vpc_flow_logs_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -2904,6 +3040,14 @@ "guardduty_is_enabled", "ssm_managed_compliant_patching", "ssm_managed_compliant_patching" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -4079,6 +4223,14 @@ ], "Checks": [ "guardduty_is_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -4095,6 +4247,14 @@ ], "Checks": [ "guardduty_is_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -4149,6 +4309,20 @@ "s3_bucket_server_access_logging_enabled", "securityhub_enabled", "vpc_flow_logs_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -4184,6 +4358,20 @@ "s3_bucket_server_access_logging_enabled", "securityhub_enabled", "vpc_flow_logs_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -4199,6 +4387,14 @@ ], "Checks": [ "guardduty_is_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -4270,6 +4466,20 @@ "s3_bucket_server_access_logging_enabled", "securityhub_enabled", "vpc_flow_logs_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -4286,6 +4496,14 @@ ], "Checks": [ "guardduty_is_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -4303,6 +4521,14 @@ ], "Checks": [ "guardduty_is_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -4320,6 +4546,14 @@ ], "Checks": [ "guardduty_is_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -4336,6 +4570,14 @@ ], "Checks": [ "guardduty_is_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -4353,6 +4595,14 @@ "Checks": [ "guardduty_is_enabled", "ssm_managed_compliant_patching" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -4369,6 +4619,14 @@ ], "Checks": [ "guardduty_is_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -4385,6 +4643,14 @@ ], "Checks": [ "guardduty_is_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -4401,6 +4667,14 @@ ], "Checks": [ "guardduty_is_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -4418,6 +4692,14 @@ ], "Checks": [ "guardduty_is_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -4435,6 +4717,14 @@ ], "Checks": [ "guardduty_is_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -4516,6 +4806,14 @@ ], "Checks": [ "guardduty_is_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -4558,6 +4856,14 @@ ], "Checks": [ "guardduty_is_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -4575,6 +4881,14 @@ ], "Checks": [ "guardduty_is_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -4591,6 +4905,14 @@ ], "Checks": [ "guardduty_is_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -4607,6 +4929,14 @@ ], "Checks": [ "guardduty_is_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -5683,6 +6013,14 @@ ], "Checks": [ "guardduty_is_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -5850,6 +6188,14 @@ ], "Checks": [ "guardduty_is_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -5890,6 +6236,14 @@ ], "Checks": [ "guardduty_is_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -5907,6 +6261,14 @@ ], "Checks": [ "guardduty_is_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -5924,6 +6286,14 @@ ], "Checks": [ "guardduty_is_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -5940,6 +6310,14 @@ ], "Checks": [ "guardduty_is_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -5956,6 +6334,14 @@ ], "Checks": [ "guardduty_is_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -5988,6 +6374,14 @@ ], "Checks": [ "guardduty_is_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -6012,6 +6406,14 @@ "rds_instance_integration_cloudwatch_logs", "redshift_cluster_audit_logging", "s3_bucket_server_access_logging_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -6028,6 +6430,14 @@ ], "Checks": [ "guardduty_is_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -6045,6 +6455,14 @@ ], "Checks": [ "guardduty_is_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -6062,6 +6480,14 @@ ], "Checks": [ "guardduty_is_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -6078,6 +6504,14 @@ ], "Checks": [ "guardduty_is_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -6114,6 +6548,14 @@ ], "Checks": [ "guardduty_is_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -6130,6 +6572,14 @@ ], "Checks": [ "guardduty_is_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -6197,6 +6647,14 @@ ], "Checks": [ "guardduty_is_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -6213,6 +6671,14 @@ ], "Checks": [ "guardduty_is_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -6233,6 +6699,14 @@ "cloudwatch_changes_to_network_route_tables_alarm_configured", "cloudwatch_changes_to_vpcs_alarm_configured", "guardduty_is_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -6253,6 +6727,14 @@ "cloudwatch_changes_to_network_route_tables_alarm_configured", "cloudwatch_changes_to_vpcs_alarm_configured", "guardduty_is_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { diff --git a/prowler/compliance/aws/nist_csf_1.1_aws.json b/prowler/compliance/aws/nist_csf_1.1_aws.json index a55097e70c5..9921efce568 100644 --- a/prowler/compliance/aws/nist_csf_1.1_aws.json +++ b/prowler/compliance/aws/nist_csf_1.1_aws.json @@ -48,6 +48,20 @@ "Checks": [ "guardduty_is_enabled", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -99,6 +113,20 @@ "guardduty_no_high_severity_findings", "s3_bucket_server_access_logging_enabled", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -144,6 +172,20 @@ "s3_bucket_server_access_logging_enabled", "securityhub_enabled", "vpc_flow_logs_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -179,6 +221,26 @@ "cloudwatch_log_metric_filter_unauthorized_api_calls", "rds_instance_enhanced_monitoring_enabled", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -201,6 +263,20 @@ "guardduty_is_enabled", "s3_bucket_server_access_logging_enabled", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -218,6 +294,20 @@ "Checks": [ "guardduty_is_enabled", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -243,6 +333,20 @@ "guardduty_is_enabled", "guardduty_no_high_severity_findings", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -265,6 +369,20 @@ "guardduty_is_enabled", "s3_bucket_server_access_logging_enabled", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -291,6 +409,20 @@ "s3_bucket_server_access_logging_enabled", "securityhub_enabled", "vpc_flow_logs_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -316,6 +448,20 @@ "guardduty_is_enabled", "guardduty_no_high_severity_findings", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -349,6 +495,14 @@ "Checks": [ "config_recorder_all_regions_enabled", "ec2_instance_managed_by_ssm" + ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -454,6 +608,20 @@ "guardduty_is_enabled", "securityhub_enabled", "ssm_managed_compliant_patching" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -471,6 +639,20 @@ "Checks": [ "guardduty_is_enabled", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -488,6 +670,20 @@ "Checks": [ "guardduty_is_enabled", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -523,6 +719,26 @@ "cloudwatch_log_metric_filter_unauthorized_api_calls", "rds_instance_enhanced_monitoring_enabled", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -554,6 +770,26 @@ "cloudwatch_log_metric_filter_unauthorized_api_calls", "rds_instance_enhanced_monitoring_enabled", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -827,6 +1063,20 @@ "sagemaker_notebook_instance_without_direct_internet_access_configured", "securityhub_enabled", "vpc_flow_logs_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -881,6 +1131,14 @@ "Checks": [ "ec2_instance_managed_by_ssm", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -1035,6 +1293,14 @@ "ec2_instance_managed_by_ssm", "ssm_managed_compliant_patching", "ssm_managed_compliant_patching" + ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { diff --git a/prowler/compliance/aws/nist_csf_2.0_aws.json b/prowler/compliance/aws/nist_csf_2.0_aws.json index e890b085734..c06eeec11d3 100644 --- a/prowler/compliance/aws/nist_csf_2.0_aws.json +++ b/prowler/compliance/aws/nist_csf_2.0_aws.json @@ -72,6 +72,20 @@ "securityhub_enabled", "wellarchitected_workload_no_high_or_medium_risks", "servicecatalog_portfolio_shared_within_organization_only" + ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -322,6 +336,26 @@ "wellarchitected_workload_no_high_or_medium_risks", "organizations_delegated_administrators", "organizations_tags_policies_enabled_and_attached" + ], + "ConfigRequirements": [ + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "accessanalyzer_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -352,6 +386,26 @@ "vpc_flow_logs_enabled", "iam_root_mfa_enabled", "iam_root_credentials_management_enabled" + ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "accessanalyzer_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -408,6 +462,20 @@ "accessanalyzer_enabled", "guardduty_no_high_severity_findings", "trustedadvisor_errors_and_warnings" + ], + "ConfigRequirements": [ + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "accessanalyzer_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -442,6 +510,26 @@ "organizations_scp_check_deny_regions", "organizations_tags_policies_enabled_and_attached", "organizations_delegated_administrators" + ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "accessanalyzer_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -574,6 +662,14 @@ "opensearch_service_domains_encryption_at_rest_enabled", "redshift_cluster_encrypted_at_rest", "sns_topics_kms_encryption_at_rest_enabled" + ], + "ConfigRequirements": [ + { + "Check": "drs_job_exist", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -610,6 +706,17 @@ "iam_inline_policy_allows_privilege_escalation", "ssm_documents_set_as_public", "s3_bucket_shadow_resource_vulnerability" + ], + "ConfigRequirements": [ + { + "Check": "acm_certificates_with_secure_key_algorithms", + "ConfigKey": "insecure_key_algorithms", + "Operator": "superset", + "Value": [ + "RSA-1024", + "P-192" + ] + } ] }, { @@ -726,6 +833,14 @@ "iam_role_administratoraccess_policy", "iam_policy_no_full_access_to_cloudtrail", "iam_policy_no_full_access_to_kms" + ], + "ConfigRequirements": [ + { + "Check": "accessanalyzer_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -853,6 +968,14 @@ "iam_customer_unattached_policy_no_administrative_privileges", "accessanalyzer_enabled", "cognito_user_pool_password_policy_symbol" + ], + "ConfigRequirements": [ + { + "Check": "accessanalyzer_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -1224,6 +1347,14 @@ "inspector2_active_findings_exist", "secretsmanager_automatic_rotation_enabled", "secretsmanager_secret_rotated_periodically" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -1265,6 +1396,14 @@ "Checks": [ "ssmincidents_enabled_with_plans", "drs_job_exist" + ], + "ConfigRequirements": [ + { + "Check": "drs_job_exist", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -1283,6 +1422,14 @@ "inspector2_is_enabled", "guardduty_is_enabled", "inspector2_active_findings_exist" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -1329,6 +1476,14 @@ "vpc_flow_logs_enabled", "config_recorder_all_regions_enabled", "config_recorder_using_aws_service_role" + ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -1540,6 +1695,14 @@ "guardduty_is_enabled", "inspector2_is_enabled", "accessanalyzer_enabled_without_findings" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -1662,6 +1825,14 @@ "guardduty_rds_protection_enabled", "guardduty_lambda_protection_enabled", "guardduty_eks_runtime_monitoring_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { diff --git a/prowler/compliance/aws/pci_3.2.1_aws.json b/prowler/compliance/aws/pci_3.2.1_aws.json index c240548f6ae..ca8e968bf9f 100644 --- a/prowler/compliance/aws/pci_3.2.1_aws.json +++ b/prowler/compliance/aws/pci_3.2.1_aws.json @@ -628,6 +628,14 @@ "ssm_managed_compliant_patching", "ec2_elastic_ip_unassigned" ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "ItemId": "2.4", @@ -643,6 +651,14 @@ "Checks": [ "config_recorder_all_regions_enabled" ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "ItemId": "2.4.a", @@ -2413,6 +2429,14 @@ "cloudtrail_log_file_validation_enabled", "s3_bucket_cross_region_replication" ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "ItemId": "10.5", @@ -2430,6 +2454,14 @@ "s3_bucket_object_versioning", "cloudtrail_log_file_validation_enabled" ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "ItemId": "10.5.2", @@ -2616,6 +2648,14 @@ "Checks": [ "guardduty_is_enabled" ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "ItemId": "11.4", @@ -2631,6 +2671,14 @@ "Checks": [ "guardduty_is_enabled" ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "ItemId": "11.4.a", @@ -2646,6 +2694,14 @@ "Checks": [ "guardduty_is_enabled" ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "ItemId": "11.4.b", @@ -2661,6 +2717,14 @@ "Checks": [ "guardduty_is_enabled" ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "ItemId": "11.4.c", @@ -2676,6 +2740,14 @@ "Checks": [ "config_recorder_all_regions_enabled" ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "ItemId": "11.5", @@ -2691,6 +2763,14 @@ "Checks": [ "config_recorder_all_regions_enabled" ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "ItemId": "11.5.a", @@ -2706,6 +2786,14 @@ "Checks": [ "config_recorder_all_regions_enabled" ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "ItemId": "11.5.b", diff --git a/prowler/compliance/aws/pci_4.0_aws.json b/prowler/compliance/aws/pci_4.0_aws.json index dc8fab91406..e21b5435568 100644 --- a/prowler/compliance/aws/pci_4.0_aws.json +++ b/prowler/compliance/aws/pci_4.0_aws.json @@ -4403,6 +4403,14 @@ "Checks": [ "securityhub_enabled" ], + "ConfigRequirements": [ + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "Section": "10.2.1.1: Audit logs are implemented to support the detection of anomalies and suspicious activity, and the forensic analysis of events. ", @@ -9281,6 +9289,14 @@ "Checks": [ "securityhub_enabled" ], + "ConfigRequirements": [ + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "Section": "10.4.1.1: Audit logs are reviewed to identify anomalies or suspicious activity. ", @@ -9363,6 +9379,14 @@ "Checks": [ "securityhub_enabled" ], + "ConfigRequirements": [ + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "Section": "10.4.1: Audit logs are reviewed to identify anomalies or suspicious activity. ", @@ -9459,6 +9483,14 @@ "Checks": [ "securityhub_enabled" ], + "ConfigRequirements": [ + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "Section": "10.4.2: Audit logs are reviewed to identify anomalies or suspicious activity. ", @@ -9551,6 +9583,14 @@ "Checks": [ "cloudwatch_log_group_retention_policy_specific_days_enabled" ], + "ConfigRequirements": [ + { + "Check": "cloudwatch_log_group_retention_policy_specific_days_enabled", + "ConfigKey": "log_group_retention_days", + "Operator": "gte", + "Value": 365 + } + ], "Attributes": [ { "Section": "10.5.1: Audit log history is retained and available for analysis. ", @@ -10179,6 +10219,14 @@ "Checks": [ "securityhub_enabled" ], + "ConfigRequirements": [ + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "Section": "10.6.3: Time-synchronization mechanisms support consistent time settings across all systems. ", @@ -10343,6 +10391,14 @@ "Checks": [ "securityhub_enabled" ], + "ConfigRequirements": [ + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "Section": "10.7.1: Failures of critical security control systems are detected, reported, and responded to promptly. ", @@ -10451,6 +10507,14 @@ "Checks": [ "securityhub_enabled" ], + "ConfigRequirements": [ + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "Section": "10.7.2: Failures of critical security control systems are detected, reported, and responded to promptly. ", @@ -10625,6 +10689,14 @@ "Checks": [ "guardduty_is_enabled" ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "Section": "11.5.1.1: Network intrusions and unexpected file changes are detected and responded to. ", @@ -10653,6 +10725,14 @@ "Checks": [ "guardduty_is_enabled" ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "Section": "11.5.1: Network intrusions and unexpected file changes are detected and responded to. ", @@ -11445,6 +11525,14 @@ "Checks": [ "cloudwatch_log_group_retention_policy_specific_days_enabled" ], + "ConfigRequirements": [ + { + "Check": "cloudwatch_log_group_retention_policy_specific_days_enabled", + "ConfigKey": "log_group_retention_days", + "Operator": "gte", + "Value": 365 + } + ], "Attributes": [ { "Section": "3.2.1: Storage of account data is kept to a minimum. ", @@ -11567,6 +11655,14 @@ "Checks": [ "cloudwatch_log_group_retention_policy_specific_days_enabled" ], + "ConfigRequirements": [ + { + "Check": "cloudwatch_log_group_retention_policy_specific_days_enabled", + "ConfigKey": "log_group_retention_days", + "Operator": "gte", + "Value": 365 + } + ], "Attributes": [ { "Section": "3.3.1.1: Sensitive authentication data (SAD) is not stored after authorization. ", @@ -11689,6 +11785,14 @@ "Checks": [ "cloudwatch_log_group_retention_policy_specific_days_enabled" ], + "ConfigRequirements": [ + { + "Check": "cloudwatch_log_group_retention_policy_specific_days_enabled", + "ConfigKey": "log_group_retention_days", + "Operator": "gte", + "Value": 365 + } + ], "Attributes": [ { "Section": "3.3.1.3: Sensitive authentication data (SAD) is not stored after authorization. ", @@ -11811,6 +11915,14 @@ "Checks": [ "cloudwatch_log_group_retention_policy_specific_days_enabled" ], + "ConfigRequirements": [ + { + "Check": "cloudwatch_log_group_retention_policy_specific_days_enabled", + "ConfigKey": "log_group_retention_days", + "Operator": "gte", + "Value": 365 + } + ], "Attributes": [ { "Section": "3.3.2: Sensitive authentication data (SAD) is not stored after authorization. ", @@ -11933,6 +12045,14 @@ "Checks": [ "cloudwatch_log_group_retention_policy_specific_days_enabled" ], + "ConfigRequirements": [ + { + "Check": "cloudwatch_log_group_retention_policy_specific_days_enabled", + "ConfigKey": "log_group_retention_days", + "Operator": "gte", + "Value": 365 + } + ], "Attributes": [ { "Section": "3.3.3: Sensitive authentication data (SAD) is not stored after authorization. ", @@ -13573,6 +13693,17 @@ "Checks": [ "acm_certificates_with_secure_key_algorithms" ], + "ConfigRequirements": [ + { + "Check": "acm_certificates_with_secure_key_algorithms", + "ConfigKey": "insecure_key_algorithms", + "Operator": "superset", + "Value": [ + "RSA-1024", + "P-192" + ] + } + ], "Attributes": [ { "Section": "3.7.1: Where cryptography is used to protect stored account data, key management processes and procedures covering all aspects of the key lifecycle are defined and implemented. ", @@ -15001,6 +15132,14 @@ "Checks": [ "cloudwatch_log_group_retention_policy_specific_days_enabled" ], + "ConfigRequirements": [ + { + "Check": "cloudwatch_log_group_retention_policy_specific_days_enabled", + "ConfigKey": "log_group_retention_days", + "Operator": "gte", + "Value": 365 + } + ], "Attributes": [ { "Section": "5.3.4: Anti-malware mechanisms and processes are active, maintained, and monitored. ", @@ -22504,6 +22643,14 @@ "Checks": [ "securityhub_enabled" ], + "ConfigRequirements": [ + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "Section": "A3.3.1: PCI DSS is incorporated into business-as-usual (BAU) activities. ", @@ -23000,6 +23147,14 @@ "Checks": [ "securityhub_enabled" ], + "ConfigRequirements": [ + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "Section": "A3.5.1: Suspicious events are identified and responded to. ", diff --git a/prowler/compliance/aws/prowler_threatscore_aws.json b/prowler/compliance/aws/prowler_threatscore_aws.json index 902beb8abd9..c8c093907a4 100644 --- a/prowler/compliance/aws/prowler_threatscore_aws.json +++ b/prowler/compliance/aws/prowler_threatscore_aws.json @@ -174,6 +174,20 @@ "iam_user_accesskey_unused", "iam_user_console_access_unused" ], + "ConfigRequirements": [ + { + "Check": "iam_user_accesskey_unused", + "ConfigKey": "max_unused_access_keys_days", + "Operator": "lte", + "Value": 45 + }, + { + "Check": "iam_user_console_access_unused", + "ConfigKey": "max_console_access_days", + "Operator": "lte", + "Value": 45 + } + ], "Attributes": [ { "Title": "IAM credentials unused disabled", @@ -336,6 +350,14 @@ "Checks": [ "accessanalyzer_enabled" ], + "ConfigRequirements": [ + { + "Check": "accessanalyzer_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "Title": "Access Analyzer enabled", @@ -1541,6 +1563,14 @@ "Checks": [ "config_recorder_all_regions_enabled" ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "Title": "AWS Config is enabled", @@ -1829,6 +1859,14 @@ "Checks": [ "securityhub_enabled" ], + "ConfigRequirements": [ + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ], "Attributes": [ { "Title": "Security Hub enabled", diff --git a/prowler/compliance/aws/rbi_cyber_security_framework_aws.json b/prowler/compliance/aws/rbi_cyber_security_framework_aws.json index f4e8d1d70e7..5de1f5ca8a2 100644 --- a/prowler/compliance/aws/rbi_cyber_security_framework_aws.json +++ b/prowler/compliance/aws/rbi_cyber_security_framework_aws.json @@ -185,6 +185,14 @@ "securityhub_enabled", "vpc_flow_logs_enabled", "opensearch_service_domains_audit_logging_enabled" + ], + "ConfigRequirements": [ + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { diff --git a/prowler/compliance/aws/secnumcloud_3.2_aws.json b/prowler/compliance/aws/secnumcloud_3.2_aws.json index d8736c2d6b6..701f931b05c 100644 --- a/prowler/compliance/aws/secnumcloud_3.2_aws.json +++ b/prowler/compliance/aws/secnumcloud_3.2_aws.json @@ -202,6 +202,14 @@ "Checks": [ "config_recorder_all_regions_enabled", "ec2_instance_managed_by_ssm" + ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -323,6 +331,14 @@ "iam_role_administratoraccess_policy", "iam_user_administrator_access_policy", "iam_user_two_active_access_key" + ], + "ConfigRequirements": [ + { + "Check": "accessanalyzer_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -563,6 +579,17 @@ "Checks": [ "acm_certificates_expiration_check", "acm_certificates_with_secure_key_algorithms" + ], + "ConfigRequirements": [ + { + "Check": "acm_certificates_with_secure_key_algorithms", + "ConfigKey": "insecure_key_algorithms", + "Operator": "superset", + "Value": [ + "RSA-1024", + "P-192" + ] + } ] }, { @@ -735,6 +762,14 @@ "config_recorder_all_regions_enabled", "cloudtrail_multi_region_enabled", "cloudtrail_multi_region_enabled_logging_management_events" + ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -774,6 +809,14 @@ "guardduty_lambda_protection_enabled", "guardduty_eks_audit_log_enabled", "guardduty_eks_runtime_monitoring_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -911,6 +954,20 @@ "cloudwatch_changes_to_network_gateways_alarm_configured", "cloudwatch_changes_to_network_route_tables_alarm_configured", "cloudwatch_changes_to_vpcs_alarm_configured" + ], + "ConfigRequirements": [ + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -1014,6 +1071,14 @@ "config_recorder_all_regions_enabled", "config_recorder_using_aws_service_role", "vpc_flow_logs_enabled" + ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -1060,6 +1125,14 @@ "Checks": [ "guardduty_is_enabled", "vpc_flow_logs_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -1091,6 +1164,14 @@ "Checks": [ "config_recorder_all_regions_enabled", "cloudtrail_multi_region_enabled" + ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -1268,6 +1349,20 @@ "guardduty_is_enabled", "securityhub_enabled", "cloudwatch_alarm_actions_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -1418,6 +1513,14 @@ "Checks": [ "backup_plans_exist", "config_recorder_all_regions_enabled" + ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -1481,6 +1584,20 @@ "Checks": [ "securityhub_enabled", "config_recorder_all_regions_enabled" + ], + "ConfigRequirements": [ + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -1498,6 +1615,14 @@ "Checks": [ "inspector2_is_enabled", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { diff --git a/prowler/compliance/aws/soc2_aws.json b/prowler/compliance/aws/soc2_aws.json index 5a027d04163..c0041a9daeb 100644 --- a/prowler/compliance/aws/soc2_aws.json +++ b/prowler/compliance/aws/soc2_aws.json @@ -43,6 +43,14 @@ "cloudtrail_s3_dataevents_write_enabled", "cloudtrail_multi_region_enabled", "config_recorder_all_regions_enabled" + ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -61,6 +69,26 @@ "guardduty_is_enabled", "securityhub_enabled", "config_recorder_all_regions_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -80,6 +108,14 @@ "ssm_managed_compliant_patching", "guardduty_no_high_severity_findings", "guardduty_is_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -116,6 +152,14 @@ ], "Checks": [ "config_recorder_all_regions_enabled" + ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -133,6 +177,14 @@ "Checks": [ "guardduty_is_enabled", "guardduty_no_high_severity_findings" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -312,6 +364,20 @@ "Checks": [ "guardduty_is_enabled", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -331,6 +397,20 @@ "securityhub_enabled", "ec2_instance_managed_by_ssm", "ssm_managed_compliant_patching" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -367,6 +447,20 @@ "guardduty_is_enabled", "apigateway_restapi_logging_enabled", "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_22" + ], + "ConfigRequirements": [ + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -399,6 +493,20 @@ "cloudwatch_log_group_retention_policy_specific_days_enabled", "vpc_flow_logs_enabled", "guardduty_no_high_severity_findings" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -426,6 +534,20 @@ "redshift_cluster_automated_snapshot", "s3_bucket_object_versioning", "securityhub_enabled" + ], + "ConfigRequirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -463,6 +585,14 @@ ], "Checks": [ "config_recorder_all_regions_enabled" + ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { @@ -600,6 +730,14 @@ "rds_cluster_integration_cloudwatch_logs", "glue_etl_jobs_logging_enabled", "stepfunctions_statemachine_logging_enabled" + ], + "ConfigRequirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } ] }, { diff --git a/prowler/compliance/azure/c5_azure.json b/prowler/compliance/azure/c5_azure.json index 4ac3b4dd53f..6fff7a36915 100644 --- a/prowler/compliance/azure/c5_azure.json +++ b/prowler/compliance/azure/c5_azure.json @@ -2681,6 +2681,17 @@ "app_function_latest_runtime_version", "mysql_flexible_server_minimum_tls_version_12", "sqlserver_recommended_minimal_tls_version" + ], + "ConfigRequirements": [ + { + "Check": "sqlserver_recommended_minimal_tls_version", + "ConfigKey": "recommended_minimal_tls_versions", + "Operator": "subset", + "Value": [ + "1.2", + "1.3" + ] + } ] }, { @@ -2705,6 +2716,17 @@ "app_function_latest_runtime_version", "mysql_flexible_server_minimum_tls_version_12", "sqlserver_recommended_minimal_tls_version" + ], + "ConfigRequirements": [ + { + "Check": "sqlserver_recommended_minimal_tls_version", + "ConfigKey": "recommended_minimal_tls_versions", + "Operator": "subset", + "Value": [ + "1.2", + "1.3" + ] + } ] }, { @@ -3903,6 +3925,17 @@ "app_ensure_php_version_is_latest", "storage_ensure_minimum_tls_version_12", "storage_smb_protocol_version_is_latest" + ], + "ConfigRequirements": [ + { + "Check": "sqlserver_recommended_minimal_tls_version", + "ConfigKey": "recommended_minimal_tls_versions", + "Operator": "subset", + "Value": [ + "1.2", + "1.3" + ] + } ] }, { @@ -4352,6 +4385,17 @@ "sqlserver_recommended_minimal_tls_version", "sqlserver_tde_encrypted_with_cmk", "sqlserver_tde_encryption_enabled" + ], + "ConfigRequirements": [ + { + "Check": "sqlserver_recommended_minimal_tls_version", + "ConfigKey": "recommended_minimal_tls_versions", + "Operator": "subset", + "Value": [ + "1.2", + "1.3" + ] + } ] }, { @@ -5743,6 +5787,17 @@ "storage_ensure_minimum_tls_version_12", "sqlserver_tde_encrypted_with_cmk", "sqlserver_tde_encryption_enabled" + ], + "ConfigRequirements": [ + { + "Check": "sqlserver_recommended_minimal_tls_version", + "ConfigKey": "recommended_minimal_tls_versions", + "Operator": "subset", + "Value": [ + "1.2", + "1.3" + ] + } ] }, { @@ -5770,6 +5825,17 @@ "storage_ensure_minimum_tls_version_12", "sqlserver_tde_encrypted_with_cmk", "sqlserver_tde_encryption_enabled" + ], + "ConfigRequirements": [ + { + "Check": "sqlserver_recommended_minimal_tls_version", + "ConfigKey": "recommended_minimal_tls_versions", + "Operator": "subset", + "Value": [ + "1.2", + "1.3" + ] + } ] }, { @@ -6513,6 +6579,17 @@ "mysql_flexible_server_minimum_tls_version_12", "sqlserver_recommended_minimal_tls_version", "storage_ensure_minimum_tls_version_12" + ], + "ConfigRequirements": [ + { + "Check": "sqlserver_recommended_minimal_tls_version", + "ConfigKey": "recommended_minimal_tls_versions", + "Operator": "subset", + "Value": [ + "1.2", + "1.3" + ] + } ] }, { diff --git a/prowler/compliance/azure/ccc_azure.json b/prowler/compliance/azure/ccc_azure.json index cb87346d13c..661d38302fe 100644 --- a/prowler/compliance/azure/ccc_azure.json +++ b/prowler/compliance/azure/ccc_azure.json @@ -56,6 +56,25 @@ "app_ensure_using_http20", "app_ftp_deployment_disabled", "app_function_ftps_deployment_disabled" + ], + "ConfigRequirements": [ + { + "Check": "sqlserver_recommended_minimal_tls_version", + "ConfigKey": "recommended_minimal_tls_versions", + "Operator": "subset", + "Value": [ + "1.2", + "1.3" + ] + }, + { + "Check": "storage_smb_channel_encryption_with_secure_algorithm", + "ConfigKey": "recommended_smb_channel_encryption_algorithms", + "Operator": "subset", + "Value": [ + "AES-256-GCM" + ] + } ] }, { @@ -726,6 +745,16 @@ ], "Checks": [ "storage_smb_channel_encryption_with_secure_algorithm" + ], + "ConfigRequirements": [ + { + "Check": "storage_smb_channel_encryption_with_secure_algorithm", + "ConfigKey": "recommended_smb_channel_encryption_algorithms", + "Operator": "subset", + "Value": [ + "AES-256-GCM" + ] + } ] }, { diff --git a/prowler/compliance/azure/cis_4.0_azure.json b/prowler/compliance/azure/cis_4.0_azure.json index c075ff40472..5f3e4502f1a 100644 --- a/prowler/compliance/azure/cis_4.0_azure.json +++ b/prowler/compliance/azure/cis_4.0_azure.json @@ -253,6 +253,18 @@ "References": "https://learn.microsoft.com/en-us/azure/defender-for-cloud/configure-email-notifications:https://learn.microsoft.com/en-us/azure/defender-for-cloud/how-to-manage-attack-path:https://learn.microsoft.com/en-us/azure/defender-for-cloud/concept-attack-path", "DefaultValue": "" } + ], + "ConfigRequirements": [ + { + "Check": "defender_attack_path_notifications_properly_configured", + "ConfigKey": "defender_attack_path_minimal_risk_level", + "Operator": "in", + "Value": [ + "Low", + "Medium", + "High" + ] + } ] }, { @@ -375,6 +387,16 @@ "Checks": [ "storage_smb_channel_encryption_with_secure_algorithm" ], + "ConfigRequirements": [ + { + "Check": "storage_smb_channel_encryption_with_secure_algorithm", + "ConfigKey": "recommended_smb_channel_encryption_algorithms", + "Operator": "subset", + "Value": [ + "AES-256-GCM" + ] + } + ], "Attributes": [ { "Section": "10 Storage Services", diff --git a/prowler/compliance/azure/cis_5.0_azure.json b/prowler/compliance/azure/cis_5.0_azure.json index 8ea6f50d7c2..71e43aeee9a 100644 --- a/prowler/compliance/azure/cis_5.0_azure.json +++ b/prowler/compliance/azure/cis_5.0_azure.json @@ -2614,6 +2614,18 @@ "References": "https://learn.microsoft.com/en-us/azure/defender-for-cloud/configure-email-notifications:https://learn.microsoft.com/en-us/azure/defender-for-cloud/how-to-manage-attack-path:https://learn.microsoft.com/en-us/azure/defender-for-cloud/concept-attack-path", "DefaultValue": "" } + ], + "ConfigRequirements": [ + { + "Check": "defender_attack_path_notifications_properly_configured", + "ConfigKey": "defender_attack_path_minimal_risk_level", + "Operator": "in", + "Value": [ + "Low", + "Medium", + "High" + ] + } ] }, { @@ -3006,6 +3018,16 @@ "Checks": [ "storage_smb_channel_encryption_with_secure_algorithm" ], + "ConfigRequirements": [ + { + "Check": "storage_smb_channel_encryption_with_secure_algorithm", + "ConfigKey": "recommended_smb_channel_encryption_algorithms", + "Operator": "subset", + "Value": [ + "AES-256-GCM" + ] + } + ], "Attributes": [ { "Section": "9 Storage Services", diff --git a/prowler/compliance/azure/hipaa_azure.json b/prowler/compliance/azure/hipaa_azure.json index 3672218f3b0..62ffe2fdad8 100644 --- a/prowler/compliance/azure/hipaa_azure.json +++ b/prowler/compliance/azure/hipaa_azure.json @@ -767,6 +767,17 @@ "mysql_flexible_server_minimum_tls_version_12", "mysql_flexible_server_ssl_connection_enabled", "postgresql_flexible_server_enforce_ssl_enabled" + ], + "ConfigRequirements": [ + { + "Check": "sqlserver_recommended_minimal_tls_version", + "ConfigKey": "recommended_minimal_tls_versions", + "Operator": "subset", + "Value": [ + "1.2", + "1.3" + ] + } ] }, { @@ -819,6 +830,17 @@ "mysql_flexible_server_ssl_connection_enabled", "postgresql_flexible_server_enforce_ssl_enabled", "databricks_workspace_cmk_encryption_enabled" + ], + "ConfigRequirements": [ + { + "Check": "sqlserver_recommended_minimal_tls_version", + "ConfigKey": "recommended_minimal_tls_versions", + "Operator": "subset", + "Value": [ + "1.2", + "1.3" + ] + } ] } ] diff --git a/prowler/compliance/azure/nis2_azure.json b/prowler/compliance/azure/nis2_azure.json index 5c48c22f6b7..0ae814fad7c 100644 --- a/prowler/compliance/azure/nis2_azure.json +++ b/prowler/compliance/azure/nis2_azure.json @@ -1133,6 +1133,17 @@ "defender_ensure_defender_for_dns_is_on", "sqlserver_tde_encryption_enabled" ], + "ConfigRequirements": [ + { + "Check": "sqlserver_recommended_minimal_tls_version", + "ConfigKey": "recommended_minimal_tls_versions", + "Operator": "subset", + "Value": [ + "1.2", + "1.3" + ] + } + ], "Attributes": [ { "Section": "6 SECURITY IN NETWORK AND INFORMATION SYSTEMS ACQUISITION, DEVELOPMENT AND MAINTENANCE (ARTICLE 21(2), POINT (E), OF DIRECTIVE (EU) 2022/2555)", @@ -1164,6 +1175,17 @@ "network_udp_internet_access_restricted", "network_watcher_enabled" ], + "ConfigRequirements": [ + { + "Check": "sqlserver_recommended_minimal_tls_version", + "ConfigKey": "recommended_minimal_tls_versions", + "Operator": "subset", + "Value": [ + "1.2", + "1.3" + ] + } + ], "Attributes": [ { "Section": "6 SECURITY IN NETWORK AND INFORMATION SYSTEMS ACQUISITION, DEVELOPMENT AND MAINTENANCE (ARTICLE 21(2), POINT (E), OF DIRECTIVE (EU) 2022/2555)", @@ -1887,6 +1909,17 @@ "sqlserver_tde_encrypted_with_cmk", "sqlserver_tde_encryption_enabled" ], + "ConfigRequirements": [ + { + "Check": "sqlserver_recommended_minimal_tls_version", + "ConfigKey": "recommended_minimal_tls_versions", + "Operator": "subset", + "Value": [ + "1.2", + "1.3" + ] + } + ], "Attributes": [ { "Section": "12 ASSET MANAGEMENT (ARTICLE 21(2), POINT (I), OF DIRECTIVE (EU) 2022/2555)", diff --git a/prowler/compliance/azure/secnumcloud_3.2_azure.json b/prowler/compliance/azure/secnumcloud_3.2_azure.json index aedf2133d43..3f2e8c76032 100644 --- a/prowler/compliance/azure/secnumcloud_3.2_azure.json +++ b/prowler/compliance/azure/secnumcloud_3.2_azure.json @@ -440,6 +440,25 @@ "postgresql_flexible_server_enforce_ssl_enabled", "mysql_flexible_server_ssl_connection_enabled", "mysql_flexible_server_minimum_tls_version_12" + ], + "ConfigRequirements": [ + { + "Check": "sqlserver_recommended_minimal_tls_version", + "ConfigKey": "recommended_minimal_tls_versions", + "Operator": "subset", + "Value": [ + "1.2", + "1.3" + ] + }, + { + "Check": "storage_smb_channel_encryption_with_secure_algorithm", + "ConfigKey": "recommended_smb_channel_encryption_algorithms", + "Operator": "subset", + "Value": [ + "AES-256-GCM" + ] + } ] }, { diff --git a/prowler/compliance/azure/soc2_azure.json b/prowler/compliance/azure/soc2_azure.json index e0839cedaaf..c3b4db6e39f 100644 --- a/prowler/compliance/azure/soc2_azure.json +++ b/prowler/compliance/azure/soc2_azure.json @@ -266,6 +266,17 @@ "sqlserver_tde_encryption_enabled", "sqlserver_unrestricted_inbound_access", "storage_secure_transfer_required_is_enabled" + ], + "ConfigRequirements": [ + { + "Check": "sqlserver_recommended_minimal_tls_version", + "ConfigKey": "recommended_minimal_tls_versions", + "Operator": "subset", + "Value": [ + "1.2", + "1.3" + ] + } ] }, { @@ -310,6 +321,17 @@ "sqlserver_recommended_minimal_tls_version", "storage_ensure_minimum_tls_version_12", "network_subnet_nsg_associated" + ], + "ConfigRequirements": [ + { + "Check": "sqlserver_recommended_minimal_tls_version", + "ConfigKey": "recommended_minimal_tls_versions", + "Operator": "subset", + "Value": [ + "1.2", + "1.3" + ] + } ] }, { diff --git a/prowler/compliance/csa_ccm_4.0.json b/prowler/compliance/csa_ccm_4.0.json index b6bb382cda5..1cb7428e517 100644 --- a/prowler/compliance/csa_ccm_4.0.json +++ b/prowler/compliance/csa_ccm_4.0.json @@ -229,7 +229,16 @@ "oraclecloud": [ "cloudguard_enabled" ] - } + }, + "config_requirements": [ + { + "Check": "securityhub_enabled", + "Provider": "aws", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ] }, { "id": "A&A-04", @@ -334,7 +343,23 @@ "oraclecloud": [ "cloudguard_enabled" ] - } + }, + "config_requirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "Provider": "aws", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "Provider": "aws", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ] }, { "id": "AIS-04", @@ -978,7 +1003,16 @@ "defender_ensure_defender_for_server_is_on", "vm_backup_enabled" ] - } + }, + "config_requirements": [ + { + "Check": "drs_job_exist", + "Provider": "aws", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ] }, { "id": "BCR-11", @@ -1416,7 +1450,30 @@ "events_rule_security_list_changes", "events_rule_vcn_changes" ] - } + }, + "config_requirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "Provider": "aws", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "guardduty_is_enabled", + "Provider": "aws", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "Provider": "aws", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ] }, { "id": "CEK-03", @@ -1659,7 +1716,18 @@ "filestorage_file_system_encrypted_with_cmk", "objectstorage_bucket_encrypted_with_cmk" ] - } + }, + "config_requirements": [ + { + "Check": "storage_smb_channel_encryption_with_secure_algorithm", + "Provider": "azure", + "ConfigKey": "recommended_smb_channel_encryption_algorithms", + "Operator": "subset", + "Value": [ + "AES-256-GCM" + ] + } + ] }, { "id": "CEK-04", @@ -1802,7 +1870,29 @@ "dns_rsasha1_in_use_to_key_sign_in_dnssec", "dns_rsasha1_in_use_to_zone_sign_in_dnssec" ] - } + }, + "config_requirements": [ + { + "Check": "acm_certificates_with_secure_key_algorithms", + "Provider": "aws", + "ConfigKey": "insecure_key_algorithms", + "Operator": "superset", + "Value": [ + "RSA-1024", + "P-192" + ] + }, + { + "Check": "sqlserver_recommended_minimal_tls_version", + "Provider": "azure", + "ConfigKey": "recommended_minimal_tls_versions", + "Operator": "subset", + "Value": [ + "1.2", + "1.3" + ] + } + ] }, { "id": "CEK-08", @@ -2345,7 +2435,16 @@ "alibabacloud": [ "securitycenter_all_assets_agent_installed" ] - } + }, + "config_requirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "Provider": "aws", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ] }, { "id": "DSP-02", @@ -2583,7 +2682,16 @@ "alibabacloud": [ "securitycenter_all_assets_agent_installed" ] - } + }, + "config_requirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "Provider": "aws", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ] }, { "id": "DSP-04", @@ -2997,7 +3105,19 @@ "oraclecloud": [ "compute_instance_in_transit_encryption_enabled" ] - } + }, + "config_requirements": [ + { + "Check": "sqlserver_recommended_minimal_tls_version", + "Provider": "azure", + "ConfigKey": "recommended_minimal_tls_versions", + "Operator": "subset", + "Value": [ + "1.2", + "1.3" + ] + } + ] }, { "id": "DSP-16", @@ -3403,7 +3523,23 @@ "oraclecloud": [ "cloudguard_enabled" ] - } + }, + "config_requirements": [ + { + "Check": "guardduty_is_enabled", + "Provider": "aws", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "Provider": "aws", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ] }, { "id": "IAM-02", @@ -6255,7 +6391,16 @@ "cloudguard_enabled", "events_rule_cloudguard_problems" ] - } + }, + "config_requirements": [ + { + "Check": "guardduty_is_enabled", + "Provider": "aws", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ] }, { "id": "LOG-02", @@ -6558,7 +6703,23 @@ "events_notification_topic_and_subscription_exists", "events_rule_local_user_authentication" ] - } + }, + "config_requirements": [ + { + "Check": "guardduty_is_enabled", + "Provider": "aws", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "Provider": "aws", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ] }, { "id": "LOG-04", @@ -7602,7 +7763,16 @@ "events_rule_cloudguard_problems", "events_notification_topic_and_subscription_exists" ] - } + }, + "config_requirements": [ + { + "Check": "guardduty_is_enabled", + "Provider": "aws", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ] }, { "id": "SEF-03", @@ -7880,7 +8050,23 @@ "oraclecloud": [ "cloudguard_enabled" ] - } + }, + "config_requirements": [ + { + "Check": "guardduty_is_enabled", + "Provider": "aws", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "Provider": "aws", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ] }, { "id": "SEF-08", @@ -8461,7 +8647,16 @@ "oraclecloud": [ "cloudguard_enabled" ] - } + }, + "config_requirements": [ + { + "Check": "guardduty_is_enabled", + "Provider": "aws", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ] }, { "id": "TVM-05", @@ -8729,7 +8924,16 @@ "oraclecloud": [ "cloudguard_enabled" ] - } + }, + "config_requirements": [ + { + "Check": "guardduty_is_enabled", + "Provider": "aws", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ] }, { "id": "UEM-08", diff --git a/prowler/compliance/dora_2022_2554.json b/prowler/compliance/dora_2022_2554.json index e4b5fd69df8..3b828059da0 100644 --- a/prowler/compliance/dora_2022_2554.json +++ b/prowler/compliance/dora_2022_2554.json @@ -215,7 +215,37 @@ "securitycenter_vulnerability_scan_enabled", "actiontrail_multi_region_enabled" ] - } + }, + "config_requirements": [ + { + "Check": "accessanalyzer_enabled", + "Provider": "aws", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "config_recorder_all_regions_enabled", + "Provider": "aws", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "guardduty_delegated_admin_enabled_all_regions", + "Provider": "aws", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "Provider": "aws", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ] }, { "id": "DORA-Art7", @@ -299,7 +329,38 @@ "ecs_unattached_disk_encrypted", "ecs_instance_no_legacy_network" ] - } + }, + "config_requirements": [ + { + "Check": "acm_certificates_with_secure_key_algorithms", + "Provider": "aws", + "ConfigKey": "insecure_key_algorithms", + "Operator": "superset", + "Value": [ + "RSA-1024", + "P-192" + ] + }, + { + "Check": "sqlserver_recommended_minimal_tls_version", + "Provider": "azure", + "ConfigKey": "recommended_minimal_tls_versions", + "Operator": "subset", + "Value": [ + "1.2", + "1.3" + ] + }, + { + "Check": "storage_smb_channel_encryption_with_secure_algorithm", + "Provider": "azure", + "ConfigKey": "recommended_smb_channel_encryption_algorithms", + "Operator": "subset", + "Value": [ + "AES-256-GCM" + ] + } + ] }, { "id": "DORA-Art8", @@ -344,7 +405,16 @@ "securitycenter_all_assets_agent_installed", "ram_user_console_access_unused" ] - } + }, + "config_requirements": [ + { + "Check": "accessanalyzer_enabled", + "Provider": "aws", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ] }, { "id": "DORA-Art9", @@ -580,7 +650,23 @@ "ecs_instance_endpoint_protection_installed", "cs_kubernetes_cloudmonitor_enabled" ] - } + }, + "config_requirements": [ + { + "Check": "guardduty_is_enabled", + "Provider": "aws", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "Provider": "aws", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ] }, { "id": "DORA-Art11", @@ -732,7 +818,16 @@ "securitycenter_all_assets_agent_installed", "ecs_instance_latest_os_patches_applied" ] - } + }, + "config_requirements": [ + { + "Check": "securityhub_enabled", + "Provider": "aws", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ] }, { "id": "DORA-Art14", @@ -901,7 +996,23 @@ "securitycenter_notification_enabled_high_risk", "securitycenter_vulnerability_scan_enabled" ] - } + }, + "config_requirements": [ + { + "Check": "guardduty_delegated_admin_enabled_all_regions", + "Provider": "aws", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "Provider": "aws", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ] }, { "id": "DORA-Art19", @@ -1017,7 +1128,16 @@ "cs_kubernetes_cluster_check_recent", "cs_kubernetes_cluster_check_weekly" ] - } + }, + "config_requirements": [ + { + "Check": "securityhub_enabled", + "Provider": "aws", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ] }, { "id": "DORA-Art25", @@ -1079,7 +1199,23 @@ "ecs_instance_latest_os_patches_applied", "ecs_instance_no_legacy_network" ] - } + }, + "config_requirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "Provider": "aws", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "guardduty_is_enabled", + "Provider": "aws", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ] }, { "id": "DORA-Art28", @@ -1144,7 +1280,16 @@ "oss_bucket_not_publicly_accessible", "actiontrail_oss_bucket_not_publicly_accessible" ] - } + }, + "config_requirements": [ + { + "Check": "accessanalyzer_enabled", + "Provider": "aws", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ] }, { "id": "DORA-Art30", @@ -1200,7 +1345,16 @@ "ram_policy_attached_only_to_group_or_roles", "ram_no_root_access_key" ] - } + }, + "config_requirements": [ + { + "Check": "accessanalyzer_enabled", + "Provider": "aws", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ] }, { "id": "DORA-Art45", @@ -1245,7 +1399,23 @@ "actiontrail_multi_region_enabled", "sls_logstore_retention_period" ] - } + }, + "config_requirements": [ + { + "Check": "guardduty_is_enabled", + "Provider": "aws", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "Provider": "aws", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ] } ] } diff --git a/prowler/compliance/gcp/ccc_gcp.json b/prowler/compliance/gcp/ccc_gcp.json index 67a75841ef6..520b6b534a4 100644 --- a/prowler/compliance/gcp/ccc_gcp.json +++ b/prowler/compliance/gcp/ccc_gcp.json @@ -924,6 +924,14 @@ "cloudsql_instance_automated_backups", "cloudstorage_bucket_log_retention_policy_lock", "cloudstorage_bucket_sufficient_retention_period" + ], + "ConfigRequirements": [ + { + "Check": "cloudstorage_bucket_sufficient_retention_period", + "ConfigKey": "storage_min_retention_days", + "Operator": "gte", + "Value": 30 + } ] }, { @@ -5841,6 +5849,20 @@ "Checks": [ "iam_sa_user_managed_key_unused", "iam_service_account_unused" + ], + "ConfigRequirements": [ + { + "Check": "iam_sa_user_managed_key_unused", + "ConfigKey": "max_unused_account_days", + "Operator": "lte", + "Value": 90 + }, + { + "Check": "iam_service_account_unused", + "ConfigKey": "max_unused_account_days", + "Operator": "lte", + "Value": 90 + } ] }, { diff --git a/prowler/compliance/kubernetes/cis_1.10_kubernetes.json b/prowler/compliance/kubernetes/cis_1.10_kubernetes.json index a049efc040f..ed0e90d88f7 100644 --- a/prowler/compliance/kubernetes/cis_1.10_kubernetes.json +++ b/prowler/compliance/kubernetes/cis_1.10_kubernetes.json @@ -820,6 +820,14 @@ "Checks": [ "apiserver_audit_log_maxage_set" ], + "ConfigRequirements": [ + { + "Check": "apiserver_audit_log_maxage_set", + "ConfigKey": "audit_log_maxage", + "Operator": "gte", + "Value": 30 + } + ], "Attributes": [ { "Section": "1 Control Plane Components", @@ -858,6 +866,14 @@ "DefaultValue": "By default, auditing is not enabled.", "References": "https://kubernetes.io/docs/admin/kube-apiserver/:https://kubernetes.io/docs/concepts/cluster-administration/audit/:https://github.com/kubernetes/features/issues/22" } + ], + "ConfigRequirements": [ + { + "Check": "apiserver_audit_log_maxbackup_set", + "ConfigKey": "audit_log_maxbackup", + "Operator": "gte", + "Value": 10 + } ] }, { @@ -881,6 +897,14 @@ "DefaultValue": "By default, auditing is not enabled.", "References": "https://kubernetes.io/docs/admin/kube-apiserver/:https://kubernetes.io/docs/concepts/cluster-administration/audit/:https://github.com/kubernetes/features/issues/22" } + ], + "ConfigRequirements": [ + { + "Check": "apiserver_audit_log_maxsize_set", + "ConfigKey": "audit_log_maxsize", + "Operator": "gte", + "Value": 100 + } ] }, { @@ -1109,6 +1133,18 @@ "DefaultValue": "By default the Kubernetes API server supports a wide range of TLS ciphers", "References": "https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/:https://github.com/ssllabs/research/wiki/SSL-and-TLS-Deployment-Best-Practices#23-use-secure-cipher-suites" } + ], + "ConfigRequirements": [ + { + "Check": "apiserver_strong_ciphers_only", + "ConfigKey": "apiserver_strong_ciphers", + "Operator": "subset", + "Value": [ + "TLS_AES_128_GCM_SHA256", + "TLS_AES_256_GCM_SHA384", + "TLS_CHACHA20_POLY1305_SHA256" + ] + } ] }, { @@ -2069,6 +2105,23 @@ "DefaultValue": "By default the Kubernetes API server supports a wide range of TLS ciphers", "References": "" } + ], + "ConfigRequirements": [ + { + "Check": "kubelet_strong_ciphers_only", + "ConfigKey": "kubelet_strong_ciphers", + "Operator": "subset", + "Value": [ + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305", + "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305", + "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", + "TLS_RSA_WITH_AES_256_GCM_SHA384", + "TLS_RSA_WITH_AES_128_GCM_SHA256" + ] + } ] }, { diff --git a/prowler/compliance/kubernetes/cis_1.11_kubernetes.json b/prowler/compliance/kubernetes/cis_1.11_kubernetes.json index 6a19ea41618..25d725683fb 100644 --- a/prowler/compliance/kubernetes/cis_1.11_kubernetes.json +++ b/prowler/compliance/kubernetes/cis_1.11_kubernetes.json @@ -820,6 +820,14 @@ "Checks": [ "apiserver_audit_log_maxage_set" ], + "ConfigRequirements": [ + { + "Check": "apiserver_audit_log_maxage_set", + "ConfigKey": "audit_log_maxage", + "Operator": "gte", + "Value": 30 + } + ], "Attributes": [ { "Section": "1 Control Plane Components", @@ -858,6 +866,14 @@ "References": "https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/:https://kubernetes.io/docs/tasks/debug/debug-cluster/audit/:https://github.com/kubernetes/enhancements/issues/22", "DefaultValue": "By default, auditing is not enabled." } + ], + "ConfigRequirements": [ + { + "Check": "apiserver_audit_log_maxbackup_set", + "ConfigKey": "audit_log_maxbackup", + "Operator": "gte", + "Value": 10 + } ] }, { @@ -881,6 +897,14 @@ "References": "https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/:https://kubernetes.io/docs/tasks/debug/debug-cluster/audit/:https://github.com/kubernetes/enhancements/issues/22", "DefaultValue": "By default, auditing is not enabled." } + ], + "ConfigRequirements": [ + { + "Check": "apiserver_audit_log_maxsize_set", + "ConfigKey": "audit_log_maxsize", + "Operator": "gte", + "Value": 100 + } ] }, { @@ -1109,6 +1133,18 @@ "References": "https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/:https://github.com/ssllabs/research/wiki/SSL-and-TLS-Deployment-Best-Practices#23-use-secure-cipher-suites", "DefaultValue": "By default the Kubernetes API server supports a wide range of TLS ciphers" } + ], + "ConfigRequirements": [ + { + "Check": "apiserver_strong_ciphers_only", + "ConfigKey": "apiserver_strong_ciphers", + "Operator": "subset", + "Value": [ + "TLS_AES_128_GCM_SHA256", + "TLS_AES_256_GCM_SHA384", + "TLS_CHACHA20_POLY1305_SHA256" + ] + } ] }, { @@ -2112,6 +2148,23 @@ "References": "", "DefaultValue": "By default the Kubernetes API server supports a wide range of TLS ciphers" } + ], + "ConfigRequirements": [ + { + "Check": "kubelet_strong_ciphers_only", + "ConfigKey": "kubelet_strong_ciphers", + "Operator": "subset", + "Value": [ + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305", + "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305", + "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", + "TLS_RSA_WITH_AES_256_GCM_SHA384", + "TLS_RSA_WITH_AES_128_GCM_SHA256" + ] + } ] }, { diff --git a/prowler/compliance/kubernetes/cis_1.12_kubernetes.json b/prowler/compliance/kubernetes/cis_1.12_kubernetes.json index 1ba1e55a886..ded2d6944a8 100644 --- a/prowler/compliance/kubernetes/cis_1.12_kubernetes.json +++ b/prowler/compliance/kubernetes/cis_1.12_kubernetes.json @@ -820,6 +820,14 @@ "Checks": [ "apiserver_audit_log_maxage_set" ], + "ConfigRequirements": [ + { + "Check": "apiserver_audit_log_maxage_set", + "ConfigKey": "audit_log_maxage", + "Operator": "gte", + "Value": 30 + } + ], "Attributes": [ { "Section": "1 Control Plane Components", @@ -858,6 +866,14 @@ "References": "https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/:https://kubernetes.io/docs/tasks/debug/debug-cluster/audit/:https://github.com/kubernetes/enhancements/issues/22", "DefaultValue": "By default, auditing is not enabled." } + ], + "ConfigRequirements": [ + { + "Check": "apiserver_audit_log_maxbackup_set", + "ConfigKey": "audit_log_maxbackup", + "Operator": "gte", + "Value": 10 + } ] }, { @@ -881,6 +897,14 @@ "References": "https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/:https://kubernetes.io/docs/tasks/debug/debug-cluster/audit/:https://github.com/kubernetes/enhancements/issues/22", "DefaultValue": "By default, auditing is not enabled." } + ], + "ConfigRequirements": [ + { + "Check": "apiserver_audit_log_maxsize_set", + "ConfigKey": "audit_log_maxsize", + "Operator": "gte", + "Value": 100 + } ] }, { @@ -1109,6 +1133,18 @@ "References": "https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/:https://github.com/ssllabs/research/wiki/SSL-and-TLS-Deployment-Best-Practices#23-use-secure-cipher-suites", "DefaultValue": "By default the Kubernetes API server supports a wide range of TLS ciphers" } + ], + "ConfigRequirements": [ + { + "Check": "apiserver_strong_ciphers_only", + "ConfigKey": "apiserver_strong_ciphers", + "Operator": "subset", + "Value": [ + "TLS_AES_128_GCM_SHA256", + "TLS_AES_256_GCM_SHA384", + "TLS_CHACHA20_POLY1305_SHA256" + ] + } ] }, { @@ -2090,6 +2126,23 @@ "References": "", "DefaultValue": "By default the Kubernetes API server supports a wide range of TLS ciphers" } + ], + "ConfigRequirements": [ + { + "Check": "kubelet_strong_ciphers_only", + "ConfigKey": "kubelet_strong_ciphers", + "Operator": "subset", + "Value": [ + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305", + "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305", + "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", + "TLS_RSA_WITH_AES_256_GCM_SHA384", + "TLS_RSA_WITH_AES_128_GCM_SHA256" + ] + } ] }, { diff --git a/prowler/compliance/kubernetes/cis_1.8_kubernetes.json b/prowler/compliance/kubernetes/cis_1.8_kubernetes.json index 24a5733a050..a09d753f582 100644 --- a/prowler/compliance/kubernetes/cis_1.8_kubernetes.json +++ b/prowler/compliance/kubernetes/cis_1.8_kubernetes.json @@ -843,6 +843,14 @@ "Checks": [ "apiserver_audit_log_maxage_set" ], + "ConfigRequirements": [ + { + "Check": "apiserver_audit_log_maxage_set", + "ConfigKey": "audit_log_maxage", + "Operator": "gte", + "Value": 30 + } + ], "Attributes": [ { "Section": "1 Control Plane Components", @@ -881,6 +889,14 @@ "References": "https://kubernetes.io/docs/admin/kube-apiserver/:https://kubernetes.io/docs/concepts/cluster-administration/audit/:https://github.com/kubernetes/features/issues/22", "DefaultValue": "By default, auditing is not enabled." } + ], + "ConfigRequirements": [ + { + "Check": "apiserver_audit_log_maxbackup_set", + "ConfigKey": "audit_log_maxbackup", + "Operator": "gte", + "Value": 10 + } ] }, { @@ -904,6 +920,14 @@ "References": "https://kubernetes.io/docs/admin/kube-apiserver/:https://kubernetes.io/docs/concepts/cluster-administration/audit/:https://github.com/kubernetes/features/issues/22", "DefaultValue": "By default, auditing is not enabled." } + ], + "ConfigRequirements": [ + { + "Check": "apiserver_audit_log_maxsize_set", + "ConfigKey": "audit_log_maxsize", + "Operator": "gte", + "Value": 100 + } ] }, { @@ -1132,6 +1156,18 @@ "References": "https://github.com/ssllabs/research/wiki/SSL-and-TLS-Deployment-Best-Practices#23-use-secure-cipher-suites", "DefaultValue": "By default the Kubernetes API server supports a wide range of TLS ciphers" } + ], + "ConfigRequirements": [ + { + "Check": "apiserver_strong_ciphers_only", + "ConfigKey": "apiserver_strong_ciphers", + "Operator": "subset", + "Value": [ + "TLS_AES_128_GCM_SHA256", + "TLS_AES_256_GCM_SHA384", + "TLS_CHACHA20_POLY1305_SHA256" + ] + } ] }, { @@ -2092,6 +2128,23 @@ "References": "", "DefaultValue": "By default the Kubernetes API server supports a wide range of TLS ciphers" } + ], + "ConfigRequirements": [ + { + "Check": "kubelet_strong_ciphers_only", + "ConfigKey": "kubelet_strong_ciphers", + "Operator": "subset", + "Value": [ + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305", + "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305", + "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", + "TLS_RSA_WITH_AES_256_GCM_SHA384", + "TLS_RSA_WITH_AES_128_GCM_SHA256" + ] + } ] }, { diff --git a/prowler/compliance/kubernetes/pci_4.0_kubernetes.json b/prowler/compliance/kubernetes/pci_4.0_kubernetes.json index 0b301a06456..38d9557d5f0 100644 --- a/prowler/compliance/kubernetes/pci_4.0_kubernetes.json +++ b/prowler/compliance/kubernetes/pci_4.0_kubernetes.json @@ -8268,6 +8268,14 @@ "Checks": [ "apiserver_audit_log_maxage_set" ], + "ConfigRequirements": [ + { + "Check": "apiserver_audit_log_maxage_set", + "ConfigKey": "audit_log_maxage", + "Operator": "gte", + "Value": 365 + } + ], "Attributes": [ { "Section": "10.5.1: Audit log history is retained and available for analysis.", @@ -10054,6 +10062,14 @@ "Checks": [ "apiserver_audit_log_maxage_set" ], + "ConfigRequirements": [ + { + "Check": "apiserver_audit_log_maxage_set", + "ConfigKey": "audit_log_maxage", + "Operator": "gte", + "Value": 365 + } + ], "Attributes": [ { "Section": "3.3.1.3: Sensitive authentication data (SAD) is not stored after authorization.", @@ -10250,6 +10266,14 @@ "Checks": [ "apiserver_audit_log_maxage_set" ], + "ConfigRequirements": [ + { + "Check": "apiserver_audit_log_maxage_set", + "ConfigKey": "audit_log_maxage", + "Operator": "gte", + "Value": 365 + } + ], "Attributes": [ { "Section": "3.3.3: Sensitive authentication data (SAD) is not stored after authorization.", @@ -13004,6 +13028,14 @@ "Checks": [ "apiserver_audit_log_maxage_set" ], + "ConfigRequirements": [ + { + "Check": "apiserver_audit_log_maxage_set", + "ConfigKey": "audit_log_maxage", + "Operator": "gte", + "Value": 365 + } + ], "Attributes": [ { "Section": "5.3.4: Anti-malware mechanisms and processes are active, maintained, and monitored.", diff --git a/prowler/compliance/kubernetes/prowler_threatscore_kubernetes.json b/prowler/compliance/kubernetes/prowler_threatscore_kubernetes.json index 11ffe42485d..58ef7c6ddbd 100644 --- a/prowler/compliance/kubernetes/prowler_threatscore_kubernetes.json +++ b/prowler/compliance/kubernetes/prowler_threatscore_kubernetes.json @@ -1083,6 +1083,23 @@ "LevelOfRisk": 4, "Weight": 100 } + ], + "ConfigRequirements": [ + { + "Check": "kubelet_strong_ciphers_only", + "ConfigKey": "kubelet_strong_ciphers", + "Operator": "subset", + "Value": [ + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305", + "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305", + "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", + "TLS_RSA_WITH_AES_256_GCM_SHA384", + "TLS_RSA_WITH_AES_128_GCM_SHA256" + ] + } ] }, { @@ -1199,6 +1216,14 @@ "Checks": [ "apiserver_audit_log_maxage_set" ], + "ConfigRequirements": [ + { + "Check": "apiserver_audit_log_maxage_set", + "ConfigKey": "audit_log_maxage", + "Operator": "gte", + "Value": 30 + } + ], "Attributes": [ { "Title": "API Server audit log retention configured", @@ -1227,6 +1252,14 @@ "LevelOfRisk": 3, "Weight": 10 } + ], + "ConfigRequirements": [ + { + "Check": "apiserver_audit_log_maxbackup_set", + "ConfigKey": "audit_log_maxbackup", + "Operator": "gte", + "Value": 10 + } ] }, { @@ -1245,6 +1278,14 @@ "LevelOfRisk": 2, "Weight": 8 } + ], + "ConfigRequirements": [ + { + "Check": "apiserver_audit_log_maxsize_set", + "ConfigKey": "audit_log_maxsize", + "Operator": "gte", + "Value": 100 + } ] }, { diff --git a/prowler/compliance/m365/cis_4.0_m365.json b/prowler/compliance/m365/cis_4.0_m365.json index 23582fbaacf..35d64d358e1 100644 --- a/prowler/compliance/m365/cis_4.0_m365.json +++ b/prowler/compliance/m365/cis_4.0_m365.json @@ -565,6 +565,68 @@ "References": "https://learn.microsoft.com/en-us/powershell/module/exchange/get-malwarefilterpolicy?view=exchange-ps:https://learn.microsoft.com/en-us/microsoft-365/security/office-365-security/anti-malware-policies-configure?view=o365-worldwide:https://learn.microsoft.com/en-us/office/compatibility/office-file-format-reference", "DefaultValue": "The following extensions are blocked by default:ace, ani, apk, app, appx, arj, bat, cab, cmd, com, deb, dex, dll, docm, elf, exe, hta, img, iso, jar, jnlp, kext, lha, lib, library, lnk, lzh, macho, msc, msi, msix, msp, mst, pif, ppa, ppam, reg, rev, scf, scr, sct, sys, uif, vb, vbe, vbs, vxd, wsc, wsf, wsh, xll, xz, z" } + ], + "ConfigRequirements": [ + { + "Check": "defender_malware_policy_comprehensive_attachments_filter_applied", + "ConfigKey": "recommended_blocked_file_types", + "Operator": "superset", + "Value": [ + "ace", + "ani", + "apk", + "app", + "appx", + "arj", + "bat", + "cab", + "cmd", + "com", + "deb", + "dex", + "dll", + "docm", + "elf", + "exe", + "hta", + "img", + "iso", + "jar", + "jnlp", + "kext", + "lha", + "lib", + "library", + "lnk", + "lzh", + "macho", + "msc", + "msi", + "msix", + "msp", + "mst", + "pif", + "ppa", + "ppam", + "reg", + "rev", + "scf", + "scr", + "sct", + "sys", + "uif", + "vb", + "vbe", + "vbs", + "vxd", + "wsc", + "wsf", + "wsh", + "xll", + "xz", + "z" + ] + } ] }, { @@ -1209,6 +1271,14 @@ "References": "https://learn.microsoft.com/en-us/entra/identity/conditional-access/howto-conditional-access-session-lifetime", "DefaultValue": "The default configuration for user sign-in frequency is a rolling window of 90 days." } + ], + "ConfigRequirements": [ + { + "Check": "entra_admin_users_sign_in_frequency_enabled", + "ConfigKey": "sign_in_frequency", + "Operator": "lte", + "Value": 4 + } ] }, { diff --git a/prowler/compliance/m365/cis_6.0_m365.json b/prowler/compliance/m365/cis_6.0_m365.json index d0dcfb2e6d5..5815c67ac9c 100644 --- a/prowler/compliance/m365/cis_6.0_m365.json +++ b/prowler/compliance/m365/cis_6.0_m365.json @@ -582,6 +582,68 @@ "References": "https://learn.microsoft.com/en-us/powershell/module/exchange/get-malwarefilterpolicy?view=exchange-ps:https://learn.microsoft.com/en-us/microsoft-365/security/office-365-security/anti-malware-policies-configure?view=o365-worldwide:https://learn.microsoft.com/en-us/office/compatibility/office-file-format-reference", "DefaultValue": "53 extensions are blocked by default." } + ], + "ConfigRequirements": [ + { + "Check": "defender_malware_policy_comprehensive_attachments_filter_applied", + "ConfigKey": "recommended_blocked_file_types", + "Operator": "superset", + "Value": [ + "ace", + "ani", + "apk", + "app", + "appx", + "arj", + "bat", + "cab", + "cmd", + "com", + "deb", + "dex", + "dll", + "docm", + "elf", + "exe", + "hta", + "img", + "iso", + "jar", + "jnlp", + "kext", + "lha", + "lib", + "library", + "lnk", + "lzh", + "macho", + "msc", + "msi", + "msix", + "msp", + "mst", + "pif", + "ppa", + "ppam", + "reg", + "rev", + "scf", + "scr", + "sct", + "sys", + "uif", + "vb", + "vbe", + "vbs", + "vxd", + "wsc", + "wsf", + "wsh", + "xll", + "xz", + "z" + ] + } ] }, { @@ -1949,6 +2011,14 @@ "References": "https://learn.microsoft.com/en-us/purview/audit-mailboxes?view=o365-worldwide", "DefaultValue": "AuditEnabled: True for all mailboxes except Resource Mailboxes, Public Folder Mailboxes, and DiscoverySearch Mailbox" } + ], + "ConfigRequirements": [ + { + "Check": "exchange_user_mailbox_auditing_enabled", + "ConfigKey": "audit_log_age", + "Operator": "gte", + "Value": 90 + } ] }, { @@ -2110,6 +2180,14 @@ "References": "https://learn.microsoft.com/en-us/exchange/clients-and-mobile-in-exchange-online/mailtips/mailtips", "DefaultValue": "MailTipsAllTipsEnabled: True, MailTipsExternalRecipientsTipsEnabled: False, MailTipsGroupMetricsEnabled: True, MailTipsLargeAudienceThreshold: 25" } + ], + "ConfigRequirements": [ + { + "Check": "exchange_organization_mailtips_enabled", + "ConfigKey": "recommended_mailtips_large_audience_threshold", + "Operator": "lte", + "Value": 25 + } ] }, { diff --git a/prowler/compliance/m365/prowler_threatscore_m365.json b/prowler/compliance/m365/prowler_threatscore_m365.json index 286a4fff39e..f5a6cd1cd81 100644 --- a/prowler/compliance/m365/prowler_threatscore_m365.json +++ b/prowler/compliance/m365/prowler_threatscore_m365.json @@ -819,6 +819,14 @@ "LevelOfRisk": 4, "Weight": 100 } + ], + "ConfigRequirements": [ + { + "Check": "entra_admin_users_sign_in_frequency_enabled", + "ConfigKey": "sign_in_frequency", + "Operator": "lte", + "Value": 4 + } ] }, { @@ -964,6 +972,68 @@ "LevelOfRisk": 2, "Weight": 8 } + ], + "ConfigRequirements": [ + { + "Check": "defender_malware_policy_comprehensive_attachments_filter_applied", + "ConfigKey": "recommended_blocked_file_types", + "Operator": "superset", + "Value": [ + "ace", + "ani", + "apk", + "app", + "appx", + "arj", + "bat", + "cab", + "cmd", + "com", + "deb", + "dex", + "dll", + "docm", + "elf", + "exe", + "hta", + "img", + "iso", + "jar", + "jnlp", + "kext", + "lha", + "lib", + "library", + "lnk", + "lzh", + "macho", + "msc", + "msi", + "msix", + "msp", + "mst", + "pif", + "ppa", + "ppam", + "reg", + "rev", + "scf", + "scr", + "sct", + "sys", + "uif", + "vb", + "vbe", + "vbs", + "vxd", + "wsc", + "wsf", + "wsh", + "xll", + "xz", + "z" + ] + } ] }, { diff --git a/prowler/compliance/okta/okta_idaas_stig_v1r2_okta.json b/prowler/compliance/okta/okta_idaas_stig_v1r2_okta.json index bac3d9132e6..66747cdba73 100644 --- a/prowler/compliance/okta/okta_idaas_stig_v1r2_okta.json +++ b/prowler/compliance/okta/okta_idaas_stig_v1r2_okta.json @@ -25,6 +25,14 @@ "CheckText": "From the Admin Console: 1. Select Security >> Global Session Policy. 2. In the Default Policy, verify a rule is configured at Priority 1 that is not named \"Default Rule\". 3. Click the edit icon next to the Priority 1 rule. 4. Verify the \"Maximum Okta global session idle time\" is set to 15 minutes. If \"Maximum Okta global session idle time\" is not set to 15 minutes, this is a finding.", "FixText": "From the Admin Console: 1. Go to Security >> Global Session Policy. 2. Select the Default Policy. 3. In the Rules table, make these updates: - Click \"Add rule\". - Set \"Maximum Okta global session idle time\" to 15 minutes." } + ], + "ConfigRequirements": [ + { + "Check": "signon_global_session_idle_timeout_15min", + "ConfigKey": "okta_max_session_idle_minutes", + "Operator": "lte", + "Value": 15 + } ] }, { @@ -46,6 +54,14 @@ "CheckText": "From the Admin Console: 1. Select Applications >> Applications >> Okta Admin Console. 2. In the Sign On tab, under \"Okta Admin Console session\", verify the \"Maximum app session idle time\" is set to 15 minutes. If the \"Maximum app session idle time\" is not set to 15 minutes, this is a finding.", "FixText": "From the Admin Console: 1. Select Applications >> Applications >> Okta Admin Console. 2. In the Sign On tab, under \"Okta Admin Console session\", set the \"Maximum app session idle time\" to 15 minutes." } + ], + "ConfigRequirements": [ + { + "Check": "application_admin_console_session_idle_timeout_15min", + "ConfigKey": "okta_admin_console_idle_timeout_max_minutes", + "Operator": "lte", + "Value": 15 + } ] }, { @@ -69,6 +85,14 @@ "CheckText": "If Okta Services rely on external directory services for user sourcing, this is not applicable, and the connected directory services must perform this function. Go to Workflows >> Automations and verify that an Automation has been created to disable accounts after 35 days of inactivity. If the Okta configuration does not automatically disable accounts after a 35-day period of account inactivity, this is a finding.", "FixText": "From the Admin Console: 1. Go to Workflow >> Automations and select \"Add Automation\". 2. Create a name for the Automation (e.g., \"User Inactivity\"). 3. Click \"Add Condition\" and select \"User Inactivity in Okta\". 4. In the duration field, enter 35 days and click \"Save\". 5 Click the edit button next to \"Select Schedule\". 6. Configure the \"Schedule\" field for \"Run Daily\" and set the \"Time\" field to an organizationally defined time to run this automation. Click \"Save\". 7. Click the edit button next to \"Select group membership\". 8. In the \"Applies to\" field, select the group \"Everyone\" by typing it into the field. Click \"Save\". 9. Click \"Add Action\" and select \"Change User lifecycle state in Okta\". 10. In the \"Change user state to\" field, select \"Suspended\" and click \"Save\". 11. Click the \"Inactive\" button near the top of the section screen and select \"Activate\"." } + ], + "ConfigRequirements": [ + { + "Check": "user_inactivity_automation_35d_enabled", + "ConfigKey": "okta_user_inactivity_max_days", + "Operator": "lte", + "Value": 35 + } ] }, { @@ -395,6 +419,14 @@ "CheckText": "From the Admin Console: 1. Select Security >> Global Session Policy. 2. In the Default Policy, verify a rule is configured at Priority 1 that is not named \"Default Rule\". 3. Click the \"Edit\" icon next to the Priority 1 rule. 4. Verify \"Maximum Okta global session lifetime\" is set to 18 hours. If the above is not set, this is a finding.", "FixText": "From the Admin Console: 1. Go to Security >> Global Session Policy. 2. Select the Default Policy. 3. In the Rules table, make these updates: - Click \"Add rule\". - Set \"Maximum Okta global session lifetime\" to 18 hours." } + ], + "ConfigRequirements": [ + { + "Check": "signon_global_session_lifetime_18h", + "ConfigKey": "okta_max_session_lifetime_minutes", + "Operator": "lte", + "Value": 1080 + } ] }, { diff --git a/prowler/lib/check/compliance_config_eval.py b/prowler/lib/check/compliance_config_eval.py new file mode 100644 index 00000000000..763e1389c2a --- /dev/null +++ b/prowler/lib/check/compliance_config_eval.py @@ -0,0 +1,404 @@ +"""Shared evaluation of a requirement's configuration constraints. + +Some compliance requirements only hold if the configurable checks they map to +ran with a configuration strict enough for the requirement. For example CIS AWS +6.0 requirement 2.11 ("credentials unused for 45 days or more are disabled") +maps `iam_user_accesskey_unused` (config `max_unused_access_keys_days`); if the +user loosens that to 120 days the check can PASS while the requirement is, in +fact, not satisfied. + +A requirement declares its expectations via ``ConfigRequirements`` (a list of +``{Check, ConfigKey, Operator, Value}``). The configuration a scan applied is a +single, scan-global mapping (the provider's ``audit_config``), so the rules are +evaluated against that mapping directly. This module is consumed by the SDK +compliance outputs (CSV + CLI table) and by the Prowler App backend so the rule +lives in one place. +""" + +from typing import Any, Optional + +# Leading sentence of the message prepended to a finding's ``status_extended`` +# when its requirement's config constraints are not satisfied and the status is +# forced to FAIL. It opens every config-not-valid message, so it doubles as a +# stable marker for detecting the case programmatically. +CONFIG_NOT_VALID_PREFIX = "Configuration not valid for this requirement." + + +def _format_value(value: Any) -> str: + """Render a constraint value for a user-facing message (lists comma-joined).""" + if isinstance(value, (list, tuple, set)): + return ", ".join(str(item) for item in value) + return str(value) + + +def _describe_violation( + check: Any, config_key: Any, applied: Any, operator: str, expected: Any +) -> str: + """Return a product-friendly explanation of why a config violates a constraint. + + The message names the check and config key, the value the scan applied, what + the requirement needs, and how to fix it, in plain language rather than the + operator/value pair. + + Args: + check: the check the requirement maps to (e.g. ``iam_user_accesskey_unused``). + config_key: the config option that was too loose (e.g. ``max_unused_access_keys_days``). + applied: the value the scan actually applied. + operator: the constraint operator (``lte``/``gte``/``eq``/``in``/``subset``/``superset``). + expected: the value the requirement expects. + + Returns: + A full, human-readable message ending with an actionable fix. + """ + applied_str = _format_value(applied) + expected_str = _format_value(expected) + needs, fix = { + "lte": ( + f"a value of {expected_str} or lower", + f"Update it to {expected_str} or lower.", + ), + "gte": ( + f"a value of {expected_str} or higher", + f"Update it to {expected_str} or higher.", + ), + "eq": ( + f"it set to {expected_str}", + f"Update it to {expected_str}.", + ), + "in": ( + f"it set to one of {expected_str}", + f"Update it to one of {expected_str}.", + ), + "subset": ( + f"it limited to {expected_str}", + f"Remove any value that is not in {expected_str}.", + ), + "superset": ( + f"it to include {expected_str}", + f"Make sure it includes {expected_str}.", + ), + }.get(operator, (f"a different value (expected {operator} {expected_str})", "")) + message = ( + f"{CONFIG_NOT_VALID_PREFIX} The check {check} has {config_key} set to " + f"{applied_str}, but the requirement needs {needs}." + ) + return f"{message} {fix}".strip() + + +def _check_operator(applied: Any, operator: str, expected: Any) -> bool: + """Return whether ``applied`` satisfies ``operator`` against ``expected``.""" + try: + if operator == "lte": + return applied <= expected + if operator == "gte": + return applied >= expected + if operator == "eq": + return applied == expected + if operator == "in": + return applied in expected + if operator in ("subset", "superset"): + # Set comparisons for list-valued configs (allowlists / denylists). + # Both sides must be collections; anything else is not satisfiable. + if not isinstance(applied, (list, tuple, set)) or not isinstance( + expected, (list, tuple, set) + ): + return False + applied_set, expected_set = set(applied), set(expected) + if operator == "subset": + return applied_set <= expected_set + return applied_set >= expected_set + except TypeError: + # Mismatched/unhashable types → treat as not satisfied. + return False + # Unknown operator: do not block the requirement on a malformed constraint. + return True + + +def evaluate_config_constraints( + config_requirements: Optional[list[Any]], + audit_config: Optional[dict[str, Any]], + provider_type: Optional[str] = None, +) -> tuple[bool, str]: + """Evaluate a requirement's config constraints against the scan's config. + + Args: + config_requirements: list of constraints, each a mapping (or object with + the same attributes) holding ``Check``, ``ConfigKey``, ``Operator``, + ``Value`` and an optional ``Provider``. ``None``/empty means the + requirement has no config expectations. + audit_config: the scan-global configuration mapping (the provider's + ``audit_config``, i.e. ``{config_key: value}``). The applied config + is identical across every resource and region of a scan. + provider_type: the provider being scanned (e.g. ``aws``). A constraint + tagged with a ``Provider`` is only evaluated when it matches this + value; this scopes universal (multi-provider) framework constraints + to the right provider. ``None`` disables provider scoping (every + constraint is evaluated), which is the correct behaviour for + single-provider frameworks. + + Returns: + ``(is_compliant, reason)``. ``is_compliant`` is ``True`` when there are + no constraints or every explicitly-set value satisfies its constraint. + When a configured value violates a constraint, returns ``(False, reason)`` + describing the first violation. A constraint whose ``ConfigKey`` was not + explicitly set is skipped (the check's default is assumed to match what + the requirement expects). + """ + if not config_requirements: + return True, "" + + audit_config = audit_config or {} + + for constraint in config_requirements: + # Accept both dicts (API template) and objects (Pydantic model). + if isinstance(constraint, dict): + check = constraint.get("Check") + config_key = constraint.get("ConfigKey") + operator = constraint.get("Operator") + expected = constraint.get("Value") + provider = constraint.get("Provider") + else: + check = getattr(constraint, "Check", None) + config_key = getattr(constraint, "ConfigKey", None) + operator = getattr(constraint, "Operator", None) + expected = getattr(constraint, "Value", None) + provider = getattr(constraint, "Provider", None) + + # Constraint scoped to another provider → not applicable to this scan. + # Compared case-insensitively (and trimmed) so a constraint authored as + # e.g. "AWS" still scopes to the "aws" scan instead of being silently + # bypassed by a casing/format mismatch. + if ( + provider + and provider_type + and str(provider).strip().lower() != str(provider_type).strip().lower() + ): + continue + + if config_key not in audit_config: + # Config not explicitly set → default is assumed adequate. + continue + + applied = audit_config[config_key] + if not _check_operator(applied, operator, expected): + reason = _describe_violation(check, config_key, applied, operator, expected) + return False, reason + + return True, "" + + +def get_scan_audit_config() -> dict[str, Any]: + """Return the scan-global applied configuration (the provider's audit_config). + + The applied config is identical across every resource and region of a scan, + so every compliance output evaluates constraints against this single mapping. + Imported lazily to avoid a circular import with the provider package and to + keep this module usable from contexts without a global provider. + + Returns: + The provider's ``audit_config`` mapping, or ``{}`` when no global + provider is set (``AttributeError``) or the provider package cannot be + imported (``ImportError``). + """ + try: + from prowler.providers.common.provider import Provider + + return Provider.get_global_provider().audit_config or {} + except (AttributeError, ImportError): + # No global provider set, or provider package unavailable. + return {} + + +def get_scan_provider_type() -> str: + """Return the provider being scanned (e.g. ``aws``) for constraint scoping. + + Imported lazily to avoid a circular import with the provider package and to + keep this module usable from contexts without a global provider. + + Returns: + The provider's ``type`` (e.g. ``aws``), or ``""`` when no global provider + is set (``AttributeError``) or the provider package cannot be imported + (``ImportError``); an empty string disables provider scoping. + """ + try: + from prowler.providers.common.provider import Provider + + return Provider.get_global_provider().type or "" + except (AttributeError, ImportError): + # No global provider set, or provider package unavailable. + return "" + + +def _requirement_id(requirement: Any) -> Optional[str]: + """Return a requirement's id across the legacy (``Id``) and universal (``id``) models.""" + return getattr(requirement, "Id", None) or getattr(requirement, "id", None) + + +def _requirement_constraints(requirement: Any) -> Optional[list]: + """Return a requirement's config constraints across both model flavours. + + Legacy ``Compliance_Requirement`` exposes ``ConfigRequirements`` (a list of + Pydantic models); ``UniversalComplianceRequirement`` exposes + ``config_requirements`` (a list of dicts). ``evaluate_config_constraints`` + handles both element types. + """ + return getattr(requirement, "ConfigRequirements", None) or getattr( + requirement, "config_requirements", None + ) + + +def build_requirement_config_status( + requirements: list[Any], + audit_config: Optional[dict[str, Any]] = None, + provider_type: Optional[str] = None, +) -> dict[str, tuple[bool, str]]: + """Map every requirement id to its ``(is_compliant, reason)`` config verdict. + + Only requirements that actually declare constraints are included; callers use + ``dict.get(req_id)`` (returning ``None`` → no constraints → no override). + + Args: + requirements: the framework's requirements (legacy or universal models). + audit_config: the applied config; resolved via ``get_scan_audit_config`` + when omitted. + provider_type: the provider being scanned, for constraint scoping; + resolved via ``get_scan_provider_type`` when omitted. + + Returns: + A mapping ``{requirement_id: (is_compliant, reason)}`` containing only the + requirements that declare config constraints. + """ + if audit_config is None: + audit_config = get_scan_audit_config() + if provider_type is None: + provider_type = get_scan_provider_type() + status = {} + for requirement in requirements: + constraints = _requirement_constraints(requirement) + if constraints: + status[_requirement_id(requirement)] = evaluate_config_constraints( + constraints, audit_config, provider_type + ) + return status + + +def resolve_requirement_config_status( + requirement: Any, + audit_config: dict[str, Any], + cache: dict, + provider_type: Optional[str] = None, +) -> tuple[bool, str]: + """Return a requirement's ``(is_compliant, reason)`` verdict, memoised in ``cache``. + + For table generators that iterate findings × compliances and only encounter + each requirement lazily. + + Args: + requirement: the requirement (legacy or universal model). + audit_config: the scan-global applied config. + cache: a dict keyed by requirement id, reused across the whole table + build to memoise verdicts. + provider_type: the provider being scanned, for constraint scoping; + resolved via ``get_scan_provider_type`` when omitted. + + Returns: + The ``(is_compliant, reason)`` verdict; ``(True, "")`` when the + requirement declares no constraints. + """ + req_id = _requirement_id(requirement) + if req_id not in cache: + constraints = _requirement_constraints(requirement) + if constraints: + if provider_type is None: + provider_type = get_scan_provider_type() + cache[req_id] = evaluate_config_constraints( + constraints, audit_config, provider_type + ) + else: + cache[req_id] = (True, "") + return cache[req_id] + + +def apply_config_status( + status: str, + status_extended: str, + config_status: Optional[tuple[bool, str]], +) -> tuple[str, str]: + """Override a finding's ``(status, status_extended)`` when its config is invalid. + + A requirement whose configurable checks ran with a config too loose to trust + is forced to ``FAIL`` regardless of the finding's own status, with the reason + prepended to ``status_extended``. + + Args: + status: the finding's original status (e.g. ``PASS`` / ``FAIL``). + status_extended: the finding's extended status message. + config_status: the ``(is_compliant, reason)`` tuple from + ``build_requirement_config_status``/``resolve_requirement_config_status``, + or ``None`` when the requirement declares no constraints. + + Returns: + The ``(status, status_extended)`` to report: unchanged when the config is + valid (or ``config_status`` is ``None``); otherwise ``FAIL`` with the + reason prepended to ``status_extended``. + """ + if not config_status or config_status[0]: + return status, status_extended + return ( + "FAIL", + f"{config_status[1]} {status_extended}".strip(), + ) + + +def get_effective_status( + status: str, + config_status: Optional[tuple[bool, str]], +) -> str: + """Return the effective status for table aggregation. + + Args: + status: the finding's original status. + config_status: the ``(is_compliant, reason)`` tuple, or ``None`` when the + requirement declares no constraints. + + Returns: + ``FAIL`` when ``config_status`` marks the config invalid; otherwise the + finding's original ``status``. + """ + if not config_status or config_status[0]: + return status + return "FAIL" + + +def accumulate_overview_status( + index: int, + status: str, + pass_indices: set, + fail_indices: set, + muted_indices: set, +) -> None: + """Record a finding in the overview totals once, with FAIL precedence over PASS (sets mutated in place).""" + if status == "Muted": + muted_indices.add(index) + elif status == "FAIL": + fail_indices.add(index) + pass_indices.discard(index) + elif status == "PASS" and index not in fail_indices: + pass_indices.add(index) + + +def accumulate_group_status( + index: int, + status: str, + counts: dict, + seen: dict, +) -> None: + """Count a finding once per group, upgrading a counted PASS to FAIL on conflict (mutates ``counts``/``seen``).""" + previous = seen.get(index) + if previous is None: + seen[index] = status + counts[status] += 1 + elif previous == "PASS" and status == "FAIL": + seen[index] = "FAIL" + counts["PASS"] -= 1 + counts["FAIL"] += 1 diff --git a/prowler/lib/check/compliance_models.py b/prowler/lib/check/compliance_models.py index f883bf60b18..80322a6929e 100644 --- a/prowler/lib/check/compliance_models.py +++ b/prowler/lib/check/compliance_models.py @@ -3,7 +3,7 @@ import os import sys from enum import Enum -from typing import Optional, Union +from typing import Any, Literal, Optional, Union from pydantic.v1 import BaseModel, Field, ValidationError, root_validator @@ -170,6 +170,79 @@ class ISO27001_2013_Requirement_Attribute(BaseModel): Check_Summary: str +# Base Compliance Model +class Compliance_Requirement_ConfigConstraint(BaseModel): + """A constraint a requirement places on a configurable check's config. + + Declares that the configurable check ``Check`` must have run with + ``ConfigKey`` satisfying ``Operator`` ``Value`` for the requirement's + result to be trusted. Example: ``max_unused_access_keys_days <= 45``. + + ``Provider`` scopes the constraint to a single provider. It is required for + universal (multi-provider) frameworks, where the same requirement maps + checks across providers and a constraint must only apply when that provider + is the one being scanned. Single-provider frameworks may omit it (the + framework's provider is already the one being scanned). + + Operators: + - ``lte``/``gte``/``eq``: scalar comparisons (e.g. a max-age or min-retention + threshold, or a boolean toggle). + - ``in``: the applied scalar must be one of ``Value`` (a list). + - ``subset``: the applied list must be a subset of ``Value`` — for allowlist + configs (e.g. ``recommended_minimal_tls_versions``); widening the allowlist + with a weaker value (e.g. TLS ``1.0``) breaks the constraint. + - ``superset``: the applied list must be a superset of ``Value`` — for + denylist configs (e.g. ``insecure_key_algorithms``); removing a forbidden + value from the denylist breaks the constraint. + """ + + Check: str + ConfigKey: str + Operator: Literal["lte", "gte", "eq", "in", "subset", "superset"] + # ``bool`` must precede ``int`` so pydantic v1 keeps booleans (e.g. a + # ``mute_non_default_regions == false`` constraint) instead of coercing + # them to 0/1. + Value: Union[bool, int, float, str, list[Any]] + # Provider this constraint applies to (e.g. ``aws``), matched + # case-insensitively. ``None`` applies whenever the requirement runs + # (single-provider frameworks). + Provider: Optional[str] = None + + @root_validator + @classmethod + def validate_value_matches_operator(cls, values): # noqa: F841 + """Ensure ``Value``'s type is consistent with ``Operator``. + + Without this, a mistyped value (e.g. ``gte`` with a list, or ``subset`` + with a scalar) is not rejected at load time and ``_check_operator`` + silently treats it as *not satisfied*, forcing the requirement to a + spurious config-not-valid FAIL. Validating here turns that into a + clear error when the framework is loaded. + """ + operator = values.get("Operator") + value = values.get("Value") + # If Operator/Value failed their own validation they are absent here. + if operator is None or value is None: + return values + if operator in ("in", "subset", "superset"): + if not isinstance(value, list): + raise ValueError( + f"operator '{operator}' requires a list Value, got {type(value).__name__}" + ) + elif operator in ("lte", "gte"): + # bool is an int subclass but is never a valid numeric threshold. + if isinstance(value, bool) or not isinstance(value, (int, float)): + raise ValueError( + f"operator '{operator}' requires a numeric Value, got {value!r}" + ) + elif operator == "eq": + if not isinstance(value, (bool, int, float, str)): + raise ValueError( + f"operator 'eq' requires a scalar Value, got {type(value).__name__}" + ) + return values + + # MITRE Requirement Attribute for AWS class Mitre_Requirement_Attribute_AWS(BaseModel): """MITRE Requirement Attribute""" @@ -217,6 +290,9 @@ class Mitre_Requirement(BaseModel): list[Mitre_Requirement_Attribute_GCP], ] Checks: list[str] + # MITRE checks may also declare config constraints; without this field + # Pydantic silently drops them during parsing. + ConfigRequirements: Optional[list[Compliance_Requirement_ConfigConstraint]] = None # KISA-ISMS-P Requirement Attribute @@ -303,7 +379,6 @@ class STIG_Requirement_Attribute(BaseModel): FixText: Optional[str] = None -# Base Compliance Model # TODO: move this to compliance folder class Compliance_Requirement(BaseModel): """Compliance_Requirement holds the base model for every requirement within a compliance framework""" @@ -329,6 +404,7 @@ class Compliance_Requirement(BaseModel): ] ] Checks: list[str] + ConfigRequirements: Optional[list[Compliance_Requirement_ConfigConstraint]] = None class Compliance(BaseModel): @@ -701,6 +777,10 @@ class UniversalComplianceRequirement(BaseModel): name: Optional[str] = None attributes: dict = Field(default_factory=dict) checks: dict[str, list[str]] = Field(default_factory=dict) + # Typed with the same constraint model as legacy so the operator/value + # validation also covers universal frameworks. evaluate_config_constraints + # accepts both dicts and model objects, so downstream consumers are unaffected. + config_requirements: Optional[list[Compliance_Requirement_ConfigConstraint]] = None tactics: Optional[list] = None sub_techniques: Optional[list] = None platforms: Optional[list] = None @@ -894,6 +974,11 @@ def adapt_legacy_to_universal(legacy: Compliance) -> ComplianceFramework: # For MITRE, promote special fields and store raw attributes raw_attrs = [attr.dict() for attr in req.Attributes] attrs = {"_raw_attributes": raw_attrs} + config_requirements = ( + [c.dict() for c in req.ConfigRequirements] + if getattr(req, "ConfigRequirements", None) + else None + ) universal_requirements.append( UniversalComplianceRequirement( id=req.Id, @@ -901,6 +986,7 @@ def adapt_legacy_to_universal(legacy: Compliance) -> ComplianceFramework: name=req.Name, attributes=attrs, checks=req_checks, + config_requirements=config_requirements, tactics=req.Tactics, sub_techniques=req.SubTechniques, platforms=req.Platforms, @@ -913,6 +999,11 @@ def adapt_legacy_to_universal(legacy: Compliance) -> ComplianceFramework: attrs = req.Attributes[0].dict() else: attrs = {} + config_requirements = ( + [c.dict() for c in req.ConfigRequirements] + if getattr(req, "ConfigRequirements", None) + else None + ) universal_requirements.append( UniversalComplianceRequirement( id=req.Id, @@ -920,6 +1011,7 @@ def adapt_legacy_to_universal(legacy: Compliance) -> ComplianceFramework: name=req.Name, attributes=attrs, checks=req_checks, + config_requirements=config_requirements, ) ) diff --git a/prowler/lib/outputs/compliance/asd_essential_eight/asd_essential_eight.py b/prowler/lib/outputs/compliance/asd_essential_eight/asd_essential_eight.py index df23aeb1d1d..074e684f338 100644 --- a/prowler/lib/outputs/compliance/asd_essential_eight/asd_essential_eight.py +++ b/prowler/lib/outputs/compliance/asd_essential_eight/asd_essential_eight.py @@ -2,6 +2,13 @@ from tabulate import tabulate from prowler.config.config import orange_color +from prowler.lib.check.compliance_config_eval import ( + accumulate_group_status, + accumulate_overview_status, + get_effective_status, + get_scan_audit_config, + resolve_requirement_config_status, +) def get_asd_essential_eight_table( @@ -19,11 +26,13 @@ def get_asd_essential_eight_table( "Status": [], "Muted": [], } - pass_count = [] - fail_count = [] - muted_count = [] + pass_count = set() + fail_count = set() + muted_count = set() section_seen = {} provider = "" + audit_config = get_scan_audit_config() + config_status_cache = {} for index, finding in enumerate(findings): check = bulk_checks_metadata[finding.check_metadata.CheckID] check_compliances = check.Compliance @@ -31,6 +40,12 @@ def get_asd_essential_eight_table( if compliance.Framework == "ASD-Essential-Eight": provider = compliance.Provider for requirement in compliance.Requirements: + config_status = resolve_requirement_config_status( + requirement, audit_config, config_status_cache + ) + effective_status = get_effective_status( + finding.status, config_status + ) for attribute in requirement.Attributes: section = attribute.Section if section not in sections: @@ -39,29 +54,15 @@ def get_asd_essential_eight_table( "PASS": 0, "Muted": 0, } - section_seen[section] = set() + section_seen[section] = {} - # Overview totals: count each finding once per framework - if finding.muted: - if index not in muted_count: - muted_count.append(index) - elif finding.status == "FAIL": - if index not in fail_count: - fail_count.append(index) - elif finding.status == "PASS": - if index not in pass_count: - pass_count.append(index) - - # Per-section counts: count each finding once per section - # it belongs to (a finding can map to several sections). - if index not in section_seen[section]: - section_seen[section].add(index) - if finding.muted: - sections[section]["Muted"] += 1 - elif finding.status == "FAIL": - sections[section]["FAIL"] += 1 - elif finding.status == "PASS": - sections[section]["PASS"] += 1 + status = "Muted" if finding.muted else effective_status + accumulate_overview_status( + index, status, pass_count, fail_count, muted_count + ) + accumulate_group_status( + index, status, sections[section], section_seen[section] + ) sections = dict(sorted(sections.items())) for section in sections: diff --git a/prowler/lib/outputs/compliance/asd_essential_eight/asd_essential_eight_aws.py b/prowler/lib/outputs/compliance/asd_essential_eight/asd_essential_eight_aws.py index c264c81505c..4bdf9cd8511 100644 --- a/prowler/lib/outputs/compliance/asd_essential_eight/asd_essential_eight_aws.py +++ b/prowler/lib/outputs/compliance/asd_essential_eight/asd_essential_eight_aws.py @@ -1,4 +1,8 @@ from prowler.config.config import timestamp +from prowler.lib.check.compliance_config_eval import ( + apply_config_status, + build_requirement_config_status, +) from prowler.lib.check.compliance_models import Compliance from prowler.lib.outputs.compliance.asd_essential_eight.models import ( ASDEssentialEightAWSModel, @@ -36,10 +40,19 @@ def transform( Returns: - None """ + requirement_config_status = build_requirement_config_status( + compliance.Requirements + ) + for finding in findings: for requirement in compliance.Requirements: # Source of truth: framework JSON, not finding.compliance snapshot (avoids CSV/UI count drift). if finding.check_id in requirement.Checks: + row_status, row_status_extended = apply_config_status( + finding.status, + finding.status_extended, + requirement_config_status.get(requirement.Id), + ) for attribute in requirement.Attributes: compliance_row = ASDEssentialEightAWSModel( Provider=finding.provider, @@ -63,8 +76,8 @@ def transform( Requirements_Attributes_AuditProcedure=attribute.AuditProcedure, Requirements_Attributes_AdditionalInformation=attribute.AdditionalInformation, Requirements_Attributes_References=attribute.References, - Status=finding.status, - StatusExtended=finding.status_extended, + Status=row_status, + StatusExtended=row_status_extended, ResourceId=finding.resource_uid, ResourceName=finding.resource_name, CheckId=finding.check_id, diff --git a/prowler/lib/outputs/compliance/aws_well_architected/aws_well_architected.py b/prowler/lib/outputs/compliance/aws_well_architected/aws_well_architected.py index 4a157b03240..69a37949f8f 100644 --- a/prowler/lib/outputs/compliance/aws_well_architected/aws_well_architected.py +++ b/prowler/lib/outputs/compliance/aws_well_architected/aws_well_architected.py @@ -1,4 +1,8 @@ from prowler.config.config import timestamp +from prowler.lib.check.compliance_config_eval import ( + apply_config_status, + build_requirement_config_status, +) from prowler.lib.check.compliance_models import Compliance from prowler.lib.outputs.compliance.aws_well_architected.models import ( AWSWellArchitectedModel, @@ -36,10 +40,18 @@ def transform( Returns: - None """ + requirement_config_status = build_requirement_config_status( + compliance.Requirements + ) for finding in findings: for requirement in compliance.Requirements: # Source of truth: framework JSON, not finding.compliance snapshot (avoids CSV/UI count drift). if finding.check_id in requirement.Checks: + row_status, row_status_extended = apply_config_status( + finding.status, + finding.status_extended, + requirement_config_status.get(requirement.Id), + ) for attribute in requirement.Attributes: compliance_row = AWSWellArchitectedModel( Provider=finding.provider, @@ -58,8 +70,8 @@ def transform( Requirements_Attributes_AssessmentMethod=attribute.AssessmentMethod, Requirements_Attributes_Description=attribute.Description, Requirements_Attributes_ImplementationGuidanceUrl=attribute.ImplementationGuidanceUrl, - Status=finding.status, - StatusExtended=finding.status_extended, + Status=row_status, + StatusExtended=row_status_extended, ResourceId=finding.resource_uid, ResourceName=finding.resource_name, CheckId=finding.check_id, diff --git a/prowler/lib/outputs/compliance/c5/c5.py b/prowler/lib/outputs/compliance/c5/c5.py index b48260b5a2e..003b2664cfe 100644 --- a/prowler/lib/outputs/compliance/c5/c5.py +++ b/prowler/lib/outputs/compliance/c5/c5.py @@ -2,6 +2,13 @@ from tabulate import tabulate from prowler.config.config import orange_color +from prowler.lib.check.compliance_config_eval import ( + accumulate_group_status, + accumulate_overview_status, + get_effective_status, + get_scan_audit_config, + resolve_requirement_config_status, +) def get_c5_table( @@ -18,12 +25,14 @@ def get_c5_table( "Status": [], "Muted": [], } - pass_count = [] - fail_count = [] - muted_count = [] + pass_count = set() + fail_count = set() + muted_count = set() sections = {} section_seen = {} provider = "" + audit_config = get_scan_audit_config() + config_status_cache = {} for index, finding in enumerate(findings): check = bulk_checks_metadata[finding.check_metadata.CheckID] check_compliances = check.Compliance @@ -31,34 +40,26 @@ def get_c5_table( if compliance.Framework == "C5": provider = compliance.Provider for requirement in compliance.Requirements: + config_status = resolve_requirement_config_status( + requirement, audit_config, config_status_cache + ) + effective_status = get_effective_status( + finding.status, config_status + ) for attribute in requirement.Attributes: section = attribute.Section if section not in sections: sections[section] = {"FAIL": 0, "PASS": 0, "Muted": 0} - section_seen[section] = set() - - # Overview totals: count each finding once per framework - if finding.muted: - if index not in muted_count: - muted_count.append(index) - elif finding.status == "FAIL": - if index not in fail_count: - fail_count.append(index) - elif finding.status == "PASS": - if index not in pass_count: - pass_count.append(index) + section_seen[section] = {} - # Per-section counts: count each finding once per section - # it belongs to (a finding can map to several sections). - if index not in section_seen[section]: - section_seen[section].add(index) - if finding.muted: - sections[section]["Muted"] += 1 - elif finding.status == "FAIL": - sections[section]["FAIL"] += 1 - elif finding.status == "PASS": - sections[section]["PASS"] += 1 + status = "Muted" if finding.muted else effective_status + accumulate_overview_status( + index, status, pass_count, fail_count, muted_count + ) + accumulate_group_status( + index, status, sections[section], section_seen[section] + ) sections = dict(sorted(sections.items())) for section in sections: diff --git a/prowler/lib/outputs/compliance/c5/c5_aws.py b/prowler/lib/outputs/compliance/c5/c5_aws.py index 5f13b77d7ac..b8a41154820 100644 --- a/prowler/lib/outputs/compliance/c5/c5_aws.py +++ b/prowler/lib/outputs/compliance/c5/c5_aws.py @@ -1,4 +1,8 @@ from prowler.config.config import timestamp +from prowler.lib.check.compliance_config_eval import ( + apply_config_status, + build_requirement_config_status, +) from prowler.lib.check.compliance_models import Compliance from prowler.lib.outputs.compliance.c5.models import AWSC5Model from prowler.lib.outputs.compliance.compliance_output import ComplianceOutput @@ -34,10 +38,19 @@ def transform( Returns: - None """ + requirement_config_status = build_requirement_config_status( + compliance.Requirements + ) + for finding in findings: for requirement in compliance.Requirements: # Source of truth: framework JSON, not finding.compliance snapshot (avoids CSV/UI count drift). if finding.check_id in requirement.Checks: + row_status, row_status_extended = apply_config_status( + finding.status, + finding.status_extended, + requirement_config_status.get(requirement.Id), + ) for attribute in requirement.Attributes: compliance_row = AWSC5Model( Provider=finding.provider, @@ -52,8 +65,8 @@ def transform( Requirements_Attributes_Type=attribute.Type, Requirements_Attributes_AboutCriteria=attribute.AboutCriteria, Requirements_Attributes_ComplementaryCriteria=attribute.ComplementaryCriteria, - Status=finding.status, - StatusExtended=finding.status_extended, + Status=row_status, + StatusExtended=row_status_extended, ResourceId=finding.resource_uid, ResourceName=finding.resource_name, CheckId=finding.check_id, diff --git a/prowler/lib/outputs/compliance/c5/c5_azure.py b/prowler/lib/outputs/compliance/c5/c5_azure.py index 1b3a9e6b90f..0899ff6b15e 100644 --- a/prowler/lib/outputs/compliance/c5/c5_azure.py +++ b/prowler/lib/outputs/compliance/c5/c5_azure.py @@ -1,4 +1,8 @@ from prowler.config.config import timestamp +from prowler.lib.check.compliance_config_eval import ( + apply_config_status, + build_requirement_config_status, +) from prowler.lib.check.compliance_models import Compliance from prowler.lib.outputs.compliance.c5.models import AzureC5Model from prowler.lib.outputs.compliance.compliance_output import ComplianceOutput @@ -34,10 +38,19 @@ def transform( Returns: - None """ + requirement_config_status = build_requirement_config_status( + compliance.Requirements + ) + for finding in findings: for requirement in compliance.Requirements: # Source of truth: framework JSON, not finding.compliance snapshot (avoids CSV/UI count drift). if finding.check_id in requirement.Checks: + row_status, row_status_extended = apply_config_status( + finding.status, + finding.status_extended, + requirement_config_status.get(requirement.Id), + ) for attribute in requirement.Attributes: compliance_row = AzureC5Model( Provider=finding.provider, @@ -52,8 +65,8 @@ def transform( Requirements_Attributes_Type=attribute.Type, Requirements_Attributes_AboutCriteria=attribute.AboutCriteria, Requirements_Attributes_ComplementaryCriteria=attribute.ComplementaryCriteria, - Status=finding.status, - StatusExtended=finding.status_extended, + Status=row_status, + StatusExtended=row_status_extended, ResourceId=finding.resource_uid, ResourceName=finding.resource_name, CheckId=finding.check_id, diff --git a/prowler/lib/outputs/compliance/c5/c5_gcp.py b/prowler/lib/outputs/compliance/c5/c5_gcp.py index 84e66a141f8..c8b874f622f 100644 --- a/prowler/lib/outputs/compliance/c5/c5_gcp.py +++ b/prowler/lib/outputs/compliance/c5/c5_gcp.py @@ -1,4 +1,8 @@ from prowler.config.config import timestamp +from prowler.lib.check.compliance_config_eval import ( + apply_config_status, + build_requirement_config_status, +) from prowler.lib.check.compliance_models import Compliance from prowler.lib.outputs.compliance.c5.models import GCPC5Model from prowler.lib.outputs.compliance.compliance_output import ComplianceOutput @@ -34,10 +38,19 @@ def transform( Returns: - None """ + requirement_config_status = build_requirement_config_status( + compliance.Requirements + ) + for finding in findings: for requirement in compliance.Requirements: # Source of truth: framework JSON, not finding.compliance snapshot (avoids CSV/UI count drift). if finding.check_id in requirement.Checks: + row_status, row_status_extended = apply_config_status( + finding.status, + finding.status_extended, + requirement_config_status.get(requirement.Id), + ) for attribute in requirement.Attributes: compliance_row = GCPC5Model( Provider=finding.provider, @@ -52,8 +65,8 @@ def transform( Requirements_Attributes_Type=attribute.Type, Requirements_Attributes_AboutCriteria=attribute.AboutCriteria, Requirements_Attributes_ComplementaryCriteria=attribute.ComplementaryCriteria, - Status=finding.status, - StatusExtended=finding.status_extended, + Status=row_status, + StatusExtended=row_status_extended, ResourceId=finding.resource_uid, ResourceName=finding.resource_name, CheckId=finding.check_id, diff --git a/prowler/lib/outputs/compliance/ccc/ccc.py b/prowler/lib/outputs/compliance/ccc/ccc.py index d8d76ad2d95..48b2086e781 100644 --- a/prowler/lib/outputs/compliance/ccc/ccc.py +++ b/prowler/lib/outputs/compliance/ccc/ccc.py @@ -2,6 +2,13 @@ from tabulate import tabulate from prowler.config.config import orange_color +from prowler.lib.check.compliance_config_eval import ( + accumulate_group_status, + accumulate_overview_status, + get_effective_status, + get_scan_audit_config, + resolve_requirement_config_status, +) def get_ccc_table( @@ -18,12 +25,14 @@ def get_ccc_table( "Status": [], "Muted": [], } - pass_count = [] - fail_count = [] - muted_count = [] + pass_count = set() + fail_count = set() + muted_count = set() sections = {} section_seen = {} provider = "" + audit_config = get_scan_audit_config() + config_status_cache = {} for index, finding in enumerate(findings): check = bulk_checks_metadata[finding.check_metadata.CheckID] check_compliances = check.Compliance @@ -31,34 +40,26 @@ def get_ccc_table( if compliance.Framework == "CCC": provider = compliance.Provider for requirement in compliance.Requirements: + config_status = resolve_requirement_config_status( + requirement, audit_config, config_status_cache + ) + effective_status = get_effective_status( + finding.status, config_status + ) for attribute in requirement.Attributes: section = attribute.Section if section not in sections: sections[section] = {"FAIL": 0, "PASS": 0, "Muted": 0} - section_seen[section] = set() - - # Overview totals: count each finding once per framework - if finding.muted: - if index not in muted_count: - muted_count.append(index) - elif finding.status == "FAIL": - if index not in fail_count: - fail_count.append(index) - elif finding.status == "PASS": - if index not in pass_count: - pass_count.append(index) + section_seen[section] = {} - # Per-section counts: count each finding once per section - # it belongs to (a finding can map to several sections). - if index not in section_seen[section]: - section_seen[section].add(index) - if finding.muted: - sections[section]["Muted"] += 1 - elif finding.status == "FAIL": - sections[section]["FAIL"] += 1 - elif finding.status == "PASS": - sections[section]["PASS"] += 1 + status = "Muted" if finding.muted else effective_status + accumulate_overview_status( + index, status, pass_count, fail_count, muted_count + ) + accumulate_group_status( + index, status, sections[section], section_seen[section] + ) sections = dict(sorted(sections.items())) for section in sections: diff --git a/prowler/lib/outputs/compliance/ccc/ccc_aws.py b/prowler/lib/outputs/compliance/ccc/ccc_aws.py index 11144255740..20d42b15083 100644 --- a/prowler/lib/outputs/compliance/ccc/ccc_aws.py +++ b/prowler/lib/outputs/compliance/ccc/ccc_aws.py @@ -1,4 +1,8 @@ from prowler.config.config import timestamp +from prowler.lib.check.compliance_config_eval import ( + apply_config_status, + build_requirement_config_status, +) from prowler.lib.check.compliance_models import Compliance from prowler.lib.outputs.compliance.ccc.models import CCC_AWSModel from prowler.lib.outputs.compliance.compliance_output import ComplianceOutput @@ -34,10 +38,19 @@ def transform( Returns: - None """ + requirement_config_status = build_requirement_config_status( + compliance.Requirements + ) + for finding in findings: for requirement in compliance.Requirements: # Source of truth: framework JSON, not finding.compliance snapshot (avoids CSV/UI count drift). if finding.check_id in requirement.Checks: + row_status, row_status_extended = apply_config_status( + finding.status, + finding.status_extended, + requirement_config_status.get(requirement.Id), + ) for attribute in requirement.Attributes: compliance_row = CCC_AWSModel( Provider=finding.provider, @@ -56,8 +69,8 @@ def transform( Requirements_Attributes_Recommendation=attribute.Recommendation, Requirements_Attributes_SectionThreatMappings=attribute.SectionThreatMappings, Requirements_Attributes_SectionGuidelineMappings=attribute.SectionGuidelineMappings, - Status=finding.status, - StatusExtended=finding.status_extended, + Status=row_status, + StatusExtended=row_status_extended, ResourceId=finding.resource_uid, ResourceName=finding.resource_name, CheckId=finding.check_id, diff --git a/prowler/lib/outputs/compliance/ccc/ccc_azure.py b/prowler/lib/outputs/compliance/ccc/ccc_azure.py index e0cdf323308..4dcc8c5ca80 100644 --- a/prowler/lib/outputs/compliance/ccc/ccc_azure.py +++ b/prowler/lib/outputs/compliance/ccc/ccc_azure.py @@ -1,4 +1,8 @@ from prowler.config.config import timestamp +from prowler.lib.check.compliance_config_eval import ( + apply_config_status, + build_requirement_config_status, +) from prowler.lib.check.compliance_models import Compliance from prowler.lib.outputs.compliance.ccc.models import CCC_AzureModel from prowler.lib.outputs.compliance.compliance_output import ComplianceOutput @@ -34,10 +38,19 @@ def transform( Returns: - None """ + requirement_config_status = build_requirement_config_status( + compliance.Requirements + ) + for finding in findings: for requirement in compliance.Requirements: # Source of truth: framework JSON, not finding.compliance snapshot (avoids CSV/UI count drift). if finding.check_id in requirement.Checks: + row_status, row_status_extended = apply_config_status( + finding.status, + finding.status_extended, + requirement_config_status.get(requirement.Id), + ) for attribute in requirement.Attributes: compliance_row = CCC_AzureModel( Provider=finding.provider, @@ -56,8 +69,8 @@ def transform( Requirements_Attributes_Recommendation=attribute.Recommendation, Requirements_Attributes_SectionThreatMappings=attribute.SectionThreatMappings, Requirements_Attributes_SectionGuidelineMappings=attribute.SectionGuidelineMappings, - Status=finding.status, - StatusExtended=finding.status_extended, + Status=row_status, + StatusExtended=row_status_extended, ResourceId=finding.resource_uid, ResourceName=finding.resource_name, CheckId=finding.check_id, diff --git a/prowler/lib/outputs/compliance/ccc/ccc_gcp.py b/prowler/lib/outputs/compliance/ccc/ccc_gcp.py index 326a15e5c35..ed7c709c248 100644 --- a/prowler/lib/outputs/compliance/ccc/ccc_gcp.py +++ b/prowler/lib/outputs/compliance/ccc/ccc_gcp.py @@ -1,4 +1,8 @@ from prowler.config.config import timestamp +from prowler.lib.check.compliance_config_eval import ( + apply_config_status, + build_requirement_config_status, +) from prowler.lib.check.compliance_models import Compliance from prowler.lib.outputs.compliance.ccc.models import CCC_GCPModel from prowler.lib.outputs.compliance.compliance_output import ComplianceOutput @@ -34,10 +38,19 @@ def transform( Returns: - None """ + requirement_config_status = build_requirement_config_status( + compliance.Requirements + ) + for finding in findings: for requirement in compliance.Requirements: # Source of truth: framework JSON, not finding.compliance snapshot (avoids CSV/UI count drift). if finding.check_id in requirement.Checks: + row_status, row_status_extended = apply_config_status( + finding.status, + finding.status_extended, + requirement_config_status.get(requirement.Id), + ) for attribute in requirement.Attributes: compliance_row = CCC_GCPModel( Provider=finding.provider, @@ -56,8 +69,8 @@ def transform( Requirements_Attributes_Recommendation=attribute.Recommendation, Requirements_Attributes_SectionThreatMappings=attribute.SectionThreatMappings, Requirements_Attributes_SectionGuidelineMappings=attribute.SectionGuidelineMappings, - Status=finding.status, - StatusExtended=finding.status_extended, + Status=row_status, + StatusExtended=row_status_extended, ResourceId=finding.resource_uid, ResourceName=finding.resource_name, CheckId=finding.check_id, diff --git a/prowler/lib/outputs/compliance/cis/cis.py b/prowler/lib/outputs/compliance/cis/cis.py index 4acbd7fe582..de3120f689b 100644 --- a/prowler/lib/outputs/compliance/cis/cis.py +++ b/prowler/lib/outputs/compliance/cis/cis.py @@ -2,6 +2,13 @@ from tabulate import tabulate from prowler.config.config import orange_color +from prowler.lib.check.compliance_config_eval import ( + accumulate_group_status, + accumulate_overview_status, + get_effective_status, + get_scan_audit_config, + resolve_requirement_config_status, +) def get_cis_table( @@ -23,9 +30,11 @@ def get_cis_table( "Level 2": [], "Muted": [], } - pass_count = [] - fail_count = [] - muted_count = [] + pass_count = set() + fail_count = set() + muted_count = set() + audit_config = get_scan_audit_config() + config_status_cache = {} for index, finding in enumerate(findings): check = bulk_checks_metadata[finding.check_metadata.CheckID] check_compliances = check.Compliance @@ -34,6 +43,12 @@ def get_cis_table( if compliance.Framework == "CIS" and version_in_name in compliance.Version: provider = compliance.Provider for requirement in compliance.Requirements: + config_status = resolve_requirement_config_status( + requirement, audit_config, config_status_cache + ) + effective_status = get_effective_status( + finding.status, config_status + ) for attribute in requirement.Attributes: section = attribute.Section # Check if Section exists @@ -46,43 +61,37 @@ def get_cis_table( } section_muted_seen[section] = set() section_split_seen[section] = { - "Level 1": set(), - "Level 2": set(), + "Level 1": {}, + "Level 2": {}, } + + status = "Muted" if finding.muted else effective_status + accumulate_overview_status( + index, status, pass_count, fail_count, muted_count + ) if finding.muted: - # Overview total: count each finding once per framework - if index not in muted_count: - muted_count.append(index) # Per-section Muted: count each finding once per section # it belongs to (a finding can map to several sections). if index not in section_muted_seen[section]: section_muted_seen[section].add(index) sections[section]["Muted"] += 1 - else: - if finding.status == "FAIL" and index not in fail_count: - fail_count.append(index) - elif finding.status == "PASS" and index not in pass_count: - pass_count.append(index) + if "Level 1" in attribute.Profile: - if ( - not finding.muted - and index not in section_split_seen[section]["Level 1"] - ): - section_split_seen[section]["Level 1"].add(index) - if finding.status == "FAIL": - sections[section]["Level 1"]["FAIL"] += 1 - else: - sections[section]["Level 1"]["PASS"] += 1 + if not finding.muted: + accumulate_group_status( + index, + effective_status, + sections[section]["Level 1"], + section_split_seen[section]["Level 1"], + ) elif "Level 2" in attribute.Profile: - if ( - not finding.muted - and index not in section_split_seen[section]["Level 2"] - ): - section_split_seen[section]["Level 2"].add(index) - if finding.status == "FAIL": - sections[section]["Level 2"]["FAIL"] += 1 - else: - sections[section]["Level 2"]["PASS"] += 1 + if not finding.muted: + accumulate_group_status( + index, + effective_status, + sections[section]["Level 2"], + section_split_seen[section]["Level 2"], + ) # Add results to table sections = dict(sorted(sections.items())) diff --git a/prowler/lib/outputs/compliance/cis/cis_alibabacloud.py b/prowler/lib/outputs/compliance/cis/cis_alibabacloud.py index a1880d3af98..77bf02ee981 100644 --- a/prowler/lib/outputs/compliance/cis/cis_alibabacloud.py +++ b/prowler/lib/outputs/compliance/cis/cis_alibabacloud.py @@ -1,4 +1,8 @@ from prowler.config.config import timestamp +from prowler.lib.check.compliance_config_eval import ( + apply_config_status, + build_requirement_config_status, +) from prowler.lib.check.compliance_models import Compliance from prowler.lib.outputs.compliance.cis.models import AlibabaCloudCISModel from prowler.lib.outputs.compliance.compliance_output import ComplianceOutput @@ -34,10 +38,18 @@ def transform( Returns: - None """ + requirement_config_status = build_requirement_config_status( + compliance.Requirements + ) for finding in findings: for requirement in compliance.Requirements: # Source of truth: framework JSON, not finding.compliance snapshot (avoids CSV/UI count drift). if finding.check_id in requirement.Checks: + row_status, row_status_extended = apply_config_status( + finding.status, + finding.status_extended, + requirement_config_status.get(requirement.Id), + ) for attribute in requirement.Attributes: compliance_row = AlibabaCloudCISModel( Provider=finding.provider, @@ -59,8 +71,8 @@ def transform( Requirements_Attributes_AdditionalInformation=attribute.AdditionalInformation, Requirements_Attributes_DefaultValue=attribute.DefaultValue, Requirements_Attributes_References=attribute.References, - Status=finding.status, - StatusExtended=finding.status_extended, + Status=row_status, + StatusExtended=row_status_extended, ResourceId=finding.resource_uid, ResourceName=finding.resource_name, CheckId=finding.check_id, diff --git a/prowler/lib/outputs/compliance/cis/cis_aws.py b/prowler/lib/outputs/compliance/cis/cis_aws.py index ac0250150c1..58bf7fbc27f 100644 --- a/prowler/lib/outputs/compliance/cis/cis_aws.py +++ b/prowler/lib/outputs/compliance/cis/cis_aws.py @@ -1,4 +1,8 @@ from prowler.config.config import timestamp +from prowler.lib.check.compliance_config_eval import ( + apply_config_status, + build_requirement_config_status, +) from prowler.lib.check.compliance_models import Compliance from prowler.lib.outputs.compliance.cis.models import AWSCISModel from prowler.lib.outputs.compliance.compliance_output import ComplianceOutput @@ -34,10 +38,19 @@ def transform( Returns: - None """ + requirement_config_status = build_requirement_config_status( + compliance.Requirements + ) + for finding in findings: for requirement in compliance.Requirements: # Source of truth: framework JSON, not finding.compliance snapshot (avoids CSV/UI count drift). if finding.check_id in requirement.Checks: + row_status, row_status_extended = apply_config_status( + finding.status, + finding.status_extended, + requirement_config_status.get(requirement.Id), + ) for attribute in requirement.Attributes: compliance_row = AWSCISModel( Provider=finding.provider, @@ -59,8 +72,8 @@ def transform( Requirements_Attributes_AdditionalInformation=attribute.AdditionalInformation, Requirements_Attributes_DefaultValue=attribute.DefaultValue, Requirements_Attributes_References=attribute.References, - Status=finding.status, - StatusExtended=finding.status_extended, + Status=row_status, + StatusExtended=row_status_extended, ResourceId=finding.resource_uid, ResourceName=finding.resource_name, CheckId=finding.check_id, diff --git a/prowler/lib/outputs/compliance/cis/cis_azure.py b/prowler/lib/outputs/compliance/cis/cis_azure.py index b1e09ca453d..53b1cbdd329 100644 --- a/prowler/lib/outputs/compliance/cis/cis_azure.py +++ b/prowler/lib/outputs/compliance/cis/cis_azure.py @@ -1,4 +1,8 @@ from prowler.config.config import timestamp +from prowler.lib.check.compliance_config_eval import ( + apply_config_status, + build_requirement_config_status, +) from prowler.lib.check.compliance_models import Compliance from prowler.lib.outputs.compliance.cis.models import AzureCISModel from prowler.lib.outputs.compliance.compliance_output import ComplianceOutput @@ -34,10 +38,18 @@ def transform( Returns: - None """ + requirement_config_status = build_requirement_config_status( + compliance.Requirements + ) for finding in findings: for requirement in compliance.Requirements: # Source of truth: framework JSON, not finding.compliance snapshot (avoids CSV/UI count drift). if finding.check_id in requirement.Checks: + row_status, row_status_extended = apply_config_status( + finding.status, + finding.status_extended, + requirement_config_status.get(requirement.Id), + ) for attribute in requirement.Attributes: compliance_row = AzureCISModel( Provider=finding.provider, @@ -59,8 +71,8 @@ def transform( Requirements_Attributes_AdditionalInformation=attribute.AdditionalInformation, Requirements_Attributes_DefaultValue=attribute.DefaultValue, Requirements_Attributes_References=attribute.References, - Status=finding.status, - StatusExtended=finding.status_extended, + Status=row_status, + StatusExtended=row_status_extended, ResourceId=finding.resource_uid, ResourceName=finding.resource_name, CheckId=finding.check_id, diff --git a/prowler/lib/outputs/compliance/cis/cis_gcp.py b/prowler/lib/outputs/compliance/cis/cis_gcp.py index b2889333beb..c2a11dae77a 100644 --- a/prowler/lib/outputs/compliance/cis/cis_gcp.py +++ b/prowler/lib/outputs/compliance/cis/cis_gcp.py @@ -1,4 +1,8 @@ from prowler.config.config import timestamp +from prowler.lib.check.compliance_config_eval import ( + apply_config_status, + build_requirement_config_status, +) from prowler.lib.check.compliance_models import Compliance from prowler.lib.outputs.compliance.cis.models import GCPCISModel from prowler.lib.outputs.compliance.compliance_output import ComplianceOutput @@ -34,10 +38,18 @@ def transform( Returns: - None """ + requirement_config_status = build_requirement_config_status( + compliance.Requirements + ) for finding in findings: for requirement in compliance.Requirements: # Source of truth: framework JSON, not finding.compliance snapshot (avoids CSV/UI count drift). if finding.check_id in requirement.Checks: + row_status, row_status_extended = apply_config_status( + finding.status, + finding.status_extended, + requirement_config_status.get(requirement.Id), + ) for attribute in requirement.Attributes: compliance_row = GCPCISModel( Provider=finding.provider, @@ -58,8 +70,8 @@ def transform( Requirements_Attributes_AuditProcedure=attribute.AuditProcedure, Requirements_Attributes_AdditionalInformation=attribute.AdditionalInformation, Requirements_Attributes_References=attribute.References, - Status=finding.status, - StatusExtended=finding.status_extended, + Status=row_status, + StatusExtended=row_status_extended, ResourceId=finding.resource_uid, ResourceName=finding.resource_name, CheckId=finding.check_id, diff --git a/prowler/lib/outputs/compliance/cis/cis_github.py b/prowler/lib/outputs/compliance/cis/cis_github.py index 2ce5b644801..039bd1b9938 100644 --- a/prowler/lib/outputs/compliance/cis/cis_github.py +++ b/prowler/lib/outputs/compliance/cis/cis_github.py @@ -1,4 +1,8 @@ from prowler.config.config import timestamp +from prowler.lib.check.compliance_config_eval import ( + apply_config_status, + build_requirement_config_status, +) from prowler.lib.check.compliance_models import Compliance from prowler.lib.outputs.compliance.cis.models import GithubCISModel from prowler.lib.outputs.compliance.compliance_output import ComplianceOutput @@ -34,10 +38,18 @@ def transform( Returns: - None """ + requirement_config_status = build_requirement_config_status( + compliance.Requirements + ) for finding in findings: for requirement in compliance.Requirements: # Source of truth: framework JSON, not finding.compliance snapshot (avoids CSV/UI count drift). if finding.check_id in requirement.Checks: + row_status, row_status_extended = apply_config_status( + finding.status, + finding.status_extended, + requirement_config_status.get(requirement.Id), + ) for attribute in requirement.Attributes: compliance_row = GithubCISModel( Provider=finding.provider, @@ -58,8 +70,8 @@ def transform( Requirements_Attributes_AdditionalInformation=attribute.AdditionalInformation, Requirements_Attributes_References=attribute.References, Requirements_Attributes_DefaultValue=attribute.DefaultValue, - Status=finding.status, - StatusExtended=finding.status_extended, + Status=row_status, + StatusExtended=row_status_extended, ResourceId=finding.resource_uid, ResourceName=finding.resource_name, CheckId=finding.check_id, diff --git a/prowler/lib/outputs/compliance/cis/cis_googleworkspace.py b/prowler/lib/outputs/compliance/cis/cis_googleworkspace.py index d74375bf855..cf2d3755c72 100644 --- a/prowler/lib/outputs/compliance/cis/cis_googleworkspace.py +++ b/prowler/lib/outputs/compliance/cis/cis_googleworkspace.py @@ -1,4 +1,8 @@ from prowler.config.config import timestamp +from prowler.lib.check.compliance_config_eval import ( + apply_config_status, + build_requirement_config_status, +) from prowler.lib.check.compliance_models import Compliance from prowler.lib.outputs.compliance.cis.models import GoogleWorkspaceCISModel from prowler.lib.outputs.compliance.compliance_output import ComplianceOutput @@ -34,10 +38,18 @@ def transform( Returns: - None """ + requirement_config_status = build_requirement_config_status( + compliance.Requirements + ) for finding in findings: for requirement in compliance.Requirements: # Source of truth: framework JSON, not finding.compliance snapshot (avoids CSV/UI count drift). if finding.check_id in requirement.Checks: + row_status, row_status_extended = apply_config_status( + finding.status, + finding.status_extended, + requirement_config_status.get(requirement.Id), + ) for attribute in requirement.Attributes: compliance_row = GoogleWorkspaceCISModel( Provider=finding.provider, @@ -58,8 +70,8 @@ def transform( Requirements_Attributes_AdditionalInformation=attribute.AdditionalInformation, Requirements_Attributes_DefaultValue=attribute.DefaultValue, Requirements_Attributes_References=attribute.References, - Status=finding.status, - StatusExtended=finding.status_extended, + Status=row_status, + StatusExtended=row_status_extended, ResourceId=finding.resource_uid, ResourceName=finding.resource_name, CheckId=finding.check_id, diff --git a/prowler/lib/outputs/compliance/cis/cis_kubernetes.py b/prowler/lib/outputs/compliance/cis/cis_kubernetes.py index 9607123e443..23c482310e1 100644 --- a/prowler/lib/outputs/compliance/cis/cis_kubernetes.py +++ b/prowler/lib/outputs/compliance/cis/cis_kubernetes.py @@ -1,4 +1,8 @@ from prowler.config.config import timestamp +from prowler.lib.check.compliance_config_eval import ( + apply_config_status, + build_requirement_config_status, +) from prowler.lib.check.compliance_models import Compliance from prowler.lib.outputs.compliance.cis.models import KubernetesCISModel from prowler.lib.outputs.compliance.compliance_output import ComplianceOutput @@ -34,10 +38,18 @@ def transform( Returns: - None """ + requirement_config_status = build_requirement_config_status( + compliance.Requirements + ) for finding in findings: for requirement in compliance.Requirements: # Source of truth: framework JSON, not finding.compliance snapshot (avoids CSV/UI count drift). if finding.check_id in requirement.Checks: + row_status, row_status_extended = apply_config_status( + finding.status, + finding.status_extended, + requirement_config_status.get(requirement.Id), + ) for attribute in requirement.Attributes: compliance_row = KubernetesCISModel( Provider=finding.provider, @@ -59,8 +71,8 @@ def transform( Requirements_Attributes_AdditionalInformation=attribute.AdditionalInformation, Requirements_Attributes_References=attribute.References, Requirements_Attributes_DefaultValue=attribute.DefaultValue, - Status=finding.status, - StatusExtended=finding.status_extended, + Status=row_status, + StatusExtended=row_status_extended, ResourceId=finding.resource_uid, ResourceName=finding.resource_name, CheckId=finding.check_id, diff --git a/prowler/lib/outputs/compliance/cis/cis_m365.py b/prowler/lib/outputs/compliance/cis/cis_m365.py index 1a001669463..8dc06155e7c 100644 --- a/prowler/lib/outputs/compliance/cis/cis_m365.py +++ b/prowler/lib/outputs/compliance/cis/cis_m365.py @@ -1,4 +1,8 @@ from prowler.config.config import timestamp +from prowler.lib.check.compliance_config_eval import ( + apply_config_status, + build_requirement_config_status, +) from prowler.lib.check.compliance_models import Compliance from prowler.lib.outputs.compliance.cis.models import M365CISModel from prowler.lib.outputs.compliance.compliance_output import ComplianceOutput @@ -34,10 +38,18 @@ def transform( Returns: - None """ + requirement_config_status = build_requirement_config_status( + compliance.Requirements + ) for finding in findings: for requirement in compliance.Requirements: # Source of truth: framework JSON, not finding.compliance snapshot (avoids CSV/UI count drift). if finding.check_id in requirement.Checks: + row_status, row_status_extended = apply_config_status( + finding.status, + finding.status_extended, + requirement_config_status.get(requirement.Id), + ) for attribute in requirement.Attributes: compliance_row = M365CISModel( Provider=finding.provider, @@ -59,8 +71,8 @@ def transform( Requirements_Attributes_AdditionalInformation=attribute.AdditionalInformation, Requirements_Attributes_DefaultValue=attribute.DefaultValue, Requirements_Attributes_References=attribute.References, - Status=finding.status, - StatusExtended=finding.status_extended, + Status=row_status, + StatusExtended=row_status_extended, ResourceId=finding.resource_uid, ResourceName=finding.resource_name, CheckId=finding.check_id, diff --git a/prowler/lib/outputs/compliance/cis/cis_oraclecloud.py b/prowler/lib/outputs/compliance/cis/cis_oraclecloud.py index 117c3ca0047..d8110d72db5 100644 --- a/prowler/lib/outputs/compliance/cis/cis_oraclecloud.py +++ b/prowler/lib/outputs/compliance/cis/cis_oraclecloud.py @@ -1,4 +1,8 @@ from prowler.config.config import timestamp +from prowler.lib.check.compliance_config_eval import ( + apply_config_status, + build_requirement_config_status, +) from prowler.lib.check.compliance_models import Compliance from prowler.lib.outputs.compliance.cis.models import OracleCloudCISModel from prowler.lib.outputs.compliance.compliance_output import ComplianceOutput @@ -34,10 +38,18 @@ def transform( Returns: - None """ + requirement_config_status = build_requirement_config_status( + compliance.Requirements + ) for finding in findings: for requirement in compliance.Requirements: # Source of truth: framework JSON, not finding.compliance snapshot (avoids CSV/UI count drift). if finding.check_id in requirement.Checks: + row_status, row_status_extended = apply_config_status( + finding.status, + finding.status_extended, + requirement_config_status.get(requirement.Id), + ) for attribute in requirement.Attributes: compliance_row = OracleCloudCISModel( Provider=finding.provider, @@ -59,8 +71,8 @@ def transform( Requirements_Attributes_AdditionalInformation=attribute.AdditionalInformation, Requirements_Attributes_DefaultValue=attribute.DefaultValue, Requirements_Attributes_References=attribute.References, - Status=finding.status, - StatusExtended=finding.status_extended, + Status=row_status, + StatusExtended=row_status_extended, ResourceId=finding.resource_uid, ResourceName=finding.resource_name, CheckId=finding.check_id, diff --git a/prowler/lib/outputs/compliance/cisa_scuba/cisa_scuba_googleworkspace.py b/prowler/lib/outputs/compliance/cisa_scuba/cisa_scuba_googleworkspace.py index 9f1336e8326..d2f6faa2125 100644 --- a/prowler/lib/outputs/compliance/cisa_scuba/cisa_scuba_googleworkspace.py +++ b/prowler/lib/outputs/compliance/cisa_scuba/cisa_scuba_googleworkspace.py @@ -1,4 +1,8 @@ from prowler.config.config import timestamp +from prowler.lib.check.compliance_config_eval import ( + apply_config_status, + build_requirement_config_status, +) from prowler.lib.check.compliance_models import Compliance from prowler.lib.outputs.compliance.cisa_scuba.models import ( GoogleWorkspaceCISASCuBAModel, @@ -36,10 +40,18 @@ def transform( Returns: - None """ + requirement_config_status = build_requirement_config_status( + compliance.Requirements + ) for finding in findings: for requirement in compliance.Requirements: # Source of truth: framework JSON, not finding.compliance snapshot (avoids CSV/UI count drift). if finding.check_id in requirement.Checks: + row_status, row_status_extended = apply_config_status( + finding.status, + finding.status_extended, + requirement_config_status.get(requirement.Id), + ) for attribute in requirement.Attributes: compliance_row = GoogleWorkspaceCISASCuBAModel( Provider=finding.provider, @@ -52,8 +64,8 @@ def transform( Requirements_Attributes_SubSection=attribute.SubSection, Requirements_Attributes_Service=attribute.Service, Requirements_Attributes_Type=attribute.Type, - Status=finding.status, - StatusExtended=finding.status_extended, + Status=row_status, + StatusExtended=row_status_extended, ResourceId=finding.resource_uid, ResourceName=finding.resource_name, CheckId=finding.check_id, diff --git a/prowler/lib/outputs/compliance/ens/ens.py b/prowler/lib/outputs/compliance/ens/ens.py index c39abe0a6a6..4fcca922c74 100644 --- a/prowler/lib/outputs/compliance/ens/ens.py +++ b/prowler/lib/outputs/compliance/ens/ens.py @@ -2,6 +2,11 @@ from tabulate import tabulate from prowler.config.config import orange_color +from prowler.lib.check.compliance_config_eval import ( + get_effective_status, + get_scan_audit_config, + resolve_requirement_config_status, +) def get_ens_table( @@ -25,9 +30,11 @@ def get_ens_table( "Opcional": [], "Muted": [], } - pass_count = [] - fail_count = [] - muted_count = [] + pass_count = set() + fail_count = set() + muted_count = set() + audit_config = get_scan_audit_config() + config_status_cache = {} for index, finding in enumerate(findings): check = bulk_checks_metadata[finding.check_metadata.CheckID] check_compliances = check.Compliance @@ -35,6 +42,12 @@ def get_ens_table( if compliance.Framework == "ENS": provider = compliance.Provider for requirement in compliance.Requirements: + config_status = resolve_requirement_config_status( + requirement, audit_config, config_status_cache + ) + effective_status = get_effective_status( + finding.status, config_status + ) for attribute in requirement.Attributes: marco_categoria = f"{attribute.Marco}/{attribute.Categoria}" # Check if Marco/Categoria exists @@ -50,25 +63,24 @@ def get_ens_table( marco_muted_seen[marco_categoria] = set() if finding.muted: # Overview total: count each finding once per framework - if index not in muted_count: - muted_count.append(index) + muted_count.add(index) # Per-marco Muted: count each finding once per marco # it belongs to (a finding can map to several marcos). if index not in marco_muted_seen[marco_categoria]: marco_muted_seen[marco_categoria].add(index) marcos[marco_categoria]["Muted"] += 1 else: - if finding.status == "FAIL": + if effective_status == "FAIL": if attribute.Tipo != "recomendacion": - if index not in fail_count: - fail_count.append(index) + fail_count.add(index) + pass_count.discard(index) # Mark every marco the finding belongs to as # NO CUMPLE, not just the first one seen. marcos[marco_categoria][ "Estado" ] = f"{Fore.RED}NO CUMPLE{Style.RESET_ALL}" - elif finding.status == "PASS" and index not in pass_count: - pass_count.append(index) + elif effective_status == "PASS" and index not in fail_count: + pass_count.add(index) if attribute.Nivel == "opcional": marcos[marco_categoria]["Opcional"] += 1 elif attribute.Nivel == "alto": diff --git a/prowler/lib/outputs/compliance/ens/ens_aws.py b/prowler/lib/outputs/compliance/ens/ens_aws.py index 40d185294b4..543799f7b9b 100644 --- a/prowler/lib/outputs/compliance/ens/ens_aws.py +++ b/prowler/lib/outputs/compliance/ens/ens_aws.py @@ -1,4 +1,8 @@ from prowler.config.config import timestamp +from prowler.lib.check.compliance_config_eval import ( + apply_config_status, + build_requirement_config_status, +) from prowler.lib.check.compliance_models import Compliance from prowler.lib.outputs.compliance.compliance_output import ComplianceOutput from prowler.lib.outputs.compliance.ens.models import AWSENSModel @@ -34,10 +38,19 @@ def transform( Returns: - None """ + requirement_config_status = build_requirement_config_status( + compliance.Requirements + ) + for finding in findings: for requirement in compliance.Requirements: # Source of truth: framework JSON, not finding.compliance snapshot (avoids CSV/UI count drift). if finding.check_id in requirement.Checks: + row_status, row_status_extended = apply_config_status( + finding.status, + finding.status_extended, + requirement_config_status.get(requirement.Id), + ) for attribute in requirement.Attributes: compliance_row = AWSENSModel( Provider=finding.provider, @@ -60,8 +73,8 @@ def transform( Requirements_Attributes_Dependencias=",".join( attribute.Dependencias ), - Status=finding.status, - StatusExtended=finding.status_extended, + Status=row_status, + StatusExtended=row_status_extended, ResourceId=finding.resource_uid, ResourceName=finding.resource_name, CheckId=finding.check_id, diff --git a/prowler/lib/outputs/compliance/ens/ens_azure.py b/prowler/lib/outputs/compliance/ens/ens_azure.py index 20d727fed0a..23055da2a2d 100644 --- a/prowler/lib/outputs/compliance/ens/ens_azure.py +++ b/prowler/lib/outputs/compliance/ens/ens_azure.py @@ -1,4 +1,8 @@ from prowler.config.config import timestamp +from prowler.lib.check.compliance_config_eval import ( + apply_config_status, + build_requirement_config_status, +) from prowler.lib.check.compliance_models import Compliance from prowler.lib.outputs.compliance.compliance_output import ComplianceOutput from prowler.lib.outputs.compliance.ens.models import AzureENSModel @@ -34,10 +38,19 @@ def transform( Returns: - None """ + requirement_config_status = build_requirement_config_status( + compliance.Requirements + ) + for finding in findings: for requirement in compliance.Requirements: # Source of truth: framework JSON, not finding.compliance snapshot (avoids CSV/UI count drift). if finding.check_id in requirement.Checks: + row_status, row_status_extended = apply_config_status( + finding.status, + finding.status_extended, + requirement_config_status.get(requirement.Id), + ) for attribute in requirement.Attributes: compliance_row = AzureENSModel( Provider=finding.provider, @@ -60,8 +73,8 @@ def transform( Requirements_Attributes_Dependencias=",".join( attribute.Dependencias ), - Status=finding.status, - StatusExtended=finding.status_extended, + Status=row_status, + StatusExtended=row_status_extended, ResourceId=finding.resource_uid, ResourceName=finding.resource_name, CheckId=finding.check_id, diff --git a/prowler/lib/outputs/compliance/ens/ens_gcp.py b/prowler/lib/outputs/compliance/ens/ens_gcp.py index 8a2baaca664..f616e48f831 100644 --- a/prowler/lib/outputs/compliance/ens/ens_gcp.py +++ b/prowler/lib/outputs/compliance/ens/ens_gcp.py @@ -1,4 +1,8 @@ from prowler.config.config import timestamp +from prowler.lib.check.compliance_config_eval import ( + apply_config_status, + build_requirement_config_status, +) from prowler.lib.check.compliance_models import Compliance from prowler.lib.outputs.compliance.compliance_output import ComplianceOutput from prowler.lib.outputs.compliance.ens.models import GCPENSModel @@ -34,10 +38,19 @@ def transform( Returns: - None """ + requirement_config_status = build_requirement_config_status( + compliance.Requirements + ) + for finding in findings: for requirement in compliance.Requirements: # Source of truth: framework JSON, not finding.compliance snapshot (avoids CSV/UI count drift). if finding.check_id in requirement.Checks: + row_status, row_status_extended = apply_config_status( + finding.status, + finding.status_extended, + requirement_config_status.get(requirement.Id), + ) for attribute in requirement.Attributes: compliance_row = GCPENSModel( Provider=finding.provider, @@ -60,8 +73,8 @@ def transform( Requirements_Attributes_Dependencias=",".join( attribute.Dependencias ), - Status=finding.status, - StatusExtended=finding.status_extended, + Status=row_status, + StatusExtended=row_status_extended, ResourceId=finding.resource_uid, ResourceName=finding.resource_name, CheckId=finding.check_id, diff --git a/prowler/lib/outputs/compliance/generic/generic.py b/prowler/lib/outputs/compliance/generic/generic.py index b774f09577a..b3f1ad7ec94 100644 --- a/prowler/lib/outputs/compliance/generic/generic.py +++ b/prowler/lib/outputs/compliance/generic/generic.py @@ -1,4 +1,8 @@ from prowler.config.config import timestamp +from prowler.lib.check.compliance_config_eval import ( + apply_config_status, + build_requirement_config_status, +) from prowler.lib.check.compliance_models import Compliance from prowler.lib.outputs.compliance.compliance_output import ComplianceOutput from prowler.lib.outputs.compliance.generic.models import GenericComplianceModel @@ -35,11 +39,24 @@ def transform( - None """ + requirement_config_status = build_requirement_config_status( + compliance.Requirements + ) + def compliance_row(requirement, attribute, finding=None): # Read attribute fields defensively: GenericCompliance is the # last-resort renderer for any framework, and provider-specific # schemas (e.g. CIS, ENS, ISO27001) do not declare the universal # Section/SubSection/SubGroup/Service/Type/Comment fields. + status, status_extended = ( + apply_config_status( + finding.status, + finding.status_extended, + requirement_config_status.get(requirement.Id), + ) + if finding + else ("MANUAL", "Manual check") + ) return GenericComplianceModel( Provider=(finding.provider if finding else compliance.Provider.lower()), Description=compliance.Description, @@ -56,8 +73,8 @@ def compliance_row(requirement, attribute, finding=None): Requirements_Attributes_Service=getattr(attribute, "Service", None), Requirements_Attributes_Type=getattr(attribute, "Type", None), Requirements_Attributes_Comment=getattr(attribute, "Comment", None), - Status=finding.status if finding else "MANUAL", - StatusExtended=(finding.status_extended if finding else "Manual check"), + Status=status, + StatusExtended=status_extended, ResourceId=finding.resource_uid if finding else "manual_check", ResourceName=finding.resource_name if finding else "Manual check", CheckId=finding.check_id if finding else "manual", diff --git a/prowler/lib/outputs/compliance/generic/generic_table.py b/prowler/lib/outputs/compliance/generic/generic_table.py index 9136cf19e2f..97e54088388 100644 --- a/prowler/lib/outputs/compliance/generic/generic_table.py +++ b/prowler/lib/outputs/compliance/generic/generic_table.py @@ -2,6 +2,11 @@ from tabulate import tabulate from prowler.config.config import orange_color +from prowler.lib.check.compliance_config_eval import ( + get_effective_status, + get_scan_audit_config, + resolve_requirement_config_status, +) def get_generic_compliance_table( @@ -15,6 +20,8 @@ def get_generic_compliance_table( pass_count = [] fail_count = [] muted_count = [] + audit_config = get_scan_audit_config() + config_status_cache = {} for index, finding in enumerate(findings): check = bulk_checks_metadata[finding.check_metadata.CheckID] check_compliances = check.Compliance @@ -25,13 +32,25 @@ def get_generic_compliance_table( and compliance.Version in compliance_framework.upper() and compliance.Provider.upper() in compliance_framework.upper() ): + effective_status = finding.status + for requirement in compliance.Requirements: + if finding.check_id in requirement.Checks: + config_status = resolve_requirement_config_status( + requirement, audit_config, config_status_cache + ) + if ( + get_effective_status(finding.status, config_status) + == "FAIL" + ): + effective_status = "FAIL" + break if finding.muted: if index not in muted_count: muted_count.append(index) else: - if finding.status == "FAIL" and index not in fail_count: + if effective_status == "FAIL" and index not in fail_count: fail_count.append(index) - elif finding.status == "PASS" and index not in pass_count: + elif effective_status == "PASS" and index not in pass_count: pass_count.append(index) if ( len(fail_count) + len(pass_count) + len(muted_count) > 1 diff --git a/prowler/lib/outputs/compliance/iso27001/iso27001_aws.py b/prowler/lib/outputs/compliance/iso27001/iso27001_aws.py index 282f27a218e..3376b8c5ade 100644 --- a/prowler/lib/outputs/compliance/iso27001/iso27001_aws.py +++ b/prowler/lib/outputs/compliance/iso27001/iso27001_aws.py @@ -1,4 +1,8 @@ from prowler.config.config import timestamp +from prowler.lib.check.compliance_config_eval import ( + apply_config_status, + build_requirement_config_status, +) from prowler.lib.check.compliance_models import Compliance from prowler.lib.outputs.compliance.compliance_output import ComplianceOutput from prowler.lib.outputs.compliance.iso27001.models import AWSISO27001Model @@ -34,10 +38,18 @@ def transform( Returns: - None """ + requirement_config_status = build_requirement_config_status( + compliance.Requirements + ) for finding in findings: for requirement in compliance.Requirements: # Source of truth: framework JSON, not finding.compliance snapshot (avoids CSV/UI count drift). if finding.check_id in requirement.Checks: + row_status, row_status_extended = apply_config_status( + finding.status, + finding.status_extended, + requirement_config_status.get(requirement.Id), + ) for attribute in requirement.Attributes: compliance_row = AWSISO27001Model( Provider=finding.provider, @@ -52,8 +64,8 @@ def transform( Requirements_Attributes_Objetive_ID=attribute.Objetive_ID, Requirements_Attributes_Objetive_Name=attribute.Objetive_Name, Requirements_Attributes_Check_Summary=attribute.Check_Summary, - Status=finding.status, - StatusExtended=finding.status_extended, + Status=row_status, + StatusExtended=row_status_extended, ResourceId=finding.resource_uid, CheckId=finding.check_id, Muted=finding.muted, diff --git a/prowler/lib/outputs/compliance/iso27001/iso27001_azure.py b/prowler/lib/outputs/compliance/iso27001/iso27001_azure.py index 0112bbd7e16..f1f964c7265 100644 --- a/prowler/lib/outputs/compliance/iso27001/iso27001_azure.py +++ b/prowler/lib/outputs/compliance/iso27001/iso27001_azure.py @@ -1,4 +1,8 @@ from prowler.config.config import timestamp +from prowler.lib.check.compliance_config_eval import ( + apply_config_status, + build_requirement_config_status, +) from prowler.lib.check.compliance_models import Compliance from prowler.lib.outputs.compliance.compliance_output import ComplianceOutput from prowler.lib.outputs.compliance.iso27001.models import AzureISO27001Model @@ -34,10 +38,18 @@ def transform( Returns: - None """ + requirement_config_status = build_requirement_config_status( + compliance.Requirements + ) for finding in findings: for requirement in compliance.Requirements: # Source of truth: framework JSON, not finding.compliance snapshot (avoids CSV/UI count drift). if finding.check_id in requirement.Checks: + row_status, row_status_extended = apply_config_status( + finding.status, + finding.status_extended, + requirement_config_status.get(requirement.Id), + ) for attribute in requirement.Attributes: compliance_row = AzureISO27001Model( Provider=finding.provider, @@ -52,8 +64,8 @@ def transform( Requirements_Attributes_Objetive_ID=attribute.Objetive_ID, Requirements_Attributes_Objetive_Name=attribute.Objetive_Name, Requirements_Attributes_Check_Summary=attribute.Check_Summary, - Status=finding.status, - StatusExtended=finding.status_extended, + Status=row_status, + StatusExtended=row_status_extended, ResourceId=finding.resource_uid, CheckId=finding.check_id, Muted=finding.muted, diff --git a/prowler/lib/outputs/compliance/iso27001/iso27001_gcp.py b/prowler/lib/outputs/compliance/iso27001/iso27001_gcp.py index f60a30e67e4..9a5de17bfab 100644 --- a/prowler/lib/outputs/compliance/iso27001/iso27001_gcp.py +++ b/prowler/lib/outputs/compliance/iso27001/iso27001_gcp.py @@ -1,4 +1,8 @@ from prowler.config.config import timestamp +from prowler.lib.check.compliance_config_eval import ( + apply_config_status, + build_requirement_config_status, +) from prowler.lib.check.compliance_models import Compliance from prowler.lib.outputs.compliance.compliance_output import ComplianceOutput from prowler.lib.outputs.compliance.iso27001.models import GCPISO27001Model @@ -34,10 +38,18 @@ def transform( Returns: - None """ + requirement_config_status = build_requirement_config_status( + compliance.Requirements + ) for finding in findings: for requirement in compliance.Requirements: # Source of truth: framework JSON, not finding.compliance snapshot (avoids CSV/UI count drift). if finding.check_id in requirement.Checks: + row_status, row_status_extended = apply_config_status( + finding.status, + finding.status_extended, + requirement_config_status.get(requirement.Id), + ) for attribute in requirement.Attributes: compliance_row = GCPISO27001Model( Provider=finding.provider, @@ -52,8 +64,8 @@ def transform( Requirements_Attributes_Objetive_ID=attribute.Objetive_ID, Requirements_Attributes_Objetive_Name=attribute.Objetive_Name, Requirements_Attributes_Check_Summary=attribute.Check_Summary, - Status=finding.status, - StatusExtended=finding.status_extended, + Status=row_status, + StatusExtended=row_status_extended, ResourceId=finding.resource_uid, CheckId=finding.check_id, Muted=finding.muted, diff --git a/prowler/lib/outputs/compliance/iso27001/iso27001_kubernetes.py b/prowler/lib/outputs/compliance/iso27001/iso27001_kubernetes.py index 59fd593ce69..5ca81e82d56 100644 --- a/prowler/lib/outputs/compliance/iso27001/iso27001_kubernetes.py +++ b/prowler/lib/outputs/compliance/iso27001/iso27001_kubernetes.py @@ -1,4 +1,8 @@ from prowler.config.config import timestamp +from prowler.lib.check.compliance_config_eval import ( + apply_config_status, + build_requirement_config_status, +) from prowler.lib.check.compliance_models import Compliance from prowler.lib.outputs.compliance.compliance_output import ComplianceOutput from prowler.lib.outputs.compliance.iso27001.models import KubernetesISO27001Model @@ -34,10 +38,18 @@ def transform( Returns: - None """ + requirement_config_status = build_requirement_config_status( + compliance.Requirements + ) for finding in findings: for requirement in compliance.Requirements: # Source of truth: framework JSON, not finding.compliance snapshot (avoids CSV/UI count drift). if finding.check_id in requirement.Checks: + row_status, row_status_extended = apply_config_status( + finding.status, + finding.status_extended, + requirement_config_status.get(requirement.Id), + ) for attribute in requirement.Attributes: compliance_row = KubernetesISO27001Model( Provider=finding.provider, @@ -52,8 +64,8 @@ def transform( Requirements_Attributes_Objetive_ID=attribute.Objetive_ID, Requirements_Attributes_Objetive_Name=attribute.Objetive_Name, Requirements_Attributes_Check_Summary=attribute.Check_Summary, - Status=finding.status, - StatusExtended=finding.status_extended, + Status=row_status, + StatusExtended=row_status_extended, ResourceId=finding.resource_uid, CheckId=finding.check_id, Muted=finding.muted, diff --git a/prowler/lib/outputs/compliance/iso27001/iso27001_m365.py b/prowler/lib/outputs/compliance/iso27001/iso27001_m365.py index cf6712a4a6a..84c50720025 100644 --- a/prowler/lib/outputs/compliance/iso27001/iso27001_m365.py +++ b/prowler/lib/outputs/compliance/iso27001/iso27001_m365.py @@ -1,4 +1,8 @@ from prowler.config.config import timestamp +from prowler.lib.check.compliance_config_eval import ( + apply_config_status, + build_requirement_config_status, +) from prowler.lib.check.compliance_models import Compliance from prowler.lib.outputs.compliance.compliance_output import ComplianceOutput from prowler.lib.outputs.compliance.iso27001.models import M365ISO27001Model @@ -34,10 +38,18 @@ def transform( Returns: - None """ + requirement_config_status = build_requirement_config_status( + compliance.Requirements + ) for finding in findings: for requirement in compliance.Requirements: # Source of truth: framework JSON, not finding.compliance snapshot (avoids CSV/UI count drift). if finding.check_id in requirement.Checks: + row_status, row_status_extended = apply_config_status( + finding.status, + finding.status_extended, + requirement_config_status.get(requirement.Id), + ) for attribute in requirement.Attributes: compliance_row = M365ISO27001Model( Provider=finding.provider, @@ -52,8 +64,8 @@ def transform( Requirements_Attributes_Objetive_ID=attribute.Objetive_ID, Requirements_Attributes_Objetive_Name=attribute.Objetive_Name, Requirements_Attributes_Check_Summary=attribute.Check_Summary, - Status=finding.status, - StatusExtended=finding.status_extended, + Status=row_status, + StatusExtended=row_status_extended, ResourceId=finding.resource_uid, CheckId=finding.check_id, Muted=finding.muted, diff --git a/prowler/lib/outputs/compliance/iso27001/iso27001_nhn.py b/prowler/lib/outputs/compliance/iso27001/iso27001_nhn.py index 2ad5a0bda45..ad8ea6dcabe 100644 --- a/prowler/lib/outputs/compliance/iso27001/iso27001_nhn.py +++ b/prowler/lib/outputs/compliance/iso27001/iso27001_nhn.py @@ -1,4 +1,8 @@ from prowler.config.config import timestamp +from prowler.lib.check.compliance_config_eval import ( + apply_config_status, + build_requirement_config_status, +) from prowler.lib.check.compliance_models import Compliance from prowler.lib.outputs.compliance.compliance_output import ComplianceOutput from prowler.lib.outputs.compliance.iso27001.models import NHNISO27001Model @@ -34,10 +38,18 @@ def transform( Returns: - None """ + requirement_config_status = build_requirement_config_status( + compliance.Requirements + ) for finding in findings: for requirement in compliance.Requirements: # Source of truth: framework JSON, not finding.compliance snapshot (avoids CSV/UI count drift). if finding.check_id in requirement.Checks: + row_status, row_status_extended = apply_config_status( + finding.status, + finding.status_extended, + requirement_config_status.get(requirement.Id), + ) for attribute in requirement.Attributes: compliance_row = NHNISO27001Model( Provider=finding.provider, @@ -52,8 +64,8 @@ def transform( Requirements_Attributes_Objetive_ID=attribute.Objetive_ID, Requirements_Attributes_Objetive_Name=attribute.Objetive_Name, Requirements_Attributes_Check_Summary=attribute.Check_Summary, - Status=finding.status, - StatusExtended=finding.status_extended, + Status=row_status, + StatusExtended=row_status_extended, ResourceId=finding.resource_uid, CheckId=finding.check_id, Muted=finding.muted, diff --git a/prowler/lib/outputs/compliance/kisa_ismsp/kisa_ismsp.py b/prowler/lib/outputs/compliance/kisa_ismsp/kisa_ismsp.py index e7c00b188d9..dda83342b66 100644 --- a/prowler/lib/outputs/compliance/kisa_ismsp/kisa_ismsp.py +++ b/prowler/lib/outputs/compliance/kisa_ismsp/kisa_ismsp.py @@ -2,6 +2,12 @@ from tabulate import tabulate from prowler.config.config import orange_color +from prowler.lib.check.compliance_config_eval import ( + accumulate_overview_status, + get_effective_status, + get_scan_audit_config, + resolve_requirement_config_status, +) def get_kisa_ismsp_table( @@ -22,9 +28,11 @@ def get_kisa_ismsp_table( "Status": [], "Muted": [], } - pass_count = [] - fail_count = [] - muted_count = [] + pass_count = set() + fail_count = set() + muted_count = set() + audit_config = get_scan_audit_config() + config_status_cache = {} for index, finding in enumerate(findings): check = bulk_checks_metadata[finding.check_metadata.CheckID] check_compliances = check.Compliance @@ -35,6 +43,12 @@ def get_kisa_ismsp_table( ): provider = compliance.Provider for requirement in compliance.Requirements: + config_status = resolve_requirement_config_status( + requirement, audit_config, config_status_cache + ) + effective_status = get_effective_status( + finding.status, config_status + ) for attribute in requirement.Attributes: section = attribute.Section # Check if Section exists @@ -46,29 +60,27 @@ def get_kisa_ismsp_table( }, "Muted": 0, } - section_seen[section] = set() + section_seen[section] = {} - # Overview totals: count each finding once per framework - if finding.muted: - if index not in muted_count: - muted_count.append(index) - elif finding.status == "FAIL": - if index not in fail_count: - fail_count.append(index) - elif finding.status == "PASS": - if index not in pass_count: - pass_count.append(index) + status = "Muted" if finding.muted else effective_status + accumulate_overview_status( + index, status, pass_count, fail_count, muted_count + ) - # Per-section counts: count each finding once per section - # it belongs to (a finding can map to several sections). - if index not in section_seen[section]: - section_seen[section].add(index) - if finding.muted: + # FAIL/PASS live under ["Status"], Muted at top level. + previous = section_seen[section].get(index) + if previous is None: + section_seen[section][index] = status + if status == "Muted": sections[section]["Muted"] += 1 - elif finding.status == "FAIL": + elif status == "FAIL": sections[section]["Status"]["FAIL"] += 1 - elif finding.status == "PASS": + elif status == "PASS": sections[section]["Status"]["PASS"] += 1 + elif previous == "PASS" and status == "FAIL": + section_seen[section][index] = "FAIL" + sections[section]["Status"]["PASS"] -= 1 + sections[section]["Status"]["FAIL"] += 1 # Add results to table sections = dict(sorted(sections.items())) diff --git a/prowler/lib/outputs/compliance/kisa_ismsp/kisa_ismsp_aws.py b/prowler/lib/outputs/compliance/kisa_ismsp/kisa_ismsp_aws.py index f927c2d4b11..3d05632d262 100644 --- a/prowler/lib/outputs/compliance/kisa_ismsp/kisa_ismsp_aws.py +++ b/prowler/lib/outputs/compliance/kisa_ismsp/kisa_ismsp_aws.py @@ -1,4 +1,8 @@ from prowler.config.config import timestamp +from prowler.lib.check.compliance_config_eval import ( + apply_config_status, + build_requirement_config_status, +) from prowler.lib.check.compliance_models import Compliance from prowler.lib.outputs.compliance.compliance_output import ComplianceOutput from prowler.lib.outputs.compliance.kisa_ismsp.models import AWSKISAISMSPModel @@ -34,10 +38,19 @@ def transform( Returns: - None """ + requirement_config_status = build_requirement_config_status( + compliance.Requirements + ) + for finding in findings: for requirement in compliance.Requirements: # Source of truth: framework JSON, not finding.compliance snapshot (avoids CSV/UI count drift). if finding.check_id in requirement.Checks: + row_status, row_status_extended = apply_config_status( + finding.status, + finding.status_extended, + requirement_config_status.get(requirement.Id), + ) for attribute in requirement.Attributes: compliance_row = AWSKISAISMSPModel( Provider=finding.provider, @@ -55,8 +68,8 @@ def transform( Requirements_Attributes_RelatedRegulations=attribute.RelatedRegulations, Requirements_Attributes_AuditEvidence=attribute.AuditEvidence, Requirements_Attributes_NonComplianceCases=attribute.NonComplianceCases, - Status=finding.status, - StatusExtended=finding.status_extended, + Status=row_status, + StatusExtended=row_status_extended, ResourceId=finding.resource_uid, ResourceName=finding.resource_name, CheckId=finding.check_id, diff --git a/prowler/lib/outputs/compliance/mitre_attack/mitre_attack.py b/prowler/lib/outputs/compliance/mitre_attack/mitre_attack.py index 7492624aff8..a352acfa6e6 100644 --- a/prowler/lib/outputs/compliance/mitre_attack/mitre_attack.py +++ b/prowler/lib/outputs/compliance/mitre_attack/mitre_attack.py @@ -2,6 +2,13 @@ from tabulate import tabulate from prowler.config.config import orange_color +from prowler.lib.check.compliance_config_eval import ( + accumulate_group_status, + accumulate_overview_status, + get_effective_status, + get_scan_audit_config, + resolve_requirement_config_status, +) def get_mitre_attack_table( @@ -21,9 +28,11 @@ def get_mitre_attack_table( "Status": [], "Muted": [], } - pass_count = [] - fail_count = [] - muted_count = [] + pass_count = set() + fail_count = set() + muted_count = set() + audit_config = get_scan_audit_config() + config_status_cache = {} for index, finding in enumerate(findings): check = bulk_checks_metadata[finding.check_metadata.CheckID] check_compliances = check.Compliance @@ -34,32 +43,23 @@ def get_mitre_attack_table( ): provider = compliance.Provider for requirement in compliance.Requirements: + config_status = resolve_requirement_config_status( + requirement, audit_config, config_status_cache + ) + effective_status = get_effective_status( + finding.status, config_status + ) + status = "Muted" if finding.muted else effective_status for tactic in requirement.Tactics: if tactic not in tactics: tactics[tactic] = {"FAIL": 0, "PASS": 0, "Muted": 0} - tactic_seen[tactic] = set() - - # Overview totals: count each finding once per framework - if finding.muted: - if index not in muted_count: - muted_count.append(index) - elif finding.status == "FAIL": - if index not in fail_count: - fail_count.append(index) - elif finding.status == "PASS": - if index not in pass_count: - pass_count.append(index) - - # Per-tactic counts: count each finding once per tactic - # it belongs to (a finding can map to several tactics). - if index not in tactic_seen[tactic]: - tactic_seen[tactic].add(index) - if finding.muted: - tactics[tactic]["Muted"] += 1 - elif finding.status == "FAIL": - tactics[tactic]["FAIL"] += 1 - elif finding.status == "PASS": - tactics[tactic]["PASS"] += 1 + tactic_seen[tactic] = {} + accumulate_overview_status( + index, status, pass_count, fail_count, muted_count + ) + accumulate_group_status( + index, status, tactics[tactic], tactic_seen[tactic] + ) # Add results to table tactics = dict(sorted(tactics.items())) for tactic in tactics: diff --git a/prowler/lib/outputs/compliance/mitre_attack/mitre_attack_aws.py b/prowler/lib/outputs/compliance/mitre_attack/mitre_attack_aws.py index 33a7aca74bb..92f4ac0e5bf 100644 --- a/prowler/lib/outputs/compliance/mitre_attack/mitre_attack_aws.py +++ b/prowler/lib/outputs/compliance/mitre_attack/mitre_attack_aws.py @@ -1,4 +1,8 @@ from prowler.config.config import timestamp +from prowler.lib.check.compliance_config_eval import ( + apply_config_status, + build_requirement_config_status, +) from prowler.lib.check.compliance_models import Compliance from prowler.lib.outputs.compliance.compliance_output import ComplianceOutput from prowler.lib.outputs.compliance.mitre_attack.models import AWSMitreAttackModel @@ -35,10 +39,19 @@ def transform( Returns: - None """ + requirement_config_status = build_requirement_config_status( + compliance.Requirements + ) + for finding in findings: for requirement in compliance.Requirements: # Source of truth: framework JSON, not finding.compliance snapshot (avoids CSV/UI count drift). if finding.check_id in requirement.Checks: + row_status, row_status_extended = apply_config_status( + finding.status, + finding.status_extended, + requirement_config_status.get(requirement.Id), + ) compliance_row = AWSMitreAttackModel( Provider=finding.provider, Description=compliance.Description, @@ -66,8 +79,8 @@ def transform( Requirements_Attributes_Comments=", ".join( attribute.Comment for attribute in requirement.Attributes ), - Status=finding.status, - StatusExtended=finding.status_extended, + Status=row_status, + StatusExtended=row_status_extended, ResourceId=finding.resource_uid, ResourceName=finding.resource_name, CheckId=finding.check_id, diff --git a/prowler/lib/outputs/compliance/mitre_attack/mitre_attack_azure.py b/prowler/lib/outputs/compliance/mitre_attack/mitre_attack_azure.py index 0254aad86e8..a43e27e775e 100644 --- a/prowler/lib/outputs/compliance/mitre_attack/mitre_attack_azure.py +++ b/prowler/lib/outputs/compliance/mitre_attack/mitre_attack_azure.py @@ -1,4 +1,8 @@ from prowler.config.config import timestamp +from prowler.lib.check.compliance_config_eval import ( + apply_config_status, + build_requirement_config_status, +) from prowler.lib.check.compliance_models import Compliance from prowler.lib.outputs.compliance.compliance_output import ComplianceOutput from prowler.lib.outputs.compliance.mitre_attack.models import AzureMitreAttackModel @@ -35,10 +39,19 @@ def transform( Returns: - None """ + requirement_config_status = build_requirement_config_status( + compliance.Requirements + ) + for finding in findings: for requirement in compliance.Requirements: # Source of truth: framework JSON, not finding.compliance snapshot (avoids CSV/UI count drift). if finding.check_id in requirement.Checks: + row_status, row_status_extended = apply_config_status( + finding.status, + finding.status_extended, + requirement_config_status.get(requirement.Id), + ) compliance_row = AzureMitreAttackModel( Provider=finding.provider, Description=compliance.Description, @@ -67,8 +80,8 @@ def transform( Requirements_Attributes_Comments=", ".join( attribute.Comment for attribute in requirement.Attributes ), - Status=finding.status, - StatusExtended=finding.status_extended, + Status=row_status, + StatusExtended=row_status_extended, ResourceId=finding.resource_uid, ResourceName=finding.resource_name, CheckId=finding.check_id, diff --git a/prowler/lib/outputs/compliance/mitre_attack/mitre_attack_gcp.py b/prowler/lib/outputs/compliance/mitre_attack/mitre_attack_gcp.py index 46342734941..b8efdc52504 100644 --- a/prowler/lib/outputs/compliance/mitre_attack/mitre_attack_gcp.py +++ b/prowler/lib/outputs/compliance/mitre_attack/mitre_attack_gcp.py @@ -1,4 +1,8 @@ from prowler.config.config import timestamp +from prowler.lib.check.compliance_config_eval import ( + apply_config_status, + build_requirement_config_status, +) from prowler.lib.check.compliance_models import Compliance from prowler.lib.outputs.compliance.compliance_output import ComplianceOutput from prowler.lib.outputs.compliance.mitre_attack.models import GCPMitreAttackModel @@ -35,10 +39,19 @@ def transform( Returns: - None """ + requirement_config_status = build_requirement_config_status( + compliance.Requirements + ) + for finding in findings: for requirement in compliance.Requirements: # Source of truth: framework JSON, not finding.compliance snapshot (avoids CSV/UI count drift). if finding.check_id in requirement.Checks: + row_status, row_status_extended = apply_config_status( + finding.status, + finding.status_extended, + requirement_config_status.get(requirement.Id), + ) compliance_row = GCPMitreAttackModel( Provider=finding.provider, Description=compliance.Description, @@ -66,8 +79,8 @@ def transform( Requirements_Attributes_Comments=", ".join( attribute.Comment for attribute in requirement.Attributes ), - Status=finding.status, - StatusExtended=finding.status_extended, + Status=row_status, + StatusExtended=row_status_extended, ResourceId=finding.resource_uid, ResourceName=finding.resource_name, CheckId=finding.check_id, diff --git a/prowler/lib/outputs/compliance/okta_idaas_stig/okta_idaas_stig.py b/prowler/lib/outputs/compliance/okta_idaas_stig/okta_idaas_stig.py index 1febe02f60b..c743ffad033 100644 --- a/prowler/lib/outputs/compliance/okta_idaas_stig/okta_idaas_stig.py +++ b/prowler/lib/outputs/compliance/okta_idaas_stig/okta_idaas_stig.py @@ -2,6 +2,11 @@ from tabulate import tabulate from prowler.config.config import orange_color +from prowler.lib.check.compliance_config_eval import ( + get_effective_status, + get_scan_audit_config, + resolve_requirement_config_status, +) def get_okta_idaas_stig_table( @@ -24,12 +29,28 @@ def get_okta_idaas_stig_table( sections = {} section_seen = {} provider = "" + audit_config = get_scan_audit_config() + config_status_cache = {} for index, finding in enumerate(findings): check = bulk_checks_metadata[finding.check_metadata.CheckID] check_compliances = check.Compliance for compliance in check_compliances: if compliance.Framework == "Okta-IDaaS-STIG": provider = compliance.Provider + # A configurable check that passed with a too-loose config is + # forced to FAIL (source of truth: framework ConfigRequirements). + effective_status = finding.status + for requirement in compliance.Requirements: + if finding.check_id in requirement.Checks: + config_status = resolve_requirement_config_status( + requirement, audit_config, config_status_cache + ) + if ( + get_effective_status(finding.status, config_status) + == "FAIL" + ): + effective_status = "FAIL" + break for requirement in compliance.Requirements: for attribute in requirement.Attributes: section = attribute.Section @@ -42,10 +63,10 @@ def get_okta_idaas_stig_table( if finding.muted: if index not in muted_count: muted_count.append(index) - elif finding.status == "FAIL": + elif effective_status == "FAIL": if index not in fail_count: fail_count.append(index) - elif finding.status == "PASS": + elif effective_status == "PASS": if index not in pass_count: pass_count.append(index) @@ -55,9 +76,9 @@ def get_okta_idaas_stig_table( section_seen[section].add(index) if finding.muted: sections[section]["Muted"] += 1 - elif finding.status == "FAIL": + elif effective_status == "FAIL": sections[section]["FAIL"] += 1 - elif finding.status == "PASS": + elif effective_status == "PASS": sections[section]["PASS"] += 1 sections = dict(sorted(sections.items())) diff --git a/prowler/lib/outputs/compliance/okta_idaas_stig/okta_idaas_stig_okta.py b/prowler/lib/outputs/compliance/okta_idaas_stig/okta_idaas_stig_okta.py index 25f71b4def9..b8a72f9f959 100644 --- a/prowler/lib/outputs/compliance/okta_idaas_stig/okta_idaas_stig_okta.py +++ b/prowler/lib/outputs/compliance/okta_idaas_stig/okta_idaas_stig_okta.py @@ -1,4 +1,8 @@ from prowler.config.config import timestamp +from prowler.lib.check.compliance_config_eval import ( + apply_config_status, + build_requirement_config_status, +) from prowler.lib.check.compliance_models import Compliance from prowler.lib.outputs.compliance.compliance_output import ComplianceOutput from prowler.lib.outputs.compliance.okta_idaas_stig.models import OktaIDaaSSTIGModel @@ -34,10 +38,18 @@ def transform( Returns: - None """ + requirement_config_status = build_requirement_config_status( + compliance.Requirements + ) for finding in findings: for requirement in compliance.Requirements: # Source of truth: framework JSON, not finding.compliance snapshot (avoids CSV/UI count drift). if finding.check_id in requirement.Checks: + row_status, row_status_extended = apply_config_status( + finding.status, + finding.status_extended, + requirement_config_status.get(requirement.Id), + ) for attribute in requirement.Attributes: compliance_row = OktaIDaaSSTIGModel( Provider=finding.provider, @@ -54,8 +66,8 @@ def transform( Requirements_Attributes_CCI=attribute.CCI, Requirements_Attributes_CheckText=attribute.CheckText, Requirements_Attributes_FixText=attribute.FixText, - Status=finding.status, - StatusExtended=finding.status_extended, + Status=row_status, + StatusExtended=row_status_extended, ResourceId=finding.resource_uid, ResourceName=finding.resource_name, CheckId=finding.check_id, diff --git a/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore.py b/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore.py index b17307f04af..cb23d67ffb6 100644 --- a/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore.py +++ b/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore.py @@ -2,6 +2,13 @@ from tabulate import tabulate from prowler.config.config import orange_color +from prowler.lib.check.compliance_config_eval import ( + accumulate_group_status, + accumulate_overview_status, + get_effective_status, + get_scan_audit_config, + resolve_requirement_config_status, +) from prowler.lib.check.compliance_models import Compliance @@ -20,18 +27,20 @@ def get_prowler_threatscore_table( "Score": [], "Muted": [], } - pass_count = [] - fail_count = [] - muted_count = [] + pass_count = set() + fail_count = set() + muted_count = set() pillars = {} pillar_seen = {} provider = "" generic_score = 0 max_generic_score = 0 - counted_findings_generic = [] + counted_findings_generic = {} score_per_pillar = {} max_score_per_pillar = {} counted_findings_per_pillar = {} + audit_config = get_scan_audit_config() + config_status_cache = {} for index, finding in enumerate(findings): check = bulk_checks_metadata[finding.check_metadata.CheckID] check_compliances = check.Compliance @@ -39,6 +48,12 @@ def get_prowler_threatscore_table( if compliance.Framework == "ProwlerThreatScore": provider = compliance.Provider for requirement in compliance.Requirements: + config_status = resolve_requirement_config_status( + requirement, audit_config, config_status_cache + ) + effective_status = get_effective_status( + finding.status, config_status + ) for attribute in requirement.Attributes: pillar = attribute.Section @@ -51,57 +66,51 @@ def get_prowler_threatscore_table( ): score_per_pillar[pillar] = 0 max_score_per_pillar[pillar] = 0 - counted_findings_per_pillar[pillar] = [] + counted_findings_per_pillar[pillar] = {} - if ( - index not in counted_findings_per_pillar[pillar] - and not finding.muted - ): - if finding.status == "PASS": - score_per_pillar[pillar] += ( - attribute.LevelOfRisk * attribute.Weight - ) - max_score_per_pillar[pillar] += ( - attribute.LevelOfRisk * attribute.Weight - ) - counted_findings_per_pillar[pillar].append(index) + # Revoke an earlier PASS score if a later requirement FAILs. + if not finding.muted: + contribution = attribute.LevelOfRisk * attribute.Weight + counted = counted_findings_per_pillar[pillar] + if index not in counted: + max_score_per_pillar[pillar] += contribution + if effective_status == "PASS": + score_per_pillar[pillar] += contribution + counted[index] = contribution + else: + counted[index] = 0 + elif effective_status == "FAIL" and counted[index]: + score_per_pillar[pillar] -= counted[index] + counted[index] = 0 if pillar not in pillars: pillars[pillar] = {"FAIL": 0, "PASS": 0, "Muted": 0} - pillar_seen[pillar] = set() - - # Overview totals: count each finding once per framework - if finding.muted: - if index not in muted_count: - muted_count.append(index) - elif finding.status == "FAIL": - if index not in fail_count: - fail_count.append(index) - elif finding.status == "PASS": - if index not in pass_count: - pass_count.append(index) + pillar_seen[pillar] = {} - # Per-pillar counts: count each finding once per pillar - # it belongs to (a finding can map to several pillars). - if index not in pillar_seen[pillar]: - pillar_seen[pillar].add(index) - if finding.muted: - pillars[pillar]["Muted"] += 1 - elif finding.status == "FAIL": - pillars[pillar]["FAIL"] += 1 - elif finding.status == "PASS": - pillars[pillar]["PASS"] += 1 + status = "Muted" if finding.muted else effective_status + accumulate_overview_status( + index, status, pass_count, fail_count, muted_count + ) + accumulate_group_status( + index, status, pillars[pillar], pillar_seen[pillar] + ) - # Generic score - if index not in counted_findings_generic and not finding.muted: - if finding.status == "PASS": - generic_score += ( - attribute.LevelOfRisk * attribute.Weight - ) - max_generic_score += ( - attribute.LevelOfRisk * attribute.Weight - ) - counted_findings_generic.append(index) + # Generic score, with the same PASS-revocation on FAIL. + if not finding.muted: + contribution = attribute.LevelOfRisk * attribute.Weight + if index not in counted_findings_generic: + max_generic_score += contribution + if effective_status == "PASS": + generic_score += contribution + counted_findings_generic[index] = contribution + else: + counted_findings_generic[index] = 0 + elif ( + effective_status == "FAIL" + and counted_findings_generic[index] + ): + generic_score -= counted_findings_generic[index] + counted_findings_generic[index] = 0 no_findings_pillars = [] bulk_compliance = ( diff --git a/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_alibaba.py b/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_alibaba.py index 510c098dff4..7d682a3e62f 100644 --- a/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_alibaba.py +++ b/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_alibaba.py @@ -1,4 +1,8 @@ from prowler.config.config import timestamp +from prowler.lib.check.compliance_config_eval import ( + apply_config_status, + build_requirement_config_status, +) from prowler.lib.check.compliance_models import Compliance from prowler.lib.outputs.compliance.compliance_output import ComplianceOutput from prowler.lib.outputs.compliance.prowler_threatscore.models import ( @@ -36,10 +40,19 @@ def transform( Returns: - None """ + requirement_config_status = build_requirement_config_status( + compliance.Requirements + ) + for finding in findings: for requirement in compliance.Requirements: # Source of truth: framework JSON, not finding.compliance snapshot (avoids CSV/UI count drift). if finding.check_id in requirement.Checks: + row_status, row_status_extended = apply_config_status( + finding.status, + finding.status_extended, + requirement_config_status.get(requirement.Id), + ) for attribute in requirement.Attributes: compliance_row = ProwlerThreatScoreAlibabaModel( Provider=finding.provider, @@ -56,8 +69,8 @@ def transform( Requirements_Attributes_AdditionalInformation=attribute.AdditionalInformation, Requirements_Attributes_LevelOfRisk=attribute.LevelOfRisk, Requirements_Attributes_Weight=attribute.Weight, - Status=finding.status, - StatusExtended=finding.status_extended, + Status=row_status, + StatusExtended=row_status_extended, ResourceId=finding.resource_uid, ResourceName=finding.resource_name, CheckId=finding.check_id, diff --git a/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_aws.py b/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_aws.py index ae992f8a75c..f1280808e39 100644 --- a/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_aws.py +++ b/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_aws.py @@ -1,4 +1,8 @@ from prowler.config.config import timestamp +from prowler.lib.check.compliance_config_eval import ( + apply_config_status, + build_requirement_config_status, +) from prowler.lib.check.compliance_models import Compliance from prowler.lib.outputs.compliance.compliance_output import ComplianceOutput from prowler.lib.outputs.compliance.prowler_threatscore.models import ( @@ -36,10 +40,19 @@ def transform( Returns: - None """ + requirement_config_status = build_requirement_config_status( + compliance.Requirements + ) + for finding in findings: for requirement in compliance.Requirements: # Source of truth: framework JSON, not finding.compliance snapshot (avoids CSV/UI count drift). if finding.check_id in requirement.Checks: + row_status, row_status_extended = apply_config_status( + finding.status, + finding.status_extended, + requirement_config_status.get(requirement.Id), + ) for attribute in requirement.Attributes: compliance_row = ProwlerThreatScoreAWSModel( Provider=finding.provider, @@ -56,8 +69,8 @@ def transform( Requirements_Attributes_AdditionalInformation=attribute.AdditionalInformation, Requirements_Attributes_LevelOfRisk=attribute.LevelOfRisk, Requirements_Attributes_Weight=attribute.Weight, - Status=finding.status, - StatusExtended=finding.status_extended, + Status=row_status, + StatusExtended=row_status_extended, ResourceId=finding.resource_uid, ResourceName=finding.resource_name, CheckId=finding.check_id, diff --git a/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_azure.py b/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_azure.py index dd0a3b9a56e..51185113699 100644 --- a/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_azure.py +++ b/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_azure.py @@ -1,4 +1,8 @@ from prowler.config.config import timestamp +from prowler.lib.check.compliance_config_eval import ( + apply_config_status, + build_requirement_config_status, +) from prowler.lib.check.compliance_models import Compliance from prowler.lib.outputs.compliance.compliance_output import ComplianceOutput from prowler.lib.outputs.compliance.prowler_threatscore.models import ( @@ -36,10 +40,19 @@ def transform( Returns: - None """ + requirement_config_status = build_requirement_config_status( + compliance.Requirements + ) + for finding in findings: for requirement in compliance.Requirements: # Source of truth: framework JSON, not finding.compliance snapshot (avoids CSV/UI count drift). if finding.check_id in requirement.Checks: + row_status, row_status_extended = apply_config_status( + finding.status, + finding.status_extended, + requirement_config_status.get(requirement.Id), + ) for attribute in requirement.Attributes: compliance_row = ProwlerThreatScoreAzureModel( Provider=finding.provider, @@ -56,8 +69,8 @@ def transform( Requirements_Attributes_AdditionalInformation=attribute.AdditionalInformation, Requirements_Attributes_LevelOfRisk=attribute.LevelOfRisk, Requirements_Attributes_Weight=attribute.Weight, - Status=finding.status, - StatusExtended=finding.status_extended, + Status=row_status, + StatusExtended=row_status_extended, ResourceId=finding.resource_uid, ResourceName=finding.resource_name, CheckId=finding.check_id, diff --git a/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_gcp.py b/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_gcp.py index c3bad98ade7..39f9c238505 100644 --- a/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_gcp.py +++ b/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_gcp.py @@ -1,4 +1,8 @@ from prowler.config.config import timestamp +from prowler.lib.check.compliance_config_eval import ( + apply_config_status, + build_requirement_config_status, +) from prowler.lib.check.compliance_models import Compliance from prowler.lib.outputs.compliance.compliance_output import ComplianceOutput from prowler.lib.outputs.compliance.prowler_threatscore.models import ( @@ -36,10 +40,19 @@ def transform( Returns: - None """ + requirement_config_status = build_requirement_config_status( + compliance.Requirements + ) + for finding in findings: for requirement in compliance.Requirements: # Source of truth: framework JSON, not finding.compliance snapshot (avoids CSV/UI count drift). if finding.check_id in requirement.Checks: + row_status, row_status_extended = apply_config_status( + finding.status, + finding.status_extended, + requirement_config_status.get(requirement.Id), + ) for attribute in requirement.Attributes: compliance_row = ProwlerThreatScoreGCPModel( Provider=finding.provider, @@ -56,8 +69,8 @@ def transform( Requirements_Attributes_AdditionalInformation=attribute.AdditionalInformation, Requirements_Attributes_LevelOfRisk=attribute.LevelOfRisk, Requirements_Attributes_Weight=attribute.Weight, - Status=finding.status, - StatusExtended=finding.status_extended, + Status=row_status, + StatusExtended=row_status_extended, ResourceId=finding.resource_uid, ResourceName=finding.resource_name, CheckId=finding.check_id, diff --git a/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_kubernetes.py b/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_kubernetes.py index 51f88348f02..88bf41582a2 100644 --- a/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_kubernetes.py +++ b/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_kubernetes.py @@ -1,4 +1,8 @@ from prowler.config.config import timestamp +from prowler.lib.check.compliance_config_eval import ( + apply_config_status, + build_requirement_config_status, +) from prowler.lib.check.compliance_models import Compliance from prowler.lib.outputs.compliance.compliance_output import ComplianceOutput from prowler.lib.outputs.compliance.prowler_threatscore.models import ( @@ -36,10 +40,19 @@ def transform( Returns: - None """ + requirement_config_status = build_requirement_config_status( + compliance.Requirements + ) + for finding in findings: for requirement in compliance.Requirements: # Source of truth: framework JSON, not finding.compliance snapshot (avoids CSV/UI count drift). if finding.check_id in requirement.Checks: + row_status, row_status_extended = apply_config_status( + finding.status, + finding.status_extended, + requirement_config_status.get(requirement.Id), + ) for attribute in requirement.Attributes: compliance_row = ProwlerThreatScoreKubernetesModel( Provider=finding.provider, @@ -56,8 +69,8 @@ def transform( Requirements_Attributes_AdditionalInformation=attribute.AdditionalInformation, Requirements_Attributes_LevelOfRisk=attribute.LevelOfRisk, Requirements_Attributes_Weight=attribute.Weight, - Status=finding.status, - StatusExtended=finding.status_extended, + Status=row_status, + StatusExtended=row_status_extended, ResourceId=finding.resource_uid, ResourceName=finding.resource_name, CheckId=finding.check_id, diff --git a/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_m365.py b/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_m365.py index d0b2ad635c4..b7b533c72ae 100644 --- a/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_m365.py +++ b/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_m365.py @@ -1,4 +1,8 @@ from prowler.config.config import timestamp +from prowler.lib.check.compliance_config_eval import ( + apply_config_status, + build_requirement_config_status, +) from prowler.lib.check.compliance_models import Compliance from prowler.lib.outputs.compliance.compliance_output import ComplianceOutput from prowler.lib.outputs.compliance.prowler_threatscore.models import ( @@ -36,10 +40,19 @@ def transform( Returns: - None """ + requirement_config_status = build_requirement_config_status( + compliance.Requirements + ) + for finding in findings: for requirement in compliance.Requirements: # Source of truth: framework JSON, not finding.compliance snapshot (avoids CSV/UI count drift). if finding.check_id in requirement.Checks: + row_status, row_status_extended = apply_config_status( + finding.status, + finding.status_extended, + requirement_config_status.get(requirement.Id), + ) for attribute in requirement.Attributes: compliance_row = ProwlerThreatScoreM365Model( Provider=finding.provider, @@ -56,8 +69,8 @@ def transform( Requirements_Attributes_AdditionalInformation=attribute.AdditionalInformation, Requirements_Attributes_LevelOfRisk=attribute.LevelOfRisk, Requirements_Attributes_Weight=attribute.Weight, - Status=finding.status, - StatusExtended=finding.status_extended, + Status=row_status, + StatusExtended=row_status_extended, ResourceId=finding.resource_uid, ResourceName=finding.resource_name, CheckId=finding.check_id, diff --git a/prowler/lib/outputs/compliance/universal/ocsf_compliance.py b/prowler/lib/outputs/compliance/universal/ocsf_compliance.py index 2886f7e4d2f..07c1f67b10d 100644 --- a/prowler/lib/outputs/compliance/universal/ocsf_compliance.py +++ b/prowler/lib/outputs/compliance/universal/ocsf_compliance.py @@ -19,6 +19,10 @@ from py_ocsf_models.objects.resource_details import ResourceDetails from prowler.config.config import prowler_version +from prowler.lib.check.compliance_config_eval import ( + apply_config_status, + build_requirement_config_status, +) from prowler.lib.check.compliance_models import ComplianceFramework from prowler.lib.logger import logger from prowler.lib.outputs.utils import unroll_dict_to_list @@ -181,11 +185,21 @@ def _transform( for check_id in all_checks: check_req_map.setdefault(check_id, []).append(req) + # Scope constraints to this output's provider (e.g. an Azure constraint + # must not affect an AWS output). + requirement_config_status = build_requirement_config_status( + framework.requirements, provider_type=self._provider + ) + for finding in findings: if finding.check_id in check_req_map: for req in check_req_map[finding.check_id]: cf = self._build_compliance_finding( - finding, framework, req, compliance_name + finding, + framework, + req, + compliance_name, + requirement_config_status.get(req.id, (True, "")), ) if cf: self._data.append(cf) @@ -240,10 +254,14 @@ def _build_compliance_finding( framework: ComplianceFramework, requirement, compliance_name: str, + config_status: tuple = (True, ""), ) -> ComplianceFinding: try: + effective_status, message = apply_config_status( + finding.status, finding.status_extended, config_status + ) compliance_status = PROWLER_TO_COMPLIANCE_STATUS.get( - finding.status, ComplianceStatusID.Unknown + effective_status, ComplianceStatusID.Unknown ) check_status = PROWLER_TO_COMPLIANCE_STATUS.get( finding.status, ComplianceStatusID.Unknown @@ -272,6 +290,7 @@ def _build_compliance_finding( requirements=[requirement.id], control=requirement.description, status_id=compliance_status, + # Nested Check preserves the raw check result. checks=[ Check( uid=finding.check_id, @@ -293,7 +312,7 @@ def _build_compliance_finding( else None ), ), - message=finding.status_extended, + message=message, metadata=Metadata( event_code=finding.check_id, product=Product( @@ -339,8 +358,10 @@ def _build_compliance_finding( severity=finding_severity.name, status_id=event_status.value, status=event_status.name, - status_code=finding.status, - status_detail=finding.status_extended, + # Effective status, so the top-level never contradicts the + # nested compliance status. + status_code=effective_status, + status_detail=message, time=time_value, time_dt=( finding.timestamp diff --git a/prowler/lib/outputs/compliance/universal/universal_output.py b/prowler/lib/outputs/compliance/universal/universal_output.py index a3cdb1389af..59afd1fa311 100644 --- a/prowler/lib/outputs/compliance/universal/universal_output.py +++ b/prowler/lib/outputs/compliance/universal/universal_output.py @@ -5,6 +5,10 @@ from pydantic.v1 import create_model from prowler.config.config import timestamp +from prowler.lib.check.compliance_config_eval import ( + apply_config_status, + build_requirement_config_status, +) from prowler.lib.check.compliance_models import ComplianceFramework from prowler.lib.logger import logger from prowler.lib.utils.utils import open_file @@ -134,7 +138,9 @@ def _serialize_attr_value(self, value): return " | ".join(str(v) for v in value) return value - def _build_row(self, finding, framework, requirement, is_manual=False): + def _build_row( + self, finding, framework, requirement, is_manual=False, config_status=None + ): """Build a single row dict for a finding + requirement combination.""" row = { "Provider": ( @@ -180,10 +186,14 @@ def _build_row(self, finding, framework, requirement, is_manual=False): ) row["Requirements_TechniqueURL"] = requirement.technique_url - row["Status"] = finding.status if not is_manual else "MANUAL" - row["StatusExtended"] = ( - finding.status_extended if not is_manual else "Manual check" - ) + if is_manual: + row["Status"] = "MANUAL" + row["StatusExtended"] = "Manual check" + else: + # Config-invalid PASS reports as FAIL, matching OCSF/table outputs. + row["Status"], row["StatusExtended"] = apply_config_status( + finding.status, finding.status_extended, config_status + ) row["ResourceId"] = finding.resource_uid if not is_manual else "manual_check" row["ResourceName"] = finding.resource_name if not is_manual else "Manual check" row["CheckId"] = finding.check_id if not is_manual else "manual" @@ -222,6 +232,12 @@ def _transform( check_req_map[check_id] = [] check_req_map[check_id].append(req) + # Scope constraints to this output's provider (e.g. an Azure constraint + # must not affect an AWS output). + requirement_config_status = build_requirement_config_status( + framework.requirements, provider_type=self._provider + ) + # Process findings using the provider-filtered check_req_map. # This ensures that for multi-provider dict checks, only the checks # belonging to the current provider produce output rows. @@ -229,7 +245,12 @@ def _transform( check_id = finding.check_id if check_id in check_req_map: for req in check_req_map[check_id]: - row = self._build_row(finding, framework, req) + row = self._build_row( + finding, + framework, + req, + config_status=requirement_config_status.get(req.id), + ) try: self._data.append(self._row_model(**row)) except Exception as e: diff --git a/prowler/lib/outputs/compliance/universal/universal_table.py b/prowler/lib/outputs/compliance/universal/universal_table.py index e54ad5155c2..5f4a6cf88b7 100644 --- a/prowler/lib/outputs/compliance/universal/universal_table.py +++ b/prowler/lib/outputs/compliance/universal/universal_table.py @@ -2,6 +2,13 @@ from tabulate import tabulate from prowler.config.config import orange_color +from prowler.lib.check.compliance_config_eval import ( + accumulate_group_status, + accumulate_overview_status, + get_effective_status, + get_scan_audit_config, + resolve_requirement_config_status, +) from prowler.lib.check.compliance_models import ComplianceFramework @@ -164,9 +171,11 @@ def _render_grouped( check_map = _build_requirement_check_map(framework, provider) groups = {} group_seen = {} - pass_count = [] - fail_count = [] - muted_count = [] + pass_count = set() + fail_count = set() + muted_count = set() + audit_config = get_scan_audit_config() + config_status_cache = {} for index, finding in enumerate(findings): check_id = finding.check_metadata.CheckID @@ -174,32 +183,24 @@ def _render_grouped( continue for req in check_map[check_id]: + effective_status = get_effective_status( + finding.status, + resolve_requirement_config_status( + req, audit_config, config_status_cache, provider_type=provider + ), + ) for group_key in _get_group_key(req, group_by): if group_key not in groups: groups[group_key] = {"FAIL": 0, "PASS": 0, "Muted": 0} - group_seen[group_key] = set() + group_seen[group_key] = {} - # Overview totals: count each finding once per framework - if finding.muted: - if index not in muted_count: - muted_count.append(index) - elif finding.status == "FAIL": - if index not in fail_count: - fail_count.append(index) - elif finding.status == "PASS": - if index not in pass_count: - pass_count.append(index) - - # Per-group counts: count each finding once per group it belongs - # to (a finding can map to several groups via several requirements). - if index not in group_seen[group_key]: - group_seen[group_key].add(index) - if finding.muted: - groups[group_key]["Muted"] += 1 - elif finding.status == "FAIL": - groups[group_key]["FAIL"] += 1 - elif finding.status == "PASS": - groups[group_key]["PASS"] += 1 + status = "Muted" if finding.muted else effective_status + accumulate_overview_status( + index, status, pass_count, fail_count, muted_count + ) + accumulate_group_status( + index, status, groups[group_key], group_seen[group_key] + ) if not _print_overview( pass_count, fail_count, muted_count, compliance_framework_name, labels @@ -272,9 +273,11 @@ def _render_split( groups = {} group_muted_seen = {} group_split_seen = {} - pass_count = [] - fail_count = [] - muted_count = [] + pass_count = set() + fail_count = set() + muted_count = set() + audit_config = get_scan_audit_config() + config_status_cache = {} for index, finding in enumerate(findings): check_id = finding.check_metadata.CheckID @@ -282,6 +285,12 @@ def _render_split( continue for req in check_map[check_id]: + effective_status = get_effective_status( + finding.status, + resolve_requirement_config_status( + req, audit_config, config_status_cache, provider_type=provider + ), + ) for group_key in _get_group_key(req, group_by): if group_key not in groups: groups[group_key] = { @@ -289,33 +298,33 @@ def _render_split( } groups[group_key]["Muted"] = 0 group_muted_seen[group_key] = set() - group_split_seen[group_key] = {sv: set() for sv in split_values} + group_split_seen[group_key] = {sv: {} for sv in split_values} split_val = req.attributes.get(split_field, "") if finding.muted: # Overview total: count each finding once per framework - if index not in muted_count: - muted_count.append(index) + muted_count.add(index) # Per-group Muted: count each finding once per group it # belongs to (a finding can map to several groups). if index not in group_muted_seen[group_key]: group_muted_seen[group_key].add(index) groups[group_key]["Muted"] += 1 else: - if finding.status == "FAIL" and index not in fail_count: - fail_count.append(index) - elif finding.status == "PASS" and index not in pass_count: - pass_count.append(index) + if effective_status == "FAIL": + fail_count.add(index) + pass_count.discard(index) + elif effective_status == "PASS" and index not in fail_count: + pass_count.add(index) for sv in split_values: if sv in str(split_val): - if index not in group_split_seen[group_key][sv]: - group_split_seen[group_key][sv].add(index) - if finding.status == "FAIL": - groups[group_key][sv]["FAIL"] += 1 - else: - groups[group_key][sv]["PASS"] += 1 + accumulate_group_status( + index, + effective_status, + groups[group_key][sv], + group_split_seen[group_key][sv], + ) if not _print_overview( pass_count, fail_count, muted_count, compliance_framework_name, labels @@ -387,16 +396,18 @@ def _render_scored( weight_field = scoring.weight_field groups = {} group_seen = {} - pass_count = [] - fail_count = [] - muted_count = [] + pass_count = set() + fail_count = set() + muted_count = set() score_per_group = {} max_score_per_group = {} counted_per_group = {} generic_score = 0 max_generic_score = 0 - counted_generic = [] + counted_generic = {} + audit_config = get_scan_audit_config() + config_status_cache = {} for index, finding in enumerate(findings): check_id = finding.check_metadata.CheckID @@ -404,6 +415,12 @@ def _render_scored( continue for req in check_map[check_id]: + effective_status = get_effective_status( + finding.status, + resolve_requirement_config_status( + req, audit_config, config_status_cache, provider_type=provider + ), + ) for group_key in _get_group_key(req, group_by): attrs = req.attributes risk = attrs.get(risk_field, 0) @@ -411,44 +428,47 @@ def _render_scored( if group_key not in groups: groups[group_key] = {"FAIL": 0, "PASS": 0, "Muted": 0} - group_seen[group_key] = set() + group_seen[group_key] = {} score_per_group[group_key] = 0 max_score_per_group[group_key] = 0 - counted_per_group[group_key] = [] - - if index not in counted_per_group[group_key] and not finding.muted: - if finding.status == "PASS": - score_per_group[group_key] += risk * weight - max_score_per_group[group_key] += risk * weight - counted_per_group[group_key].append(index) + counted_per_group[group_key] = {} + + # Revoke an earlier PASS score if a later requirement FAILs. + if not finding.muted: + contribution = risk * weight + counted = counted_per_group[group_key] + if index not in counted: + max_score_per_group[group_key] += contribution + if effective_status == "PASS": + score_per_group[group_key] += contribution + counted[index] = contribution + else: + counted[index] = 0 + elif effective_status == "FAIL" and counted[index]: + score_per_group[group_key] -= counted[index] + counted[index] = 0 + + status = "Muted" if finding.muted else effective_status + accumulate_overview_status( + index, status, pass_count, fail_count, muted_count + ) + accumulate_group_status( + index, status, groups[group_key], group_seen[group_key] + ) - # Overview totals: count each finding once per framework - if finding.muted: - if index not in muted_count: - muted_count.append(index) - elif finding.status == "FAIL": - if index not in fail_count: - fail_count.append(index) - elif finding.status == "PASS": - if index not in pass_count: - pass_count.append(index) - - # Per-group counts: count each finding once per group it belongs - # to (a finding can map to several groups via several requirements). - if index not in group_seen[group_key]: - group_seen[group_key].add(index) - if finding.muted: - groups[group_key]["Muted"] += 1 - elif finding.status == "FAIL": - groups[group_key]["FAIL"] += 1 - elif finding.status == "PASS": - groups[group_key]["PASS"] += 1 - - if index not in counted_generic and not finding.muted: - if finding.status == "PASS": - generic_score += risk * weight - max_generic_score += risk * weight - counted_generic.append(index) + # Generic score, with the same PASS-revocation on FAIL. + if not finding.muted: + contribution = risk * weight + if index not in counted_generic: + max_generic_score += contribution + if effective_status == "PASS": + generic_score += contribution + counted_generic[index] = contribution + else: + counted_generic[index] = 0 + elif effective_status == "FAIL" and counted_generic[index]: + generic_score -= counted_generic[index] + counted_generic[index] = 0 if not _print_overview( pass_count, fail_count, muted_count, compliance_framework_name, labels diff --git a/tests/lib/check/compliance_config_constraint_model_test.py b/tests/lib/check/compliance_config_constraint_model_test.py new file mode 100644 index 00000000000..48f67fd504d --- /dev/null +++ b/tests/lib/check/compliance_config_constraint_model_test.py @@ -0,0 +1,169 @@ +"""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 diff --git a/tests/lib/check/compliance_config_eval_test.py b/tests/lib/check/compliance_config_eval_test.py new file mode 100644 index 00000000000..4acec9bb4c6 --- /dev/null +++ b/tests/lib/check/compliance_config_eval_test.py @@ -0,0 +1,408 @@ +from types import SimpleNamespace +from unittest.mock import patch + +import pytest + +from prowler.lib.check.compliance_config_eval import ( + CONFIG_NOT_VALID_PREFIX, + accumulate_group_status, + accumulate_overview_status, + apply_config_status, + build_requirement_config_status, + evaluate_config_constraints, + get_effective_status, + get_scan_audit_config, + get_scan_provider_type, + resolve_requirement_config_status, +) + +CONSTRAINTS = [ + { + "Check": "iam_user_accesskey_unused", + "ConfigKey": "max_unused_access_keys_days", + "Operator": "lte", + "Value": 45, + } +] + + +class Test_evaluate_config_constraints: + def test_no_constraints_is_compliant(self): + assert evaluate_config_constraints(None, {}) == (True, "") + assert evaluate_config_constraints([], {"x": 1}) == (True, "") + + def test_config_absent_assumes_default_ok(self): + # Key not explicitly set → default assumed adequate. + is_ok, reason = evaluate_config_constraints(CONSTRAINTS, {}) + assert is_ok is True + assert reason == "" + + def test_none_audit_config_is_compliant(self): + assert evaluate_config_constraints(CONSTRAINTS, None) == (True, "") + + def test_lte_satisfied(self): + assert evaluate_config_constraints( + CONSTRAINTS, {"max_unused_access_keys_days": 45} + ) == (True, "") + + def test_lte_violated(self): + is_ok, reason = evaluate_config_constraints( + CONSTRAINTS, {"max_unused_access_keys_days": 120} + ) + assert is_ok is False + # Product-facing message: names the check, the applied value, what the + # requirement needs and how to fix it, in plain language. + assert reason.startswith(CONFIG_NOT_VALID_PREFIX) + assert "iam_user_accesskey_unused" in reason + assert "max_unused_access_keys_days" in reason + assert "set to 120" in reason + assert "45 or lower" in reason + + def test_gte_operator(self): + c = [{"Check": "c", "ConfigKey": "k", "Operator": "gte", "Value": 10}] + assert evaluate_config_constraints(c, {"k": 10})[0] is True + assert evaluate_config_constraints(c, {"k": 9})[0] is False + + def test_eq_operator(self): + c = [{"Check": "c", "ConfigKey": "k", "Operator": "eq", "Value": "HIGH"}] + assert evaluate_config_constraints(c, {"k": "HIGH"})[0] is True + assert evaluate_config_constraints(c, {"k": "LOW"})[0] is False + + def test_in_operator(self): + c = [{"Check": "c", "ConfigKey": "k", "Operator": "in", "Value": [1, 2, 3]}] + assert evaluate_config_constraints(c, {"k": 2})[0] is True + assert evaluate_config_constraints(c, {"k": 9})[0] is False + + def test_subset_operator_allowlist(self): + # Allowlist config: applied list must stay within the secure baseline. + c = [ + { + "Check": "sqlserver_recommended_minimal_tls_version", + "ConfigKey": "recommended_minimal_tls_versions", + "Operator": "subset", + "Value": ["1.2", "1.3"], + } + ] + assert ( + evaluate_config_constraints( + c, {"recommended_minimal_tls_versions": ["1.2", "1.3"]} + )[0] + is True + ) + # Stricter (subset) still passes. + assert ( + evaluate_config_constraints( + c, {"recommended_minimal_tls_versions": ["1.3"]} + )[0] + is True + ) + # Widening with a weaker value breaks it. + is_ok, reason = evaluate_config_constraints( + c, {"recommended_minimal_tls_versions": ["1.0", "1.2", "1.3"]} + ) + assert is_ok is False + assert "recommended_minimal_tls_versions" in reason + + def test_superset_operator_denylist(self): + # Denylist config: applied list must keep covering the forbidden baseline. + c = [ + { + "Check": "acm_certificates_with_secure_key_algorithms", + "ConfigKey": "insecure_key_algorithms", + "Operator": "superset", + "Value": ["RSA-1024", "P-192"], + } + ] + assert ( + evaluate_config_constraints( + c, {"insecure_key_algorithms": ["RSA-1024", "P-192"]} + )[0] + is True + ) + # Extra forbidden values are fine. + assert ( + evaluate_config_constraints( + c, {"insecure_key_algorithms": ["RSA-1024", "P-192", "P-224"]} + )[0] + is True + ) + # Removing a forbidden value breaks it. + assert ( + evaluate_config_constraints(c, {"insecure_key_algorithms": ["P-192"]})[0] + is False + ) + + def test_subset_superset_non_list_not_satisfied(self): + sub = [{"Check": "c", "ConfigKey": "k", "Operator": "subset", "Value": ["a"]}] + sup = [{"Check": "c", "ConfigKey": "k", "Operator": "superset", "Value": ["a"]}] + # A scalar applied value cannot satisfy a set constraint. + assert evaluate_config_constraints(sub, {"k": "a"})[0] is False + assert evaluate_config_constraints(sup, {"k": "a"})[0] is False + + def test_mismatched_types_not_satisfied(self): + assert ( + evaluate_config_constraints( + CONSTRAINTS, {"max_unused_access_keys_days": "x"} + )[0] + is False + ) + + def test_multiple_constraints_first_violation_reported(self): + constraints = [ + {"Check": "a", "ConfigKey": "k1", "Operator": "lte", "Value": 45}, + {"Check": "b", "ConfigKey": "k2", "Operator": "lte", "Value": 45}, + ] + is_ok, reason = evaluate_config_constraints(constraints, {"k1": 45, "k2": 90}) + assert is_ok is False + # The first violation (check "b", key "k2", applied 90) is the one reported. + assert "k2" in reason + assert "set to 90" in reason + + +class Test_provider_scoping: + # An AWS-scoped constraint on a config key whose value is too loose. + AWS_CONSTRAINT = [ + { + "Check": "securityhub_enabled", + "Provider": "aws", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": False, + } + ] + + def test_applies_when_provider_matches(self): + is_ok, _ = evaluate_config_constraints( + self.AWS_CONSTRAINT, {"mute_non_default_regions": True}, "aws" + ) + assert is_ok is False + + def test_skipped_when_provider_differs(self): + # Same loose value, but scanning GCP → the AWS constraint must not fire. + is_ok, reason = evaluate_config_constraints( + self.AWS_CONSTRAINT, {"mute_non_default_regions": True}, "gcp" + ) + assert is_ok is True + assert reason == "" + + def test_none_provider_type_disables_scoping(self): + # Without a known provider every constraint is evaluated (legacy default). + is_ok, _ = evaluate_config_constraints( + self.AWS_CONSTRAINT, {"mute_non_default_regions": True}, None + ) + assert is_ok is False + + def test_provider_match_is_case_insensitive(self): + # A constraint authored as "AWS" must still scope to the "aws" scan, + # not be silently bypassed by a casing mismatch. + constraint = [ + { + "Check": "securityhub_enabled", + "Provider": "AWS", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": False, + } + ] + is_ok, _ = evaluate_config_constraints( + constraint, {"mute_non_default_regions": True}, "aws" + ) + assert is_ok is False + + def test_untagged_constraint_applies_to_any_provider(self): + # Single-provider frameworks omit Provider → always evaluated. + is_ok, _ = evaluate_config_constraints( + CONSTRAINTS, {"max_unused_access_keys_days": 120}, "aws" + ) + assert is_ok is False + + +# A constraint forcing FAIL when the applied value is too loose. +REGION_CONSTRAINT = [ + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": False, + } +] + + +def _legacy_req(req_id, constraints=None): + """Fake legacy Compliance_Requirement (``Id`` / ``ConfigRequirements``).""" + return SimpleNamespace(Id=req_id, ConfigRequirements=constraints) + + +def _universal_req(req_id, constraints=None): + """Fake UniversalComplianceRequirement (``id`` / ``config_requirements``).""" + return SimpleNamespace(id=req_id, config_requirements=constraints) + + +class Test_build_requirement_config_status: + def test_only_requirements_with_constraints_included(self): + reqs = [_legacy_req("1", CONSTRAINTS), _legacy_req("2", None)] + status = build_requirement_config_status( + reqs, {"max_unused_access_keys_days": 120} + ) + assert set(status) == {"1"} + assert status["1"][0] is False + + def test_supports_universal_requirements(self): + reqs = [_universal_req("u1", REGION_CONSTRAINT)] + status = build_requirement_config_status( + reqs, {"mute_non_default_regions": True} + ) + assert status["u1"][0] is False + + def test_compliant_when_config_satisfied(self): + reqs = [_legacy_req("1", CONSTRAINTS)] + status = build_requirement_config_status( + reqs, {"max_unused_access_keys_days": 30} + ) + assert status["1"] == (True, "") + + +class Test_resolve_requirement_config_status: + def test_memoises_by_requirement_id(self): + cache = {} + req = _legacy_req("1", CONSTRAINTS) + first = resolve_requirement_config_status( + req, {"max_unused_access_keys_days": 120}, cache + ) + assert cache["1"] is first + assert first[0] is False + # A different audit_config is ignored once cached (intended for one build). + second = resolve_requirement_config_status(req, {}, cache) + assert second is first + + def test_requirement_without_constraints_is_ok(self): + cache = {} + req = _legacy_req("1", None) + assert resolve_requirement_config_status(req, {}, cache) == (True, "") + + +class Test_accumulate_overview_status: + def test_fail_wins_over_earlier_pass(self): + p, f, m = set(), set(), set() + accumulate_overview_status(0, "PASS", p, f, m) + accumulate_overview_status(0, "FAIL", p, f, m) + assert (p, f, m) == (set(), {0}, set()) + + def test_pass_after_fail_does_not_double_count(self): + p, f, m = set(), set(), set() + accumulate_overview_status(0, "FAIL", p, f, m) + accumulate_overview_status(0, "PASS", p, f, m) + assert (p, f, m) == (set(), {0}, set()) + + def test_pass_only(self): + p, f, m = set(), set(), set() + accumulate_overview_status(0, "PASS", p, f, m) + assert (p, f, m) == ({0}, set(), set()) + + def test_muted(self): + p, f, m = set(), set(), set() + accumulate_overview_status(0, "Muted", p, f, m) + assert (p, f, m) == (set(), set(), {0}) + + +class Test_accumulate_group_status: + def test_first_status_counted(self): + counts = {"FAIL": 0, "PASS": 0, "Muted": 0} + seen = {} + accumulate_group_status(0, "PASS", counts, seen) + assert counts == {"FAIL": 0, "PASS": 1, "Muted": 0} + assert seen == {0: "PASS"} + + def test_pass_upgraded_to_fail(self): + counts = {"FAIL": 0, "PASS": 0, "Muted": 0} + seen = {} + accumulate_group_status(0, "PASS", counts, seen) + accumulate_group_status(0, "FAIL", counts, seen) + assert counts == {"FAIL": 1, "PASS": 0, "Muted": 0} + assert seen == {0: "FAIL"} + + def test_fail_not_downgraded_by_later_pass(self): + counts = {"FAIL": 0, "PASS": 0, "Muted": 0} + seen = {} + accumulate_group_status(0, "FAIL", counts, seen) + accumulate_group_status(0, "PASS", counts, seen) + assert counts == {"FAIL": 1, "PASS": 0, "Muted": 0} + + def test_same_index_not_double_counted(self): + counts = {"FAIL": 0, "PASS": 0, "Muted": 0} + seen = {} + accumulate_group_status(0, "PASS", counts, seen) + accumulate_group_status(0, "PASS", counts, seen) + assert counts["PASS"] == 1 + + def test_works_with_fail_pass_only_counts(self): + # Level-style counts (no "Muted" key) used by CIS / split tables. + counts = {"FAIL": 0, "PASS": 0} + seen = {} + accumulate_group_status(0, "PASS", counts, seen) + accumulate_group_status(0, "FAIL", counts, seen) + assert counts == {"FAIL": 1, "PASS": 0} + + def test_muted_on_fail_pass_only_counts_raises(self): + # Level-style callers only ever pass PASS/FAIL (they guard on + # ``not finding.muted``). Passing "Muted" to a Muted-less counts must + # fail loudly rather than silently create a bogus key. + counts = {"FAIL": 0, "PASS": 0} + with pytest.raises(KeyError): + accumulate_group_status(0, "Muted", counts, {}) + + +class Test_apply_config_status: + def test_none_config_status_keeps_finding(self): + assert apply_config_status("PASS", "ext", None) == ("PASS", "ext") + + def test_compliant_keeps_finding(self): + assert apply_config_status("PASS", "ext", (True, "")) == ("PASS", "ext") + + def test_invalid_config_forces_fail_and_prepends_reason(self): + # The reason already carries the full product-facing message; it is + # prepended verbatim to the finding's extended status. + reason = f"{CONFIG_NOT_VALID_PREFIX} bad config" + status, extended = apply_config_status("PASS", "ext", (False, reason)) + assert status == "FAIL" + assert extended.startswith(CONFIG_NOT_VALID_PREFIX) + assert reason in extended + assert "ext" in extended + + +class Test_get_effective_status: + def test_none_and_compliant_keep_status(self): + assert get_effective_status("PASS", None) == "PASS" + assert get_effective_status("PASS", (True, "")) == "PASS" + + def test_invalid_config_forces_fail(self): + assert get_effective_status("PASS", (False, "reason")) == "FAIL" + + +class Test_get_scan_audit_config: + def test_returns_empty_without_global_provider(self): + # No global provider set → get_global_provider() returns None → + # ``None.audit_config`` raises AttributeError → safe empty mapping. + with patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=None, + ): + assert get_scan_audit_config() == {} + + +class Test_get_scan_provider_type: + def test_returns_empty_when_no_global_provider(self): + # No global provider set → get_global_provider() returns None → + # ``None.type`` raises AttributeError → scoping disabled (empty string). + with patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=None, + ): + assert get_scan_provider_type() == "" + + def test_returns_global_provider_type(self): + with patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=SimpleNamespace(type="aws"), + ): + assert get_scan_provider_type() == "aws" diff --git a/tests/lib/check/compliance_config_requirements_data_test.py b/tests/lib/check/compliance_config_requirements_data_test.py new file mode 100644 index 00000000000..3b3ba5757fe --- /dev/null +++ b/tests/lib/check/compliance_config_requirements_data_test.py @@ -0,0 +1,191 @@ +"""Data-integrity tests for every ``ConfigRequirements`` declared in the shipped +compliance framework JSONs. + +These guard the ~700 constraints added across the frameworks against drift: +- every constraint is well-formed (valid operator, value typed for its operator), +- every constraint targets a check the requirement actually maps (no orphans), +- the region-mute invariant holds (every requirement mapping a region-scoped + check carries the ``mute_non_default_regions == false`` constraint), +- every framework still parses through its model. +""" + +import glob +import json +import pathlib + +import pytest + +from prowler.lib.check.compliance_models import Compliance, ComplianceFramework + +_REPO_ROOT = pathlib.Path(__file__).resolve().parents[3] +_COMPLIANCE_DIR = _REPO_ROOT / "prowler" / "compliance" + +_VALID_OPERATORS = {"lte", "gte", "eq", "in", "subset", "superset"} +# Checks whose result is untrustworthy when non-default regions are muted. +_REGION_CHECKS = { + "accessanalyzer_enabled", + "config_recorder_all_regions_enabled", + "drs_job_exist", + "guardduty_delegated_admin_enabled_all_regions", + "guardduty_is_enabled", + "securityhub_enabled", +} + +_ALL_FILES = sorted(glob.glob(str(_COMPLIANCE_DIR / "**" / "*.json"), recursive=True)) + + +def _load(path): + with open(path, encoding="utf-8") as f: + return json.load(f) + + +def _requirements(data): + return data.get("Requirements") or data.get("requirements") or [] + + +def _req_id(req): + return req.get("Id") or req.get("id") + + +def _req_checks(req): + ch = req.get("Checks", req.get("checks")) + checks = set() + if isinstance(ch, dict): + for v in ch.values(): + checks |= set(v or []) + elif isinstance(ch, list): + checks |= set(ch) + return checks + + +def _req_constraints(req): + return req.get("ConfigRequirements") or req.get("config_requirements") or [] + + +def _iter_constraints(): + """Yield (file, req_id, checks, constraint) for every constraint shipped.""" + for path in _ALL_FILES: + data = _load(path) + for req in _requirements(data): + checks = _req_checks(req) + for c in _req_constraints(req): + yield pathlib.Path(path).name, _req_id(req), checks, c + + +_ALL_CONSTRAINTS = list(_iter_constraints()) + + +def test_there_are_constraints_to_validate(): + # Guards against the iteration silently finding nothing (e.g. path change). + assert len(_ALL_CONSTRAINTS) > 100 + + +@pytest.mark.parametrize( + "fname,req_id,checks,constraint", + _ALL_CONSTRAINTS, + ids=[f"{f}:{r}:{c['Check']}" for f, r, _, c in _ALL_CONSTRAINTS], +) +class Test_Constraint_Wellformed: + def test_has_required_keys(self, fname, req_id, checks, constraint): + required = {"Check", "ConfigKey", "Operator", "Value"} + # ``Provider`` is optional (universal frameworks set it, single-provider + # ones omit it); no other key is allowed. + assert required <= set(constraint) <= required | { + "Provider" + }, f"{fname}:{req_id} malformed constraint {constraint}" + + def test_operator_valid(self, fname, req_id, checks, constraint): + assert constraint["Operator"] in _VALID_OPERATORS + + def test_check_is_mapped_by_requirement(self, fname, req_id, checks, constraint): + # No orphan constraints: the target check must be one the requirement runs. + assert constraint["Check"] in checks, ( + f"{fname}:{req_id} constraint targets {constraint['Check']} " + f"which the requirement does not map" + ) + + def test_value_type_matches_operator(self, fname, req_id, checks, constraint): + op, val = constraint["Operator"], constraint["Value"] + if op in ("subset", "superset", "in"): + assert isinstance(val, list), f"{fname}:{req_id} {op} needs a list value" + elif op in ("lte", "gte"): + # Numeric threshold; bool is not a valid threshold even though it is + # an int subclass. + assert isinstance(val, (int, float)) and not isinstance( + val, bool + ), f"{fname}:{req_id} {op} needs a numeric value, got {val!r}" + elif op == "eq": + assert isinstance( + val, (bool, int, float, str) + ), f"{fname}:{req_id} eq needs a scalar value" + + +class Test_Region_Mute_Invariant: + """Every requirement mapping a region-scoped check must carry the + ``mute_non_default_regions == false`` constraint for it.""" + + def test_region_checks_always_constrained(self): + gaps = [] + for path in _ALL_FILES: + data = _load(path) + for req in _requirements(data): + checks = _req_checks(req) + constrained = { + c["Check"] + for c in _req_constraints(req) + if c["ConfigKey"] == "mute_non_default_regions" + } + for region_check in checks & _REGION_CHECKS: + if region_check not in constrained: + gaps.append( + f"{pathlib.Path(path).name}:{_req_id(req)}:{region_check}" + ) + assert not gaps, f"region-mute constraint missing for: {gaps}" + + def test_region_mute_constraints_use_eq_false(self): + for fname, req_id, _checks, c in _ALL_CONSTRAINTS: + if c["ConfigKey"] == "mute_non_default_regions": + assert ( + c["Operator"] == "eq" and c["Value"] is False + ), f"{fname}:{req_id} region-mute must be eq false" + + +class Test_Universal_Provider_Scoping: + """Universal (multi-provider) frameworks map checks per provider, so every + constraint must declare which provider it scopes to and that provider must + actually map the targeted check. Without this a constraint authored for one + provider's check would wrongly apply to scans of every other provider.""" + + def test_multiprovider_constraints_declare_consistent_provider(self): + gaps = [] + for path in _ALL_FILES: + data = _load(path) + for req in _requirements(data): + ch = req.get("Checks", req.get("checks")) + # Only universal frameworks key their checks by provider. + if not isinstance(ch, dict): + continue + for c in _req_constraints(req): + provider = c.get("Provider") + if not provider: + gaps.append( + f"{pathlib.Path(path).name}:{_req_id(req)}:" + f"{c['Check']} missing Provider" + ) + elif c["Check"] not in set(ch.get(provider, [])): + gaps.append( + f"{pathlib.Path(path).name}:{_req_id(req)}:" + f"{c['Check']} not mapped under provider {provider}" + ) + assert not gaps, f"universal constraints with bad Provider: {gaps}" + + +@pytest.mark.parametrize( + "path", _ALL_FILES, ids=[pathlib.Path(p).name for p in _ALL_FILES] +) +def test_every_framework_parses_with_constraints(path): + data = _load(path) + if "Requirements" in data: + Compliance(**data) + else: + ComplianceFramework.parse_obj(data) diff --git a/tests/lib/check/mitre_config_requirements_test.py b/tests/lib/check/mitre_config_requirements_test.py new file mode 100644 index 00000000000..ef3b33a17fe --- /dev/null +++ b/tests/lib/check/mitre_config_requirements_test.py @@ -0,0 +1,148 @@ +"""Regression coverage for ConfigRequirements on MITRE requirements. + +``mitre_attack_aws.json`` declares ``ConfigRequirements`` on its requirements, +but ``Mitre_Requirement`` historically did not define the field, so Pydantic +silently dropped the constraints during MITRE parsing and the config validation +logic never saw them. These tests prove the constraints survive parsing and that +a violated MITRE config requirement forces the compliance result to FAIL through +the universal output path. +""" + +from datetime import datetime, timezone +from types import SimpleNamespace +from unittest.mock import patch + +from py_ocsf_models.objects.compliance_status import StatusID as ComplianceStatusID + +from prowler.lib.check.compliance_models import ( + Compliance, + Mitre_Requirement, + adapt_legacy_to_universal, +) +from prowler.lib.outputs.compliance.universal.ocsf_compliance import ( + OCSFComplianceOutput, +) + +_MODULE = "prowler.providers.common.provider.Provider.get_global_provider" + + +def _mitre_compliance(check_id): + """A minimal one-requirement MITRE framework with a config constraint.""" + return Compliance( + Framework="MITRE-ATTACK", + Name="MITRE ATT&CK", + Provider="AWS", + Version="", + Description="Test MITRE framework", + Requirements=[ + { + "Name": "Test Technique", + "Id": "T9999", + "Tactics": ["initial-access"], + "SubTechniques": [], + "Platforms": ["AWS"], + "Description": "Requirement T9999", + "TechniqueURL": "https://attack.mitre.org/techniques/T9999", + "Checks": [check_id], + "ConfigRequirements": [ + { + "Check": check_id, + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": False, + } + ], + "Attributes": [ + { + "AWSService": "service", + "Category": "category", + "Value": "value", + "Comment": "comment", + } + ], + } + ], + ) + + +def _finding(check_id, status="PASS", provider="aws"): + finding = SimpleNamespace() + finding.provider = provider + finding.account_uid = "123456789012" + finding.account_name = "test-account" + finding.account_organization_uid = "org-123" + finding.account_organization_name = "test-org" + finding.region = "us-east-1" + finding.status = status + finding.status_extended = f"{check_id} is {status}" + finding.resource_uid = f"arn:aws:iam::123456789012:{check_id}" + finding.resource_name = check_id + finding.resource_details = "details" + finding.resource_metadata = {} + finding.resource_tags = {"Name": "test"} + finding.partition = "aws" + finding.muted = False + finding.check_id = check_id + finding.uid = "test-finding-uid" + finding.timestamp = datetime(2025, 1, 15, 12, 0, 0, tzinfo=timezone.utc) + finding.prowler_version = "5.0.0" + finding.metadata = SimpleNamespace( + CheckID=check_id, + CheckTitle=f"Title for {check_id}", + Description=f"Description for {check_id}", + Severity="medium", + ServiceName="iam", + ResourceType="aws-iam-role", + ) + return finding + + +class Test_Mitre_Config_Requirements: + def test_config_requirements_survive_mitre_parsing(self): + """Real mitre_attack_aws.json constraints must not be dropped on parse.""" + compliance = Compliance.parse_file( + "prowler/compliance/aws/mitre_attack_aws.json" + ) + requirement = next(r for r in compliance.Requirements if r.Id == "T1190") + assert isinstance(requirement, Mitre_Requirement) + assert requirement.ConfigRequirements + # And they propagate through the legacy -> universal adapter unchanged. + universal = adapt_legacy_to_universal(compliance) + universal_requirement = next( + r for r in universal.requirements if r.id == "T1190" + ) + assert universal_requirement.config_requirements + assert len(universal_requirement.config_requirements) == len( + requirement.ConfigRequirements + ) + + def test_violating_mitre_config_forces_fail(self): + """A PASS finding becomes FAIL when the MITRE config constraint is violated.""" + check_id = "drs_job_exist" + framework = adapt_legacy_to_universal(_mitre_compliance(check_id)) + findings = [_finding(check_id, "PASS")] + with patch(_MODULE) as mock_gp: + mock_gp.return_value.audit_config = {"mute_non_default_regions": True} + out = OCSFComplianceOutput( + findings=findings, framework=framework, provider="aws" + ) + event = out.data[0] + assert event.compliance.status_id == ComplianceStatusID.Fail + assert event.status_code == "FAIL" + assert "Configuration not valid" in event.message + # The nested Check object keeps the real (raw) finding status. + assert event.compliance.checks[0].status == "PASS" + + def test_valid_mitre_config_keeps_pass(self): + check_id = "drs_job_exist" + framework = adapt_legacy_to_universal(_mitre_compliance(check_id)) + findings = [_finding(check_id, "PASS")] + with patch(_MODULE) as mock_gp: + mock_gp.return_value.audit_config = {"mute_non_default_regions": False} + out = OCSFComplianceOutput( + findings=findings, framework=framework, provider="aws" + ) + event = out.data[0] + assert event.compliance.status_id == ComplianceStatusID.Pass + assert event.status_code == "PASS" + assert "Configuration not valid" not in event.message diff --git a/tests/lib/outputs/compliance/cis/cis_aws_config_requirements_test.py b/tests/lib/outputs/compliance/cis/cis_aws_config_requirements_test.py new file mode 100644 index 00000000000..28cc3d2e1a4 --- /dev/null +++ b/tests/lib/outputs/compliance/cis/cis_aws_config_requirements_test.py @@ -0,0 +1,89 @@ +"""Integration coverage for requirement-level config validation in the CIS AWS +CSV output. Requirement CIS 6.0 AWS 2.11 maps two configurable checks; when the +scan config is looser than the requirement demands, the requirement row must be +FAIL even if the underlying finding is PASS. The applied config is read from the +active provider's ``audit_config``.""" + +import json +import pathlib +from types import SimpleNamespace +from unittest.mock import patch + +from prowler.lib.check.compliance_models import Compliance +from prowler.lib.outputs.compliance.cis.cis_aws import AWSCIS + +_REPO_ROOT = pathlib.Path(__file__).resolve().parents[5] +_CIS_6_0 = _REPO_ROOT / "prowler" / "compliance" / "aws" / "cis_6.0_aws.json" + + +def _load_cis_60() -> Compliance: + return Compliance(**json.load(open(_CIS_6_0))) + + +def _finding(check_id: str, status: str): + return SimpleNamespace( + provider="aws", + account_uid="123456789012", + region="us-east-1", + check_id=check_id, + status=status, + status_extended=f"{check_id} {status}", + resource_uid="arn:aws:iam::123456789012:user/bob", + resource_name="bob", + muted=False, + ) + + +def _rows_for(requirement_id, findings, audit_config): + with patch( + "prowler.providers.common.provider.Provider.get_global_provider" + ) as mock_gp: + mock_gp.return_value.audit_config = audit_config + out = AWSCIS(findings=findings, compliance=_load_cis_60(), file_path=None) + return [r for r in out._data if r.Requirements_Id == requirement_id] + + +class Test_CIS_AWS_Config_Requirements: + def test_loose_config_forces_requirement_fail(self): + findings = [_finding("iam_user_accesskey_unused", "PASS")] + rows = _rows_for("2.11", findings, {"max_unused_access_keys_days": 120}) + assert rows, "expected a row for requirement 2.11" + assert all(r.Status == "FAIL" for r in rows) + assert all("Configuration not valid" in r.StatusExtended for r in rows) + + def test_valid_config_keeps_finding_status(self): + findings = [_finding("iam_user_accesskey_unused", "PASS")] + rows = _rows_for("2.11", findings, {"max_unused_access_keys_days": 45}) + assert rows + assert all(r.Status == "PASS" for r in rows) + assert all("Configuration not valid" not in r.StatusExtended for r in rows) + + def test_absent_config_assumes_default_ok(self): + findings = [_finding("iam_user_accesskey_unused", "PASS")] + rows = _rows_for("2.11", findings, {}) + assert rows + assert all(r.Status == "PASS" for r in rows) + + def test_other_requirements_unaffected(self): + # A finding for a check without ConfigRequirements keeps its status even + # when the config is loose for a different requirement. + findings = [_finding("iam_rotate_access_key_90_days", "PASS")] + rows = _rows_for("2.13", findings, {"max_unused_access_keys_days": 120}) + assert rows + assert all(r.Status == "PASS" for r in rows) + + def test_region_mute_constraint_forces_fail(self): + # Requirement 5.16 maps securityhub_enabled with a + # mute_non_default_regions == false constraint: muting non-default + # regions makes the PASS untrustworthy, so the row must be FAIL. + findings = [_finding("securityhub_enabled", "PASS")] + rows = _rows_for("5.16", findings, {"mute_non_default_regions": True}) + assert rows, "expected a row for requirement 5.16" + assert all(r.Status == "FAIL" for r in rows) + assert all("Configuration not valid" in r.StatusExtended for r in rows) + + def test_region_mute_constraint_default_passes(self): + findings = [_finding("securityhub_enabled", "PASS")] + rows = _rows_for("5.16", findings, {"mute_non_default_regions": False}) + assert rows + assert all(r.Status == "PASS" for r in rows) diff --git a/tests/lib/outputs/compliance/cis/cis_azure_config_requirements_test.py b/tests/lib/outputs/compliance/cis/cis_azure_config_requirements_test.py new file mode 100644 index 00000000000..a34402ec77a --- /dev/null +++ b/tests/lib/outputs/compliance/cis/cis_azure_config_requirements_test.py @@ -0,0 +1,75 @@ +"""Integration coverage for the ``subset`` set-operator in a CSV output. + +CIS Azure 5.0 requirement 9.1.3 maps ``storage_smb_channel_encryption_with_secure_algorithm`` +with a ``recommended_smb_channel_encryption_algorithms subset ["AES-256-GCM"]`` +constraint: widening the allowlist with a weaker algorithm makes the PASS +untrustworthy, so the requirement row must be FAIL. Exercises the shared override +path through a per-provider CSV class (not just OCSF).""" + +import json +import pathlib +from types import SimpleNamespace +from unittest.mock import patch + +from prowler.lib.check.compliance_models import Compliance +from prowler.lib.outputs.compliance.cis.cis_azure import AzureCIS + +_REPO_ROOT = pathlib.Path(__file__).resolve().parents[5] +_CIS_5_0_AZURE = _REPO_ROOT / "prowler" / "compliance" / "azure" / "cis_5.0_azure.json" +_REQUIREMENT_ID = "9.1.3" +_CHECK = "storage_smb_channel_encryption_with_secure_algorithm" + + +def _load(): + return Compliance(**json.load(open(_CIS_5_0_AZURE))) + + +def _finding(check_id, status): + return SimpleNamespace( + provider="azure", + account_uid="00000000-0000-0000-0000-000000000000", + region="eastus", + check_id=check_id, + status=status, + status_extended=f"{check_id} {status}", + resource_uid="/subscriptions/x/storageAccounts/sa", + resource_name="sa", + muted=False, + ) + + +def _rows_for(audit_config): + findings = [_finding(_CHECK, "PASS")] + with patch( + "prowler.providers.common.provider.Provider.get_global_provider" + ) as mock_gp: + mock_gp.return_value.audit_config = audit_config + out = AzureCIS(findings=findings, compliance=_load(), file_path=None) + return [r for r in out._data if r.Requirements_Id == _REQUIREMENT_ID] + + +class Test_CIS_Azure_Subset_Constraint: + def test_widened_allowlist_forces_fail(self): + rows = _rows_for( + { + "recommended_smb_channel_encryption_algorithms": [ + "AES-128-CCM", + "AES-256-GCM", + ] + } + ) + assert rows, f"expected a row for requirement {_REQUIREMENT_ID}" + assert all(r.Status == "FAIL" for r in rows) + assert all("Configuration not valid" in r.StatusExtended for r in rows) + + def test_secure_allowlist_keeps_pass(self): + rows = _rows_for( + {"recommended_smb_channel_encryption_algorithms": ["AES-256-GCM"]} + ) + assert rows + assert all(r.Status == "PASS" for r in rows) + + def test_absent_config_keeps_pass(self): + rows = _rows_for({}) + assert rows + assert all(r.Status == "PASS" for r in rows) diff --git a/tests/lib/outputs/compliance/config_status_dispatch_coverage_test.py b/tests/lib/outputs/compliance/config_status_dispatch_coverage_test.py new file mode 100644 index 00000000000..a4f09551a53 --- /dev/null +++ b/tests/lib/outputs/compliance/config_status_dispatch_coverage_test.py @@ -0,0 +1,168 @@ +"""End-to-end coverage: every shipped framework that declares ``ConfigRequirements`` +must apply the config-status override through the *real* table dispatcher. + +The companion ``config_status_renderer_coverage_test`` proves no renderer file +ignores the override. This test closes the other half of the gap that let +``okta_idaas_stig`` ship ConfigRequirements its renderers never applied: it walks +every per-provider compliance JSON that declares constraints, routes a synthetic +PASS finding through ``display_compliance_table`` exactly as a scan would, and +asserts the requirement is forced to FAIL when the scan's config is too loose. + +It runs each framework twice — once with a config that *violates* the first +constraint and once with a config that *satisfies* it — and asserts the violating +run reports strictly more failures. Comparing the two runs is language-neutral +(only the parenthesised counts are read, never the localized PASS/FAIL labels) and +self-checking (a renderer that ignored the override would report equal counts). + +Universal (multi-provider) frameworks render through a different path and are +covered by ``universal/universal_table_config_requirements_test.py``. +""" + +import glob +import io +import json +import pathlib +import re +import tempfile +from contextlib import redirect_stdout +from types import SimpleNamespace +from unittest.mock import patch + +import pytest + +from prowler.lib.check.compliance_models import Compliance +from prowler.lib.outputs.compliance.compliance import display_compliance_table + +_REPO_ROOT = pathlib.Path(__file__).resolve().parents[4] +_COMPLIANCE_DIR = _REPO_ROOT / "prowler" / "compliance" + +# Per-provider JSONs live in a provider subdir; top-level files are universal. +_PROVIDER_JSONS = sorted(glob.glob(str(_COMPLIANCE_DIR / "*" / "*.json"))) + + +def _first_constraint(data): + """Return ``(check, config_key, operator, value)`` of the first declared + constraint, or ``None`` when the framework declares none.""" + for requirement in data.get("Requirements", []): + constraints = requirement.get("ConfigRequirements") + if constraints: + c = constraints[0] + return c["Check"], c["ConfigKey"], c["Operator"], c["Value"] + return None + + +def _violating_value(operator, value): + """A config value that breaks the constraint (forces the requirement FAIL).""" + if operator == "lte": + return value + 1 + if operator == "gte": + return value - 1 + if operator == "eq": + if isinstance(value, bool): + return not value + if isinstance(value, (int, float)): + return value + 1 + return f"{value}__violates__" + if operator == "in": + return "__not_in_allowed_set__" + if operator == "subset": + return list(value) + ["__extra_not_allowed__"] + if operator == "superset": + return [] + raise AssertionError(f"unhandled operator {operator}") + + +def _satisfying_value(operator, value): + """A config value that satisfies the constraint (requirement keeps its status).""" + if operator in ("lte", "gte", "eq"): + return value + if operator == "in": + return value[0] + if operator == "subset": + return list(value) + if operator == "superset": + return list(value) + raise AssertionError(f"unhandled operator {operator}") + + +def _fail_count(findings, bulk, name, provider, applied_config): + """Render the framework table with ``applied_config`` and return the FAIL + count from the overview, or ``None`` when the table renders nothing.""" + + def _not_implemented(*_a, **_k): + raise NotImplementedError + + fake_provider = SimpleNamespace( + audit_config=applied_config, + type=provider, + display_compliance_table=_not_implemented, + ) + buffer = io.StringIO() + with tempfile.TemporaryDirectory() as tmp: + with patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=fake_provider, + ): + with redirect_stdout(buffer): + display_compliance_table(findings, bulk, name, "output", tmp, False) + plain = re.sub(r"\x1b\[[0-9;]*m", "", buffer.getvalue()) + # The overview's first parenthesised count is always the FAIL tally, in + # every renderer and every locale (only the label is translated). + counts = re.findall(r"\(\s*(\d+)\s*\)", plain) + return int(counts[0]) if counts else None + + +def _frameworks_with_constraints(): + for path in _PROVIDER_JSONS: + with open(path, encoding="utf-8") as f: + data = json.load(f) + if _first_constraint(data): + name = pathlib.Path(path).stem + yield pytest.param(path, data, id=name) + + +@pytest.mark.parametrize("path, data", list(_frameworks_with_constraints())) +def test_framework_constraints_force_fail_through_dispatcher(path, data): + provider = pathlib.Path(path).parent.name + name = pathlib.Path(path).stem + check, config_key, operator, value = _first_constraint(data) + compliance = Compliance(**data) + + def _finding(): + return SimpleNamespace( + check_metadata=SimpleNamespace(CheckID=check), + check_id=check, + status="PASS", + muted=False, + ) + + # Two findings: the renderers only print the table when more than one + # finding maps to the framework. + findings = [_finding(), _finding()] + bulk = {check: SimpleNamespace(Compliance=[compliance])} + + strict = _fail_count( + findings, bulk, name, provider, {config_key: _satisfying_value(operator, value)} + ) + loose = _fail_count( + findings, bulk, name, provider, {config_key: _violating_value(operator, value)} + ) + + if strict is None and loose is None: + # The framework's renderer gates rendering on its name/version and does + # not paint a table for this framework id. There is no status to assert + # here; the renderer itself is still proven config-aware by + # config_status_renderer_coverage_test. Surfaced rather than silently + # passed so the skip is visible. + pytest.skip(f"{name}: renderer paints no table for this framework id") + + assert strict == 0, ( + f"{name}: PASS findings reported {strict} failures with a compliant " + "config; the control run should be clean." + ) + assert loose and loose > 0, ( + f"{name}: a PASS finding whose requirement maps {check} ran with " + f"{config_key} too loose for the constraint ({operator} {value}) was NOT " + "forced to FAIL. The framework declares ConfigRequirements its renderer " + "fails to apply — wire it through the config-status helpers." + ) diff --git a/tests/lib/outputs/compliance/config_status_renderer_coverage_test.py b/tests/lib/outputs/compliance/config_status_renderer_coverage_test.py new file mode 100644 index 00000000000..ac0981c941a --- /dev/null +++ b/tests/lib/outputs/compliance/config_status_renderer_coverage_test.py @@ -0,0 +1,71 @@ +"""Guard that every dedicated compliance renderer applies the config-status rule. + +Declaring ``ConfigRequirements`` in a framework JSON is inert unless the renderer +that builds its output actually evaluates them. A requirement whose configurable +checks ran with a config too loose to trust must be forced to FAIL; that override +lives in ``prowler.lib.check.compliance_config_eval`` and every renderer that +emits a finding's status (CSV transform, CLI table, OCSF) must route through it. + +This test statically asserts the invariant: any renderer that reads a finding's +raw ``status`` must also reference one of the config-status helpers. It mirrors +the manual audit that caught ``okta_idaas_stig`` shipping ConfigRequirements that +its CSV and table renderers never applied, so the gap cannot silently reopen. +""" + +import pathlib + +import pytest + +_REPO_ROOT = pathlib.Path(__file__).resolve().parents[4] +_RENDERER_DIR = _REPO_ROOT / "prowler" / "lib" / "outputs" / "compliance" + +# Base/dispatch modules that orchestrate renderers but never emit a status row. +_EXCLUDED_BASENAMES = { + "__init__.py", + "models.py", + "compliance.py", + "compliance_check.py", + "compliance_output.py", +} + +# Any one of these, present in the source, means the renderer wires the override. +_CONFIG_STATUS_HELPERS = ( + "apply_config_status", + "build_requirement_config_status", + "resolve_requirement_config_status", + "get_effective_status", +) + +# A renderer builds its output from the finding's raw status via one of these. +_RAW_STATUS_MARKERS = ("finding.status", "finding.status_extended") + + +def _renderer_sources(): + for path in sorted(_RENDERER_DIR.glob("**/*.py")): + if path.name in _EXCLUDED_BASENAMES or "__pycache__" in path.parts: + continue + yield path + + +@pytest.mark.parametrize( + "renderer_path", + [ + pytest.param(p, id=str(p.relative_to(_RENDERER_DIR))) + for p in _renderer_sources() + ], +) +def test_renderer_emitting_status_applies_config_status(renderer_path): + source = renderer_path.read_text(encoding="utf-8") + + uses_raw_status = any(marker in source for marker in _RAW_STATUS_MARKERS) + if not uses_raw_status: + pytest.skip("renderer does not emit a finding's raw status") + + applies_config_status = any(helper in source for helper in _CONFIG_STATUS_HELPERS) + assert applies_config_status, ( + f"{renderer_path.relative_to(_REPO_ROOT)} emits a finding's raw status but " + "never applies the config-status override. Route it through " + "apply_config_status / build_requirement_config_status (CSV/OCSF) or " + "resolve_requirement_config_status / get_effective_status (CLI table), " + "otherwise its ConfigRequirements are silently ignored." + ) diff --git a/tests/lib/outputs/compliance/ens/ens_aws_config_requirements_test.py b/tests/lib/outputs/compliance/ens/ens_aws_config_requirements_test.py new file mode 100644 index 00000000000..b09d9bc0b33 --- /dev/null +++ b/tests/lib/outputs/compliance/ens/ens_aws_config_requirements_test.py @@ -0,0 +1,61 @@ +"""Integration coverage proving the shared requirement-level config validation +is applied beyond CIS. ENS RD2022 AWS requirement ``op.exp.1.aws.cfg.1`` maps +``config_recorder_all_regions_enabled`` with a ``mute_non_default_regions == +false`` constraint; muting non-default regions makes a PASS untrustworthy, so +the requirement row must be FAIL even when the finding PASSes. The applied +config is read from the active provider's ``audit_config``.""" + +import json +import pathlib +from types import SimpleNamespace +from unittest.mock import patch + +from prowler.lib.check.compliance_models import Compliance +from prowler.lib.outputs.compliance.ens.ens_aws import AWSENS + +_REPO_ROOT = pathlib.Path(__file__).resolve().parents[5] +_ENS = _REPO_ROOT / "prowler" / "compliance" / "aws" / "ens_rd2022_aws.json" +_REQUIREMENT_ID = "op.exp.1.aws.cfg.1" + + +def _load_ens() -> Compliance: + return Compliance(**json.load(open(_ENS))) + + +def _finding(check_id: str, status: str): + return SimpleNamespace( + provider="aws", + account_uid="123456789012", + region="us-east-1", + check_id=check_id, + status=status, + status_extended=f"{check_id} {status}", + resource_uid="arn:aws:config:us-east-1:123456789012:recorder/default", + resource_name="default", + muted=False, + ) + + +def _rows_for(requirement_id, findings, audit_config): + with patch( + "prowler.providers.common.provider.Provider.get_global_provider" + ) as mock_gp: + mock_gp.return_value.audit_config = audit_config + out = AWSENS(findings=findings, compliance=_load_ens(), file_path=None) + return [r for r in out._data if r.Requirements_Id == requirement_id] + + +class Test_ENS_AWS_Config_Requirements: + def test_region_mute_constraint_forces_fail(self): + findings = [_finding("config_recorder_all_regions_enabled", "PASS")] + rows = _rows_for(_REQUIREMENT_ID, findings, {"mute_non_default_regions": True}) + assert rows, f"expected a row for requirement {_REQUIREMENT_ID}" + assert all(r.Status == "FAIL" for r in rows) + assert all("Configuration not valid" in r.StatusExtended for r in rows) + + def test_default_config_keeps_finding_status(self): + findings = [_finding("config_recorder_all_regions_enabled", "PASS")] + rows = _rows_for(_REQUIREMENT_ID, findings, {}) + assert rows + assert all(r.Status == "PASS" for r in rows) + assert all("Configuration not valid" not in r.StatusExtended for r in rows) diff --git a/tests/lib/outputs/compliance/okta_idaas_stig/okta_idaas_stig_okta_test.py b/tests/lib/outputs/compliance/okta_idaas_stig/okta_idaas_stig_okta_test.py index fa616c906e4..a6a02678510 100644 --- a/tests/lib/outputs/compliance/okta_idaas_stig/okta_idaas_stig_okta_test.py +++ b/tests/lib/outputs/compliance/okta_idaas_stig/okta_idaas_stig_okta_test.py @@ -5,6 +5,10 @@ from freezegun import freeze_time from mock import patch +from prowler.lib.check.compliance_config_eval import CONFIG_NOT_VALID_PREFIX +from prowler.lib.check.compliance_models import ( + Compliance_Requirement_ConfigConstraint, +) from prowler.lib.outputs.compliance.okta_idaas_stig.models import OktaIDaaSSTIGModel from prowler.lib.outputs.compliance.okta_idaas_stig.okta_idaas_stig_okta import ( OktaIDaaSSTIG, @@ -137,3 +141,52 @@ def test_batch_write_data_to_file(self): expected_csv = f"PROVIDER;DESCRIPTION;ORGANIZATIONDOMAIN;ASSESSMENTDATE;REQUIREMENTS_ID;REQUIREMENTS_NAME;REQUIREMENTS_DESCRIPTION;REQUIREMENTS_ATTRIBUTES_SECTION;REQUIREMENTS_ATTRIBUTES_SEVERITY;REQUIREMENTS_ATTRIBUTES_RULEID;REQUIREMENTS_ATTRIBUTES_STIGID;REQUIREMENTS_ATTRIBUTES_CCI;REQUIREMENTS_ATTRIBUTES_CHECKTEXT;REQUIREMENTS_ATTRIBUTES_FIXTEXT;STATUS;STATUSEXTENDED;RESOURCEID;RESOURCENAME;CHECKID;MUTED;FRAMEWORK;NAME\r\nokta;Defense Information Systems Agency (DISA) Security Technical Implementation Guide (STIG) for Okta Identity as a Service (IDaaS).;{OKTA_ORG_DOMAIN};{datetime.now()};OKTA-APP-000020;Okta must log out a session after a 15-minute period of inactivity.;A session timeout lock is a temporary action taken when a user stops work and moves away from the immediate vicinity of the information system.;CAT II (Medium);medium;SV-273186r1098825_rule;OKTA-APP-000020;['CCI-000057', 'CCI-001133'];Verify the Global Session Policy logs out a session after 15 minutes of inactivity.;From the Admin Console configure the Global Session Policy idle timeout to 15 minutes.;PASS;;okta-global-session-policy;Default Policy;signon_global_session_idle_timeout_15min;False;Okta-IDaaS-STIG;DISA Okta Identity as a Service (IDaaS) STIG V1R2\r\nokta;Defense Information Systems Agency (DISA) Security Technical Implementation Guide (STIG) for Okta Identity as a Service (IDaaS).;;{datetime.now()};OKTA-APP-000650;Okta must enforce a minimum 15-character password length.;The shorter the password, the lower the number of possible combinations that need to be tested before the password is compromised.;CAT II (Medium);medium;SV-273209r1098894_rule;OKTA-APP-000650;['CCI-000205'];Verify the password policy enforces a minimum length of 15 characters.;From the Admin Console set the minimum password length to 15 characters.;MANUAL;Manual check;manual_check;Manual check;manual;False;Okta-IDaaS-STIG;DISA Okta Identity as a Service (IDaaS) STIG V1R2\r\n" assert content == expected_csv + + def test_config_status_override_forces_fail(self): + """A PASS finding whose requirement declares a ConfigRequirements + constraint the scan's config violates must be reported as FAIL in the + CSV, with the config-not-valid reason prepended to StatusExtended.""" + # Inject a config constraint on the first requirement (idle timeout must + # be <= 15 minutes) without mutating the shared fixture. + compliance = OKTA_IDAAS_STIG_OKTA.copy(deep=True) + compliance.Requirements[0].ConfigRequirements = [ + Compliance_Requirement_ConfigConstraint( + Check="signon_global_session_idle_timeout_15min", + ConfigKey="okta_max_session_idle_minutes", + Operator="lte", + Value=15, + ) + ] + findings = [ + generate_finding_output( + provider="okta", + account_uid=OKTA_ORG_DOMAIN, + account_name=OKTA_ORG_DOMAIN, + region="global", + service_name="signon", + status="PASS", + status_extended="Idle timeout is configured.", + check_id="signon_global_session_idle_timeout_15min", + resource_uid="okta-global-session-policy", + resource_name="Default Policy", + compliance={"Okta-IDaaS-STIG-1R2": ["OKTA-APP-000020"]}, + ) + ] + + # The scan applied a 30-minute idle timeout, too loose for the 15-minute + # requirement, so the PASS must be overridden to FAIL. + with ( + patch( + "prowler.lib.check.compliance_config_eval.get_scan_audit_config", + return_value={"okta_max_session_idle_minutes": 30}, + ), + patch( + "prowler.lib.check.compliance_config_eval.get_scan_provider_type", + return_value="okta", + ), + ): + output = OktaIDaaSSTIG(findings, compliance) + + output_data = output.data[0] + assert output_data.Status == "FAIL" + assert output_data.StatusExtended.startswith(CONFIG_NOT_VALID_PREFIX) diff --git a/tests/lib/outputs/compliance/okta_idaas_stig/okta_idaas_stig_table_test.py b/tests/lib/outputs/compliance/okta_idaas_stig/okta_idaas_stig_table_test.py index 3017ea4f922..0dd353760bc 100644 --- a/tests/lib/outputs/compliance/okta_idaas_stig/okta_idaas_stig_table_test.py +++ b/tests/lib/outputs/compliance/okta_idaas_stig/okta_idaas_stig_table_test.py @@ -1,4 +1,5 @@ from types import SimpleNamespace +from unittest.mock import patch from prowler.lib.outputs.compliance.okta_idaas_stig.okta_idaas_stig import ( get_okta_idaas_stig_table, @@ -8,18 +9,35 @@ def _make_finding(check_id, status="PASS", muted=False): return SimpleNamespace( check_metadata=SimpleNamespace(CheckID=check_id), + check_id=check_id, status=status, muted=muted, ) -def _make_compliance(provider, sections, framework="Okta-IDaaS-STIG"): - """Build a per-check compliance covering the given sections.""" +def _make_compliance( + provider, + sections, + framework="Okta-IDaaS-STIG", + checks=None, + config_requirements=None, +): + """Build a per-check compliance covering the given sections. + + ``checks`` and ``config_requirements`` let a section's requirement declare + the checks it owns and the config constraints that gate it, so the table's + config-status override can be exercised. + """ return SimpleNamespace( Framework=framework, Provider=provider, Requirements=[ - SimpleNamespace(Attributes=[SimpleNamespace(Section=section)]) + SimpleNamespace( + Id=f"REQ-{section}", + Checks=list(checks or []), + ConfigRequirements=list(config_requirements or []), + Attributes=[SimpleNamespace(Section=section)], + ) for section in sections ], ) @@ -134,3 +152,60 @@ def test_provider_column_not_leaked_from_other_framework(self, capsys, tmp_path) # The provider of the unrelated trailing framework must NOT leak into # the rendered table. assert "aws" not in captured.out + + def test_config_status_override_forces_fail(self, capsys, tmp_path): + """A configurable check that PASSes but ran with a config too loose for + its requirement must be forced to FAIL in the table, honouring the + requirement's ConfigRequirements. Without the override check_a would be + PASS and no results table would render at all.""" + constraint = { + "Check": "check_a", + "ConfigKey": "okta_max_session_idle_minutes", + "Operator": "lte", + "Value": 15, + } + bulk_metadata = { + "check_a": SimpleNamespace( + Compliance=[ + _make_compliance( + "okta", + ["IAM"], + checks=["check_a"], + config_requirements=[constraint], + ) + ] + ), + "check_b": SimpleNamespace( + Compliance=[_make_compliance("okta", ["Logging"])] + ), + } + # Both checks PASS on their own; the scan applied a 30-minute idle + # timeout, which is too loose for the 15-minute requirement. + findings = [ + _make_finding("check_a", "PASS"), + _make_finding("check_b", "PASS"), + ] + + with ( + patch( + "prowler.lib.outputs.compliance.okta_idaas_stig.okta_idaas_stig.get_scan_audit_config", + return_value={"okta_max_session_idle_minutes": 30}, + ), + patch( + "prowler.lib.check.compliance_config_eval.get_scan_provider_type", + return_value="okta", + ), + ): + get_okta_idaas_stig_table( + findings, + bulk_metadata, + "okta_idaas_stig_1r2", + "output", + str(tmp_path), + False, + ) + + captured = capsys.readouterr() + # check_a was forced to FAIL by the config override, so its section + # (IAM) must report FAIL(1). + assert "FAIL(1)" in captured.out diff --git a/tests/lib/outputs/compliance/universal/ocsf_compliance_config_requirements_test.py b/tests/lib/outputs/compliance/universal/ocsf_compliance_config_requirements_test.py new file mode 100644 index 00000000000..c846385d888 --- /dev/null +++ b/tests/lib/outputs/compliance/universal/ocsf_compliance_config_requirements_test.py @@ -0,0 +1,191 @@ +"""Integration coverage for ConfigRequirements in the OCSF compliance output. + +OCSF is the universal output path every framework renders through, so it is the +natural place to exercise the requirement-level config override end to end across +all operators. When a requirement's configurable check ran with a config too +loose to trust, the Compliance status must be FAIL (even on a PASS finding) and +the message must carry the ``Configuration not valid`` marker. The Check status keeps +the real finding status. +""" + +from datetime import datetime, timezone +from types import SimpleNamespace +from unittest.mock import patch + +import pytest +from py_ocsf_models.objects.compliance_status import StatusID as ComplianceStatusID + +from prowler.lib.check.compliance_models import ( + ComplianceFramework, + OutputsConfig, + TableConfig, + UniversalComplianceRequirement, +) +from prowler.lib.outputs.compliance.universal.ocsf_compliance import ( + OCSFComplianceOutput, +) + +_MODULE = "prowler.providers.common.provider.Provider.get_global_provider" + + +def _finding(check_id, status="PASS", provider="aws"): + finding = SimpleNamespace() + finding.provider = provider + finding.account_uid = "123456789012" + finding.account_name = "test-account" + finding.account_email = "" + finding.account_organization_uid = "org-123" + finding.account_organization_name = "test-org" + finding.account_tags = {"env": "test"} + finding.region = "us-east-1" + finding.status = status + finding.status_extended = f"{check_id} is {status}" + finding.resource_uid = f"arn:aws:iam::123456789012:{check_id}" + finding.resource_name = check_id + finding.resource_details = "details" + finding.resource_metadata = {} + finding.resource_tags = {"Name": "test"} + finding.partition = "aws" + finding.muted = False + finding.check_id = check_id + finding.uid = "test-finding-uid" + finding.timestamp = datetime(2025, 1, 15, 12, 0, 0, tzinfo=timezone.utc) + finding.prowler_version = "5.0.0" + finding.compliance = {} + finding.metadata = SimpleNamespace( + Provider=provider, + CheckID=check_id, + CheckTitle=f"Title for {check_id}", + CheckType=["test-type"], + Description=f"Description for {check_id}", + Severity="medium", + ServiceName="iam", + ResourceType="aws-iam-role", + Risk="risk", + RelatedUrl="https://example.com", + Remediation=SimpleNamespace( + Recommendation=SimpleNamespace(Text="Fix", Url="https://fix.com"), + ), + DependsOn=[], + RelatedTo=[], + Categories=["test"], + Notes="", + AdditionalURLs=[], + ) + return finding + + +def _framework(check_id, constraint): + req = UniversalComplianceRequirement( + id="REQ-1", + description="Requirement REQ-1", + attributes={}, + checks={"aws": [check_id]}, + config_requirements=[constraint], + ) + return ComplianceFramework( + framework="TestFW", + name="Test Framework", + provider="AWS", + version="1.0", + description="Test framework", + requirements=[req], + attributes_metadata=None, + outputs=OutputsConfig(table_config=TableConfig(group_by="Section")), + ) + + +def _run(check_id, constraint, audit_config): + fw = _framework(check_id, constraint) + findings = [_finding(check_id, "PASS")] + with patch(_MODULE) as mock_gp: + mock_gp.return_value.audit_config = audit_config + out = OCSFComplianceOutput(findings=findings, framework=fw, provider="aws") + return out.data[0] + + +# (check, constraint, violating_config, valid_config) +_CASES = [ + ( + "securityhub_enabled", + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": False, + }, + {"mute_non_default_regions": True}, + {"mute_non_default_regions": False}, + ), + ( + "iam_user_accesskey_unused", + { + "Check": "iam_user_accesskey_unused", + "ConfigKey": "max_unused_access_keys_days", + "Operator": "lte", + "Value": 45, + }, + {"max_unused_access_keys_days": 120}, + {"max_unused_access_keys_days": 30}, + ), + ( + "cloudwatch_log_group_retention_policy_specific_days_enabled", + { + "Check": "cloudwatch_log_group_retention_policy_specific_days_enabled", + "ConfigKey": "log_group_retention_days", + "Operator": "gte", + "Value": 365, + }, + {"log_group_retention_days": 90}, + {"log_group_retention_days": 365}, + ), + ( + "sqlserver_recommended_minimal_tls_version", + { + "Check": "sqlserver_recommended_minimal_tls_version", + "ConfigKey": "recommended_minimal_tls_versions", + "Operator": "subset", + "Value": ["1.2", "1.3"], + }, + {"recommended_minimal_tls_versions": ["1.0", "1.2", "1.3"]}, + {"recommended_minimal_tls_versions": ["1.3"]}, + ), + ( + "acm_certificates_with_secure_key_algorithms", + { + "Check": "acm_certificates_with_secure_key_algorithms", + "ConfigKey": "insecure_key_algorithms", + "Operator": "superset", + "Value": ["RSA-1024", "P-192"], + }, + {"insecure_key_algorithms": ["P-192"]}, + {"insecure_key_algorithms": ["RSA-1024", "P-192"]}, + ), +] + + +class Test_OCSF_Config_Requirements: + @pytest.mark.parametrize( + "check,constraint,bad,ok", + _CASES, + ids=[c[1]["Operator"] for c in _CASES], + ) + def test_violating_config_fails_requirement(self, check, constraint, bad, ok): + cf = _run(check, constraint, bad) + assert cf.compliance.status_id == ComplianceStatusID.Fail + assert "Configuration not valid" in cf.message + + @pytest.mark.parametrize( + "check,constraint,bad,ok", + _CASES, + ids=[c[1]["Operator"] for c in _CASES], + ) + def test_valid_config_keeps_pass(self, check, constraint, bad, ok): + cf = _run(check, constraint, ok) + assert cf.compliance.status_id == ComplianceStatusID.Pass + assert "Configuration not valid" not in cf.message + + def test_absent_config_assumes_default_ok(self): + check, constraint, _bad, _ok = _CASES[0] + cf = _run(check, constraint, {}) + assert cf.compliance.status_id == ComplianceStatusID.Pass diff --git a/tests/lib/outputs/compliance/universal/ocsf_compliance_status_scoping_test.py b/tests/lib/outputs/compliance/universal/ocsf_compliance_status_scoping_test.py new file mode 100644 index 00000000000..8408868296f --- /dev/null +++ b/tests/lib/outputs/compliance/universal/ocsf_compliance_status_scoping_test.py @@ -0,0 +1,129 @@ +"""Top-level status consistency and provider scoping for the OCSF output. + +Two regressions are covered here: + +1. The event's top-level ``status_code``/``status_detail`` must reflect the + effective (config-aware) status, so a config-invalid PASS cannot produce an + event where ``compliance.status_id`` says FAIL while ``status_code`` still + says PASS. The nested Check object keeps the raw finding status. +2. Provider scoping: an Azure-scoped constraint must never affect an AWS output + even when the global provider would otherwise be relied upon. +""" + +from datetime import datetime, timezone +from types import SimpleNamespace +from unittest.mock import patch + +from py_ocsf_models.objects.compliance_status import StatusID as ComplianceStatusID + +from prowler.lib.check.compliance_models import ( + ComplianceFramework, + OutputsConfig, + TableConfig, + UniversalComplianceRequirement, +) +from prowler.lib.outputs.compliance.universal.ocsf_compliance import ( + OCSFComplianceOutput, +) + +_MODULE = "prowler.providers.common.provider.Provider.get_global_provider" + + +def _finding(check_id, status="PASS", provider="aws"): + finding = SimpleNamespace() + finding.provider = provider + finding.account_uid = "123456789012" + finding.account_name = "test-account" + finding.account_organization_uid = "org-123" + finding.account_organization_name = "test-org" + finding.region = "us-east-1" + finding.status = status + finding.status_extended = f"{check_id} is {status}" + finding.resource_uid = f"arn:aws:iam::123456789012:{check_id}" + finding.resource_name = check_id + finding.resource_details = "details" + finding.resource_metadata = {} + finding.resource_tags = {"Name": "test"} + finding.partition = "aws" + finding.muted = False + finding.check_id = check_id + finding.uid = "test-finding-uid" + finding.timestamp = datetime(2025, 1, 15, 12, 0, 0, tzinfo=timezone.utc) + finding.prowler_version = "5.0.0" + finding.metadata = SimpleNamespace( + CheckID=check_id, + CheckTitle=f"Title for {check_id}", + Description=f"Description for {check_id}", + Severity="medium", + ServiceName="iam", + ResourceType="aws-iam-role", + ) + return finding + + +def _framework(constraint, provider="AWS", check_provider="aws"): + req = UniversalComplianceRequirement( + id="REQ-1", + description="Requirement REQ-1", + attributes={}, + checks={check_provider: ["check_a"]}, + config_requirements=[constraint], + ) + return ComplianceFramework( + framework="TestFW", + name="Test Framework", + provider=provider, + version="1.0", + description="Test framework", + requirements=[req], + attributes_metadata=None, + outputs=OutputsConfig(table_config=TableConfig(group_by="Section")), + ) + + +def _run(framework, audit_config, provider="aws", status="PASS"): + findings = [_finding("check_a", status, provider)] + with patch(_MODULE) as mock_gp: + mock_gp.return_value.audit_config = audit_config + mock_gp.return_value.type = provider + out = OCSFComplianceOutput( + findings=findings, framework=framework, provider=provider + ) + return out.data[0] + + +_CONSTRAINT = { + "Check": "check_a", + "ConfigKey": "max_unused_access_keys_days", + "Operator": "lte", + "Value": 45, +} + + +class Test_OCSF_TopLevel_Status: + def test_config_invalid_pass_forces_toplevel_fail(self): + event = _run(_framework(_CONSTRAINT), {"max_unused_access_keys_days": 120}) + # Top-level and nested compliance status agree: both FAIL. + assert event.compliance.status_id == ComplianceStatusID.Fail + assert event.status_code == "FAIL" + assert "Configuration not valid" in event.status_detail + assert event.status_detail == event.message + # The nested Check preserves the raw finding result. + assert event.compliance.checks[0].status == "PASS" + + def test_valid_config_keeps_toplevel_pass(self): + event = _run(_framework(_CONSTRAINT), {"max_unused_access_keys_days": 30}) + assert event.compliance.status_id == ComplianceStatusID.Pass + assert event.status_code == "PASS" + assert "Configuration not valid" not in event.status_detail + + +class Test_OCSF_Provider_Scoping: + def test_azure_constraint_does_not_affect_aws_output(self): + constraint = {**_CONSTRAINT, "Provider": "azure"} + event = _run( + _framework(constraint), {"max_unused_access_keys_days": 120}, provider="aws" + ) + assert event.compliance.status_id == ComplianceStatusID.Pass + assert event.status_code == "PASS" + assert "Configuration not valid" not in event.status_detail diff --git a/tests/lib/outputs/compliance/universal/universal_output_config_requirements_test.py b/tests/lib/outputs/compliance/universal/universal_output_config_requirements_test.py new file mode 100644 index 00000000000..dd83dad5756 --- /dev/null +++ b/tests/lib/outputs/compliance/universal/universal_output_config_requirements_test.py @@ -0,0 +1,115 @@ +"""Coverage for ConfigRequirements + provider scoping in the universal CSV. + +The universal CSV must apply the same effective-status logic as the OCSF/table +outputs: a config-invalid PASS is reported as FAIL instead of leaking the raw +finding status. Provider scoping must also hold, so a constraint scoped to +another provider (e.g. Azure) never affects this provider's output (e.g. AWS). +""" + +from types import SimpleNamespace +from unittest.mock import patch + +from prowler.lib.check.compliance_models import ( + AttributeMetadata, + ComplianceFramework, + OutputsConfig, + TableConfig, + UniversalComplianceRequirement, +) +from prowler.lib.outputs.compliance.universal.universal_output import ( + UniversalComplianceOutput, +) + +_MODULE = "prowler.providers.common.provider.Provider.get_global_provider" + + +def _make_finding(check_id, status="PASS", provider="aws"): + finding = SimpleNamespace() + finding.provider = provider + finding.account_uid = "123456789012" + finding.account_name = "test-account" + finding.region = "us-east-1" + finding.status = status + finding.status_extended = f"{check_id} is {status}" + finding.resource_uid = f"arn:aws:iam::123456789012:{check_id}" + finding.resource_name = check_id + finding.muted = False + finding.check_id = check_id + finding.metadata = SimpleNamespace(Provider=provider, CheckID=check_id) + finding.compliance = {} + return finding + + +def _make_framework(constraint, provider="AWS", check_provider="aws"): + req = UniversalComplianceRequirement( + id="1.1", + description="test requirement", + attributes={"Section": "IAM"}, + checks={check_provider: ["check_a"]}, + config_requirements=[constraint], + ) + return ComplianceFramework( + framework="TestFW", + name="Test Framework", + provider=provider, + version="1.0", + description="Test framework", + requirements=[req], + attributes_metadata=[AttributeMetadata(key="Section", type="str")], + outputs=OutputsConfig(table_config=TableConfig(group_by="Section")), + ) + + +def _run(framework, audit_config, provider="aws", status="PASS"): + findings = [_make_finding("check_a", status, provider)] + with patch(_MODULE) as mock_gp: + mock_gp.return_value.audit_config = audit_config + mock_gp.return_value.type = provider + out = UniversalComplianceOutput( + findings=findings, framework=framework, provider=provider + ) + return out.data[0].dict() + + +class Test_Universal_CSV_Config_Requirements: + _CONSTRAINT = { + "Check": "check_a", + "ConfigKey": "max_unused_access_keys_days", + "Operator": "lte", + "Value": 45, + } + + def test_violating_config_forces_fail(self): + fw = _make_framework(self._CONSTRAINT) + row = _run(fw, {"max_unused_access_keys_days": 120}) + assert row["Status"] == "FAIL" + assert "Configuration not valid" in row["StatusExtended"] + + def test_valid_config_keeps_pass(self): + fw = _make_framework(self._CONSTRAINT) + row = _run(fw, {"max_unused_access_keys_days": 30}) + assert row["Status"] == "PASS" + assert "Configuration not valid" not in row["StatusExtended"] + + def test_absent_config_assumes_default_ok(self): + fw = _make_framework(self._CONSTRAINT) + row = _run(fw, {}) + assert row["Status"] == "PASS" + + +class Test_Universal_CSV_Provider_Scoping: + def test_azure_constraint_does_not_affect_aws_output(self): + """An Azure-scoped constraint must not force an AWS output to FAIL.""" + constraint = { + "Check": "check_a", + "ConfigKey": "max_unused_access_keys_days", + "Operator": "lte", + "Value": 45, + "Provider": "azure", + } + fw = _make_framework(constraint) + # Even with a config that *would* violate the constraint, the AWS output + # must keep PASS because the constraint is scoped to Azure. + row = _run(fw, {"max_unused_access_keys_days": 120}, provider="aws") + assert row["Status"] == "PASS" + assert "Configuration not valid" not in row["StatusExtended"] diff --git a/tests/lib/outputs/compliance/universal/universal_table_config_requirements_test.py b/tests/lib/outputs/compliance/universal/universal_table_config_requirements_test.py new file mode 100644 index 00000000000..3ac8dc6d8bf --- /dev/null +++ b/tests/lib/outputs/compliance/universal/universal_table_config_requirements_test.py @@ -0,0 +1,155 @@ +"""Integration coverage for ConfigRequirements in the console table generators. + +The table generators aggregate pass/fail counts, so a requirement whose config +is too loose must count its (otherwise PASS) finding as FAIL. Driven through the +universal table renderer, which backs the table output for every framework using +the shared ``get_effective_status`` helper.""" + +from types import SimpleNamespace +from unittest.mock import patch + +from prowler.lib.check.compliance_models import ( + ComplianceFramework, + OutputsConfig, + TableConfig, + UniversalComplianceRequirement, +) +from prowler.lib.outputs.compliance.universal.universal_table import get_universal_table + +_MODULE = "prowler.providers.common.provider.Provider.get_global_provider" +_CHECK = "securityhub_enabled" + + +def _finding(status="PASS"): + return SimpleNamespace( + check_metadata=SimpleNamespace(CheckID=_CHECK), status=status, muted=False + ) + + +# The overview table is only printed when there is more than one finding, so the +# tests use two PASS findings (both mapping the constrained check). +_FINDINGS = [_finding("PASS"), _finding("PASS")] + + +def _framework(): + req = UniversalComplianceRequirement( + id="1.1", + description="region check", + attributes={"Section": "Monitoring"}, + checks={"aws": [_CHECK]}, + config_requirements=[ + { + "Check": _CHECK, + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": False, + } + ], + ) + return ComplianceFramework( + framework="TestFW", + name="Test Framework", + provider="AWS", + version="1.0", + description="Test", + requirements=[req], + outputs=OutputsConfig(table_config=TableConfig(group_by="Section")), + ) + + +def _render(audit_config, capsys, output_directory): + with patch(_MODULE) as mock_gp: + mock_gp.return_value.audit_config = audit_config + get_universal_table( + findings=_FINDINGS, + bulk_checks_metadata={}, + compliance_framework_name="testfw_1.0_aws", + output_filename="out", + output_directory=str(output_directory), + compliance_overview=False, + framework=_framework(), + provider="aws", + ) + return capsys.readouterr().out + + +class Test_Universal_Table_Config_Requirements: + def test_violating_config_counts_pass_finding_as_fail(self, capsys, tmp_path): + out = _render({"mute_non_default_regions": True}, capsys, tmp_path) + assert "FAIL(2)" in out + assert "PASS(2)" not in out + + def test_valid_config_keeps_pass_count(self, capsys, tmp_path): + out = _render({"mute_non_default_regions": False}, capsys, tmp_path) + assert "PASS(2)" in out + assert "FAIL(2)" not in out + + def test_absent_config_keeps_pass_count(self, capsys, tmp_path): + out = _render({}, capsys, tmp_path) + assert "PASS(2)" in out + assert "FAIL(2)" not in out + + +def _framework_two_requirements(): + """Same check evidences two requirements; only one carries a guardrail. + + Drives the double-count scenario: with the config violated, the shared + finding is FAIL for the constrained requirement and PASS for the other, so + its index would land in both pass and fail counts without FAIL precedence. + """ + constrained = UniversalComplianceRequirement( + id="1.1", + description="region check", + attributes={"Section": "Monitoring"}, + checks={"aws": [_CHECK]}, + config_requirements=[ + { + "Check": _CHECK, + "Provider": "aws", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": False, + } + ], + ) + unconstrained = UniversalComplianceRequirement( + id="2.1", + description="other check", + attributes={"Section": "Logging"}, + checks={"aws": [_CHECK]}, + ) + return ComplianceFramework( + framework="TestFW", + name="Test Framework", + provider="AWS", + version="1.0", + description="Test", + requirements=[constrained, unconstrained], + outputs=OutputsConfig(table_config=TableConfig(group_by="Section")), + ) + + +class Test_Universal_Table_Multi_Requirement_Dedup: + def test_finding_in_two_requirements_counted_once_with_fail_precedence( + self, capsys, tmp_path + ): + # mute=True violates the constrained requirement → each shared PASS + # finding must be counted once as FAIL in the overview, not double + # counted as both PASS and FAIL across the two requirements it maps. + with patch(_MODULE) as mock_gp: + mock_gp.return_value.audit_config = {"mute_non_default_regions": True} + mock_gp.return_value.type = "aws" + get_universal_table( + findings=_FINDINGS, + bulk_checks_metadata={}, + compliance_framework_name="testfw_1.0_aws", + output_filename="out", + output_directory=str(tmp_path), + compliance_overview=True, + framework=_framework_two_requirements(), + provider="aws", + ) + out = capsys.readouterr().out + # Two findings, each counted once as FAIL → 100% FAIL, 0 PASS. + assert "(2) FAIL" in out + assert "(0) PASS" in out