From 220b3b59b33a44bfe580c0201026dc42375d7a50 Mon Sep 17 00:00:00 2001 From: pedrooot Date: Tue, 23 Jun 2026 11:12:35 +0200 Subject: [PATCH 01/18] chore: merge master --- .../alibabacloud/cis_2.0_alibabacloud.json | 24 + .../prowler_threatscore_alibabacloud.json | 16 + .../aws/asd_essential_eight_aws.json | 24 + .../aws_account_security_onboarding_aws.json | 192 +++++++ .../aws/aws_ai_security_framework_aws.json | 40 +- ...ndational_security_best_practices_aws.json | 79 +++ ...aws_foundational_technical_review_aws.json | 8 + ...itected_framework_security_pillar_aws.json | 30 ++ prowler/compliance/aws/c5_aws.json | 238 +++++++++ prowler/compliance/aws/ccc_aws.json | 84 +++ prowler/compliance/aws/cis_1.4_aws.json | 30 ++ prowler/compliance/aws/cis_1.5_aws.json | 38 ++ prowler/compliance/aws/cis_2.0_aws.json | 38 ++ prowler/compliance/aws/cis_3.0_aws.json | 38 ++ prowler/compliance/aws/cis_4.0_aws.json | 38 ++ prowler/compliance/aws/cis_5.0_aws.json | 38 ++ prowler/compliance/aws/cis_6.0_aws.json | 38 ++ prowler/compliance/aws/cisa_aws.json | 28 + prowler/compliance/aws/ens_rd2022_aws.json | 136 +++++ .../aws/fedramp_20x_ksi_low_aws.json | 38 ++ .../aws/fedramp_low_revision_4_aws.json | 80 +++ .../aws/fedramp_moderate_revision_4_aws.json | 310 +++++++++++ prowler/compliance/aws/ffiec_aws.json | 156 ++++++ prowler/compliance/aws/gdpr_aws.json | 16 + .../aws/gxp_21_cfr_part_11_aws.json | 14 + .../compliance/aws/gxp_eu_annex_11_aws.json | 32 ++ prowler/compliance/aws/hipaa_aws.json | 126 +++++ prowler/compliance/aws/iso27001_2013_aws.json | 68 +++ prowler/compliance/aws/iso27001_2022_aws.json | 96 ++++ .../compliance/aws/kisa_isms_p_2023_aws.json | 114 +++++ .../aws/kisa_isms_p_2023_korean_aws.json | 114 +++++ prowler/compliance/aws/mitre_attack_aws.json | 292 +++++++++++ prowler/compliance/aws/nis2_aws.json | 54 ++ .../aws/nist_800_171_revision_2_aws.json | 226 ++++++++ .../aws/nist_800_53_revision_4_aws.json | 244 +++++++++ .../aws/nist_800_53_revision_5_aws.json | 482 ++++++++++++++++++ prowler/compliance/aws/nist_csf_1.1_aws.json | 266 ++++++++++ prowler/compliance/aws/nist_csf_2.0_aws.json | 171 +++++++ prowler/compliance/aws/pci_3.2.1_aws.json | 88 ++++ prowler/compliance/aws/pci_4.0_aws.json | 155 ++++++ .../aws/prowler_threatscore_aws.json | 38 ++ .../aws/rbi_cyber_security_framework_aws.json | 8 + .../compliance/aws/secnumcloud_3.2_aws.json | 125 +++++ prowler/compliance/aws/soc2_aws.json | 138 +++++ prowler/compliance/azure/c5_azure.json | 77 +++ prowler/compliance/azure/ccc_azure.json | 29 ++ prowler/compliance/azure/cis_4.0_azure.json | 10 + prowler/compliance/azure/cis_5.0_azure.json | 10 + prowler/compliance/azure/hipaa_azure.json | 22 + prowler/compliance/azure/nis2_azure.json | 33 ++ .../azure/secnumcloud_3.2_azure.json | 19 + prowler/compliance/azure/soc2_azure.json | 22 + prowler/compliance/csa_ccm_4.0.json | 213 +++++++- prowler/compliance/dora_2022_2554.json | 172 ++++++- prowler/compliance/gcp/ccc_gcp.json | 22 + .../kubernetes/cis_1.10_kubernetes.json | 8 + .../kubernetes/cis_1.11_kubernetes.json | 8 + .../kubernetes/cis_1.12_kubernetes.json | 8 + .../kubernetes/cis_1.8_kubernetes.json | 8 + .../kubernetes/pci_4.0_kubernetes.json | 32 ++ .../prowler_threatscore_kubernetes.json | 8 + prowler/lib/check/compliance_config_eval.py | 220 ++++++++ prowler/lib/check/compliance_models.py | 38 +- .../asd_essential_eight.py | 25 +- .../asd_essential_eight_aws.py | 20 +- .../aws_well_architected.py | 19 +- prowler/lib/outputs/compliance/c5/c5.py | 25 +- prowler/lib/outputs/compliance/c5/c5_aws.py | 20 +- prowler/lib/outputs/compliance/c5/c5_azure.py | 20 +- prowler/lib/outputs/compliance/c5/c5_gcp.py | 20 +- prowler/lib/outputs/compliance/ccc/ccc.py | 25 +- prowler/lib/outputs/compliance/ccc/ccc_aws.py | 20 +- .../lib/outputs/compliance/ccc/ccc_azure.py | 20 +- prowler/lib/outputs/compliance/ccc/ccc_gcp.py | 20 +- prowler/lib/outputs/compliance/cis/cis.py | 25 +- .../compliance/cis/cis_alibabacloud.py | 19 +- prowler/lib/outputs/compliance/cis/cis_aws.py | 22 +- .../lib/outputs/compliance/cis/cis_azure.py | 19 +- prowler/lib/outputs/compliance/cis/cis_gcp.py | 19 +- .../lib/outputs/compliance/cis/cis_github.py | 19 +- .../compliance/cis/cis_googleworkspace.py | 19 +- .../outputs/compliance/cis/cis_kubernetes.py | 19 +- .../lib/outputs/compliance/cis/cis_m365.py | 19 +- .../outputs/compliance/cis/cis_oraclecloud.py | 19 +- .../cisa_scuba/cisa_scuba_googleworkspace.py | 19 +- prowler/lib/outputs/compliance/ens/ens.py | 21 +- prowler/lib/outputs/compliance/ens/ens_aws.py | 20 +- .../lib/outputs/compliance/ens/ens_azure.py | 20 +- prowler/lib/outputs/compliance/ens/ens_gcp.py | 20 +- .../lib/outputs/compliance/generic/generic.py | 24 +- .../compliance/generic/generic_table.py | 28 +- .../compliance/iso27001/iso27001_aws.py | 19 +- .../compliance/iso27001/iso27001_azure.py | 19 +- .../compliance/iso27001/iso27001_gcp.py | 19 +- .../iso27001/iso27001_kubernetes.py | 19 +- .../compliance/iso27001/iso27001_m365.py | 19 +- .../compliance/iso27001/iso27001_nhn.py | 19 +- .../compliance/kisa_ismsp/kisa_ismsp.py | 25 +- .../compliance/kisa_ismsp/kisa_ismsp_aws.py | 20 +- .../compliance/mitre_attack/mitre_attack.py | 25 +- .../mitre_attack/mitre_attack_aws.py | 20 +- .../mitre_attack/mitre_attack_azure.py | 20 +- .../mitre_attack/mitre_attack_gcp.py | 20 +- .../prowler_threatscore.py | 30 +- .../prowler_threatscore_alibaba.py | 20 +- .../prowler_threatscore_aws.py | 20 +- .../prowler_threatscore_azure.py | 20 +- .../prowler_threatscore_gcp.py | 20 +- .../prowler_threatscore_kubernetes.py | 20 +- .../prowler_threatscore_m365.py | 20 +- .../compliance/universal/ocsf_compliance.py | 28 +- .../compliance/universal/universal_table.py | 61 ++- ...compliance_config_constraint_model_test.py | 113 ++++ .../lib/check/compliance_config_eval_test.py | 240 +++++++++ ...ompliance_config_requirements_data_test.py | 161 ++++++ .../cis/cis_aws_config_requirements_test.py | 89 ++++ .../cis/cis_azure_config_requirements_test.py | 75 +++ .../ens/ens_aws_config_requirements_test.py | 61 +++ ...csf_compliance_config_requirements_test.py | 191 +++++++ ...niversal_table_config_requirements_test.py | 90 ++++ 120 files changed, 7500 insertions(+), 155 deletions(-) create mode 100644 prowler/lib/check/compliance_config_eval.py create mode 100644 tests/lib/check/compliance_config_constraint_model_test.py create mode 100644 tests/lib/check/compliance_config_eval_test.py create mode 100644 tests/lib/check/compliance_config_requirements_data_test.py create mode 100644 tests/lib/outputs/compliance/cis/cis_aws_config_requirements_test.py create mode 100644 tests/lib/outputs/compliance/cis/cis_azure_config_requirements_test.py create mode 100644 tests/lib/outputs/compliance/ens/ens_aws_config_requirements_test.py create mode 100644 tests/lib/outputs/compliance/universal/ocsf_compliance_config_requirements_test.py create mode 100644 tests/lib/outputs/compliance/universal/universal_table_config_requirements_test.py diff --git a/prowler/compliance/alibabacloud/cis_2.0_alibabacloud.json b/prowler/compliance/alibabacloud/cis_2.0_alibabacloud.json index 7cda08efbb8..1cac54b318c 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 + } ] }, { diff --git a/prowler/compliance/alibabacloud/prowler_threatscore_alibabacloud.json b/prowler/compliance/alibabacloud/prowler_threatscore_alibabacloud.json index ca7a030dc20..9f16b6cc4dd 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", @@ -695,6 +703,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..f38ee93d28f 100644 --- a/prowler/compliance/aws/aws_foundational_security_best_practices_aws.json +++ b/prowler/compliance/aws/aws_foundational_security_best_practices_aws.json @@ -12,6 +12,14 @@ "Checks": [ "acm_certificates_expiration_check" ], + "ConfigRequirements": [ + { + "Check": "acm_certificates_expiration_check", + "ConfigKey": "days_to_expire_threshold", + "Operator": "gte", + "Value": 30 + } + ], "Attributes": [ { "ItemId": "ACM.1", @@ -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", @@ -2370,6 +2405,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 +2590,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 +2692,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", @@ -2951,6 +3022,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..2e3a311aca9 100644 --- a/prowler/compliance/azure/cis_4.0_azure.json +++ b/prowler/compliance/azure/cis_4.0_azure.json @@ -375,6 +375,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..dce5f299fea 100644 --- a/prowler/compliance/azure/cis_5.0_azure.json +++ b/prowler/compliance/azure/cis_5.0_azure.json @@ -3006,6 +3006,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 b27344582fb..4365b8a6b96 100644 --- a/prowler/compliance/azure/hipaa_azure.json +++ b/prowler/compliance/azure/hipaa_azure.json @@ -765,6 +765,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" + ] + } ] }, { @@ -817,6 +828,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..df541573af5 100644 --- a/prowler/compliance/csa_ccm_4.0.json +++ b/prowler/compliance/csa_ccm_4.0.json @@ -229,7 +229,15 @@ "oraclecloud": [ "cloudguard_enabled" ] - } + }, + "config_requirements": [ + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ] }, { "id": "A&A-04", @@ -334,7 +342,21 @@ "oraclecloud": [ "cloudguard_enabled" ] - } + }, + "config_requirements": [ + { + "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 + } + ] }, { "id": "AIS-04", @@ -978,7 +1000,15 @@ "defender_ensure_defender_for_server_is_on", "vm_backup_enabled" ] - } + }, + "config_requirements": [ + { + "Check": "drs_job_exist", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ] }, { "id": "BCR-11", @@ -1416,7 +1446,27 @@ "events_rule_security_list_changes", "events_rule_vcn_changes" ] - } + }, + "config_requirements": [ + { + "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 + } + ] }, { "id": "CEK-03", @@ -1659,7 +1709,17 @@ "filestorage_file_system_encrypted_with_cmk", "objectstorage_bucket_encrypted_with_cmk" ] - } + }, + "config_requirements": [ + { + "Check": "storage_smb_channel_encryption_with_secure_algorithm", + "ConfigKey": "recommended_smb_channel_encryption_algorithms", + "Operator": "subset", + "Value": [ + "AES-256-GCM" + ] + } + ] }, { "id": "CEK-04", @@ -1802,7 +1862,27 @@ "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", + "ConfigKey": "insecure_key_algorithms", + "Operator": "superset", + "Value": [ + "RSA-1024", + "P-192" + ] + }, + { + "Check": "sqlserver_recommended_minimal_tls_version", + "ConfigKey": "recommended_minimal_tls_versions", + "Operator": "subset", + "Value": [ + "1.2", + "1.3" + ] + } + ] }, { "id": "CEK-08", @@ -2345,7 +2425,15 @@ "alibabacloud": [ "securitycenter_all_assets_agent_installed" ] - } + }, + "config_requirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ] }, { "id": "DSP-02", @@ -2583,7 +2671,15 @@ "alibabacloud": [ "securitycenter_all_assets_agent_installed" ] - } + }, + "config_requirements": [ + { + "Check": "config_recorder_all_regions_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ] }, { "id": "DSP-04", @@ -2997,7 +3093,18 @@ "oraclecloud": [ "compute_instance_in_transit_encryption_enabled" ] - } + }, + "config_requirements": [ + { + "Check": "sqlserver_recommended_minimal_tls_version", + "ConfigKey": "recommended_minimal_tls_versions", + "Operator": "subset", + "Value": [ + "1.2", + "1.3" + ] + } + ] }, { "id": "DSP-16", @@ -3403,7 +3510,21 @@ "oraclecloud": [ "cloudguard_enabled" ] - } + }, + "config_requirements": [ + { + "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 + } + ] }, { "id": "IAM-02", @@ -6255,7 +6376,15 @@ "cloudguard_enabled", "events_rule_cloudguard_problems" ] - } + }, + "config_requirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ] }, { "id": "LOG-02", @@ -6558,7 +6687,21 @@ "events_notification_topic_and_subscription_exists", "events_rule_local_user_authentication" ] - } + }, + "config_requirements": [ + { + "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 + } + ] }, { "id": "LOG-04", @@ -7602,7 +7745,15 @@ "events_rule_cloudguard_problems", "events_notification_topic_and_subscription_exists" ] - } + }, + "config_requirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ] }, { "id": "SEF-03", @@ -7880,7 +8031,21 @@ "oraclecloud": [ "cloudguard_enabled" ] - } + }, + "config_requirements": [ + { + "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 + } + ] }, { "id": "SEF-08", @@ -8461,7 +8626,15 @@ "oraclecloud": [ "cloudguard_enabled" ] - } + }, + "config_requirements": [ + { + "Check": "guardduty_is_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ] }, { "id": "TVM-05", @@ -8729,7 +8902,15 @@ "oraclecloud": [ "cloudguard_enabled" ] - } + }, + "config_requirements": [ + { + "Check": "guardduty_is_enabled", + "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..e57557edee0 100644 --- a/prowler/compliance/dora_2022_2554.json +++ b/prowler/compliance/dora_2022_2554.json @@ -215,7 +215,33 @@ "securitycenter_vulnerability_scan_enabled", "actiontrail_multi_region_enabled" ] - } + }, + "config_requirements": [ + { + "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": "guardduty_delegated_admin_enabled_all_regions", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ] }, { "id": "DORA-Art7", @@ -299,7 +325,35 @@ "ecs_unattached_disk_encrypted", "ecs_instance_no_legacy_network" ] - } + }, + "config_requirements": [ + { + "Check": "acm_certificates_with_secure_key_algorithms", + "ConfigKey": "insecure_key_algorithms", + "Operator": "superset", + "Value": [ + "RSA-1024", + "P-192" + ] + }, + { + "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" + ] + } + ] }, { "id": "DORA-Art8", @@ -344,7 +398,15 @@ "securitycenter_all_assets_agent_installed", "ram_user_console_access_unused" ] - } + }, + "config_requirements": [ + { + "Check": "accessanalyzer_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ] }, { "id": "DORA-Art9", @@ -580,7 +642,21 @@ "ecs_instance_endpoint_protection_installed", "cs_kubernetes_cloudmonitor_enabled" ] - } + }, + "config_requirements": [ + { + "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 + } + ] }, { "id": "DORA-Art11", @@ -732,7 +808,15 @@ "securitycenter_all_assets_agent_installed", "ecs_instance_latest_os_patches_applied" ] - } + }, + "config_requirements": [ + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ] }, { "id": "DORA-Art14", @@ -901,7 +985,21 @@ "securitycenter_notification_enabled_high_risk", "securitycenter_vulnerability_scan_enabled" ] - } + }, + "config_requirements": [ + { + "Check": "guardduty_delegated_admin_enabled_all_regions", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + }, + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ] }, { "id": "DORA-Art19", @@ -1017,7 +1115,15 @@ "cs_kubernetes_cluster_check_recent", "cs_kubernetes_cluster_check_weekly" ] - } + }, + "config_requirements": [ + { + "Check": "securityhub_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ] }, { "id": "DORA-Art25", @@ -1079,7 +1185,21 @@ "ecs_instance_latest_os_patches_applied", "ecs_instance_no_legacy_network" ] - } + }, + "config_requirements": [ + { + "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 + } + ] }, { "id": "DORA-Art28", @@ -1144,7 +1264,15 @@ "oss_bucket_not_publicly_accessible", "actiontrail_oss_bucket_not_publicly_accessible" ] - } + }, + "config_requirements": [ + { + "Check": "accessanalyzer_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ] }, { "id": "DORA-Art30", @@ -1200,7 +1328,15 @@ "ram_policy_attached_only_to_group_or_roles", "ram_no_root_access_key" ] - } + }, + "config_requirements": [ + { + "Check": "accessanalyzer_enabled", + "ConfigKey": "mute_non_default_regions", + "Operator": "eq", + "Value": false + } + ] }, { "id": "DORA-Art45", @@ -1245,7 +1381,21 @@ "actiontrail_multi_region_enabled", "sls_logstore_retention_period" ] - } + }, + "config_requirements": [ + { + "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/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..6fea9c2291c 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", diff --git a/prowler/compliance/kubernetes/cis_1.11_kubernetes.json b/prowler/compliance/kubernetes/cis_1.11_kubernetes.json index 6a19ea41618..b14cc1f9499 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", diff --git a/prowler/compliance/kubernetes/cis_1.12_kubernetes.json b/prowler/compliance/kubernetes/cis_1.12_kubernetes.json index 1ba1e55a886..292b1085ce2 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", diff --git a/prowler/compliance/kubernetes/cis_1.8_kubernetes.json b/prowler/compliance/kubernetes/cis_1.8_kubernetes.json index 24a5733a050..f3762757aaa 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", 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..b8b6a606656 100644 --- a/prowler/compliance/kubernetes/prowler_threatscore_kubernetes.json +++ b/prowler/compliance/kubernetes/prowler_threatscore_kubernetes.json @@ -1199,6 +1199,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", diff --git a/prowler/lib/check/compliance_config_eval.py b/prowler/lib/check/compliance_config_eval.py new file mode 100644 index 00000000000..2eefe086932 --- /dev/null +++ b/prowler/lib/check/compliance_config_eval.py @@ -0,0 +1,220 @@ +"""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 + +# Prefix prepended to a finding's ``status_extended`` when its requirement's +# config constraints are not satisfied and the status is forced to FAIL. +CONFIG_NOT_VALID_PREFIX = "[CONFIG NOT VALID]" + + +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], + audit_config: Optional[dict], +) -> 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`` + and ``Value``. ``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. + + 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") + else: + check = getattr(constraint, "Check", None) + config_key = getattr(constraint, "ConfigKey", None) + operator = getattr(constraint, "Operator", None) + expected = getattr(constraint, "Value", None) + + 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 = ( + f"config not valid for requirement: {check}.{config_key}=" + f"{applied!r} does not satisfy {operator} {expected!r}" + ) + return False, reason + + return True, "" + + +def get_scan_audit_config() -> dict: + """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 + ``{}`` if no provider is set or audit_config is unavailable). + """ + try: + from prowler.providers.common.provider import Provider + + return Provider.get_global_provider().audit_config or {} + except Exception: + 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, + audit_config: Optional[dict] = None, +) -> dict: + """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. + """ + if audit_config is None: + audit_config = get_scan_audit_config() + status = {} + for requirement in requirements: + constraints = _requirement_constraints(requirement) + if constraints: + status[_requirement_id(requirement)] = evaluate_config_constraints( + constraints, audit_config + ) + return status + + +def resolve_requirement_config_status( + requirement: Any, + audit_config: dict, + cache: dict, +) -> 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. ``cache`` is keyed by requirement id and reused + across the whole table build. + """ + req_id = _requirement_id(requirement) + if req_id not in cache: + constraints = _requirement_constraints(requirement) + cache[req_id] = ( + evaluate_config_constraints(constraints, audit_config) + if constraints + else (True, "") + ) + return cache[req_id] + + +def apply_config_status( + status: str, + status_extended: str, + config_status: Optional[tuple], +) -> 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``. ``config_status`` is the ``(ok, reason)`` + tuple from ``build_requirement_config_status`` (``None`` → no constraints). + """ + if not config_status or config_status[0]: + return status, status_extended + return ( + "FAIL", + f"{CONFIG_NOT_VALID_PREFIX} {config_status[1]}. {status_extended}", + ) + + +def get_effective_status( + status: str, + config_status: Optional[tuple], +) -> str: + """Return the effective status for table aggregation (``FAIL`` if config invalid).""" + if not config_status or config_status[0]: + return status + return "FAIL" diff --git a/prowler/lib/check/compliance_models.py b/prowler/lib/check/compliance_models.py index f883bf60b18..090283c1d78 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 Literal, Optional, Union from pydantic.v1 import BaseModel, Field, ValidationError, root_validator @@ -304,6 +304,34 @@ class STIG_Requirement_Attribute(BaseModel): # 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``. + + 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] + + # 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 +357,7 @@ class Compliance_Requirement(BaseModel): ] ] Checks: list[str] + ConfigRequirements: Optional[list[Compliance_Requirement_ConfigConstraint]] = None class Compliance(BaseModel): @@ -701,6 +730,7 @@ class UniversalComplianceRequirement(BaseModel): name: Optional[str] = None attributes: dict = Field(default_factory=dict) checks: dict[str, list[str]] = Field(default_factory=dict) + config_requirements: Optional[list[dict]] = None tactics: Optional[list] = None sub_techniques: Optional[list] = None platforms: Optional[list] = None @@ -913,6 +943,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 +955,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..0d47ea43b5d 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,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_asd_essential_eight_table( @@ -24,6 +29,10 @@ def get_asd_essential_eight_table( muted_count = [] section_seen = {} provider = "" + # The applied config is scan-global (the provider's audit_config). Evaluate + # each requirement's config constraints once against it (memoised by Id). + 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,14 @@ def get_asd_essential_eight_table( if compliance.Framework == "ASD-Essential-Eight": provider = compliance.Provider for requirement in compliance.Requirements: + # A requirement whose configurable checks ran with an invalid + # config can't be trusted: treat the finding as FAIL. + 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: @@ -45,10 +62,10 @@ def get_asd_essential_eight_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) @@ -58,9 +75,9 @@ def get_asd_essential_eight_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/asd_essential_eight/asd_essential_eight_aws.py b/prowler/lib/outputs/compliance/asd_essential_eight/asd_essential_eight_aws.py index c264c81505c..e326ae12822 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,22 @@ def transform( Returns: - None """ + # Evaluate each requirement's config constraints once against the + # scan-global applied config; a requirement whose configurable checks + # ran with a config too loose to trust is forced to FAIL. + 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 +79,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..9f440e23fb6 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,21 @@ def transform( Returns: - None """ + # Evaluate each requirement's config constraints once against the + # scan-global applied config; a requirement whose configurable checks + # ran with a config too loose to trust is forced to FAIL. + 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 +73,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..5f3d3b2f800 100644 --- a/prowler/lib/outputs/compliance/c5/c5.py +++ b/prowler/lib/outputs/compliance/c5/c5.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_c5_table( @@ -24,6 +29,10 @@ def get_c5_table( sections = {} section_seen = {} provider = "" + # The applied config is scan-global (the provider's audit_config). Evaluate + # each requirement's config constraints once against it (memoised by Id). + 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,14 @@ def get_c5_table( if compliance.Framework == "C5": provider = compliance.Provider for requirement in compliance.Requirements: + # A requirement whose configurable checks ran with an invalid + # config can't be trusted: treat the finding as FAIL. + 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 @@ -42,10 +59,10 @@ def get_c5_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 +72,9 @@ def get_c5_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/c5/c5_aws.py b/prowler/lib/outputs/compliance/c5/c5_aws.py index 5f13b77d7ac..d62566b6d1d 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,22 @@ def transform( Returns: - None """ + # Evaluate each requirement's config constraints once against the + # scan-global applied config; a requirement whose configurable checks + # ran with a config too loose to trust is forced to FAIL. + 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 +68,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..976d37e9be9 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,22 @@ def transform( Returns: - None """ + # Evaluate each requirement's config constraints once against the + # scan-global applied config; a requirement whose configurable checks + # ran with a config too loose to trust is forced to FAIL. + 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 +68,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..ae69df8dfe9 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,22 @@ def transform( Returns: - None """ + # Evaluate each requirement's config constraints once against the + # scan-global applied config; a requirement whose configurable checks + # ran with a config too loose to trust is forced to FAIL. + 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 +68,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..547319bd38d 100644 --- a/prowler/lib/outputs/compliance/ccc/ccc.py +++ b/prowler/lib/outputs/compliance/ccc/ccc.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_ccc_table( @@ -24,6 +29,10 @@ def get_ccc_table( sections = {} section_seen = {} provider = "" + # The applied config is scan-global (the provider's audit_config). Evaluate + # each requirement's config constraints once against it (memoised by Id). + 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,14 @@ def get_ccc_table( if compliance.Framework == "CCC": provider = compliance.Provider for requirement in compliance.Requirements: + # A requirement whose configurable checks ran with an invalid + # config can't be trusted: treat the finding as FAIL. + 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 @@ -42,10 +59,10 @@ def get_ccc_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 +72,9 @@ def get_ccc_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/ccc/ccc_aws.py b/prowler/lib/outputs/compliance/ccc/ccc_aws.py index 11144255740..82f5a58eab9 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,22 @@ def transform( Returns: - None """ + # Evaluate each requirement's config constraints once against the + # scan-global applied config; a requirement whose configurable checks + # ran with a config too loose to trust is forced to FAIL. + 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 +72,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..450c2b335ed 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,22 @@ def transform( Returns: - None """ + # Evaluate each requirement's config constraints once against the + # scan-global applied config; a requirement whose configurable checks + # ran with a config too loose to trust is forced to FAIL. + 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 +72,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..19ba65227d0 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,22 @@ def transform( Returns: - None """ + # Evaluate each requirement's config constraints once against the + # scan-global applied config; a requirement whose configurable checks + # ran with a config too loose to trust is forced to FAIL. + 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 +72,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..a76d79d7924 100644 --- a/prowler/lib/outputs/compliance/cis/cis.py +++ b/prowler/lib/outputs/compliance/cis/cis.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_cis_table( @@ -26,6 +31,10 @@ def get_cis_table( pass_count = [] fail_count = [] muted_count = [] + # The applied config is scan-global (the provider's audit_config). Evaluate + # each requirement's config constraints once against it (memoised by Id). + 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,14 @@ def get_cis_table( if compliance.Framework == "CIS" and version_in_name in compliance.Version: provider = compliance.Provider for requirement in compliance.Requirements: + # A requirement whose configurable checks ran with an invalid + # config can't be trusted: treat the finding as FAIL. + 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 @@ -59,9 +76,9 @@ def get_cis_table( section_muted_seen[section].add(index) sections[section]["Muted"] += 1 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 "Level 1" in attribute.Profile: if ( @@ -69,7 +86,7 @@ def get_cis_table( and index not in section_split_seen[section]["Level 1"] ): section_split_seen[section]["Level 1"].add(index) - if finding.status == "FAIL": + if effective_status == "FAIL": sections[section]["Level 1"]["FAIL"] += 1 else: sections[section]["Level 1"]["PASS"] += 1 @@ -79,7 +96,7 @@ def get_cis_table( and index not in section_split_seen[section]["Level 2"] ): section_split_seen[section]["Level 2"].add(index) - if finding.status == "FAIL": + if effective_status == "FAIL": sections[section]["Level 2"]["FAIL"] += 1 else: sections[section]["Level 2"]["PASS"] += 1 diff --git a/prowler/lib/outputs/compliance/cis/cis_alibabacloud.py b/prowler/lib/outputs/compliance/cis/cis_alibabacloud.py index a1880d3af98..80023215179 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,21 @@ def transform( Returns: - None """ + # Evaluate each requirement's config constraints once against the + # scan-global applied config; a requirement whose configurable checks + # ran with a config too loose to trust is forced to FAIL. + 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 +74,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..f3b62056298 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,24 @@ def transform( Returns: - None """ + # The applied config is scan-global (the provider's audit_config), so + # evaluate each requirement's config constraints once against it. + 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: + # A requirement whose configurable checks ran with an invalid + # config can't be trusted: force FAIL regardless of the + # finding's own status. + 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 +77,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..0ce742f864b 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,21 @@ def transform( Returns: - None """ + # Evaluate each requirement's config constraints once against the + # scan-global applied config; a requirement whose configurable checks + # ran with a config too loose to trust is forced to FAIL. + 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 +74,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..f6a3453c556 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,21 @@ def transform( Returns: - None """ + # Evaluate each requirement's config constraints once against the + # scan-global applied config; a requirement whose configurable checks + # ran with a config too loose to trust is forced to FAIL. + 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 +73,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..fad959a7e4c 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,21 @@ def transform( Returns: - None """ + # Evaluate each requirement's config constraints once against the + # scan-global applied config; a requirement whose configurable checks + # ran with a config too loose to trust is forced to FAIL. + 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 +73,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..a0b841f40ca 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,21 @@ def transform( Returns: - None """ + # Evaluate each requirement's config constraints once against the + # scan-global applied config; a requirement whose configurable checks + # ran with a config too loose to trust is forced to FAIL. + 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 +73,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..0a559225d00 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,21 @@ def transform( Returns: - None """ + # Evaluate each requirement's config constraints once against the + # scan-global applied config; a requirement whose configurable checks + # ran with a config too loose to trust is forced to FAIL. + 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 +74,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..4be14f04103 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,21 @@ def transform( Returns: - None """ + # Evaluate each requirement's config constraints once against the + # scan-global applied config; a requirement whose configurable checks + # ran with a config too loose to trust is forced to FAIL. + 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 +74,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..d67d3c99409 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,21 @@ def transform( Returns: - None """ + # Evaluate each requirement's config constraints once against the + # scan-global applied config; a requirement whose configurable checks + # ran with a config too loose to trust is forced to FAIL. + 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 +74,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..9254d411352 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,21 @@ def transform( Returns: - None """ + # Evaluate each requirement's config constraints once against the + # scan-global applied config; a requirement whose configurable checks + # ran with a config too loose to trust is forced to FAIL. + 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 +67,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..9e95a0cc7ad 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( @@ -28,6 +33,10 @@ def get_ens_table( pass_count = [] fail_count = [] muted_count = [] + # The applied config is scan-global (the provider's audit_config). Evaluate + # each requirement's config constraints once against it (memoised by Id). + 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 +44,14 @@ def get_ens_table( if compliance.Framework == "ENS": provider = compliance.Provider for requirement in compliance.Requirements: + # A requirement whose configurable checks ran with an invalid + # config can't be trusted: treat the finding as FAIL. + 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 @@ -58,7 +75,7 @@ def get_ens_table( 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) @@ -67,7 +84,7 @@ def get_ens_table( marcos[marco_categoria][ "Estado" ] = f"{Fore.RED}NO CUMPLE{Style.RESET_ALL}" - 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 attribute.Nivel == "opcional": marcos[marco_categoria]["Opcional"] += 1 diff --git a/prowler/lib/outputs/compliance/ens/ens_aws.py b/prowler/lib/outputs/compliance/ens/ens_aws.py index 40d185294b4..ed416b3533b 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,22 @@ def transform( Returns: - None """ + # Evaluate each requirement's config constraints once against the + # scan-global applied config; a requirement whose configurable checks + # ran with a config too loose to trust is forced to FAIL. + 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 +76,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..a78debb539f 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,22 @@ def transform( Returns: - None """ + # Evaluate each requirement's config constraints once against the + # scan-global applied config; a requirement whose configurable checks + # ran with a config too loose to trust is forced to FAIL. + 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 +76,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..893b1411420 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,22 @@ def transform( Returns: - None """ + # Evaluate each requirement's config constraints once against the + # scan-global applied config; a requirement whose configurable checks + # ran with a config too loose to trust is forced to FAIL. + 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 +76,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..25d640dddbe 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,27 @@ def transform( - None """ + # Evaluate each requirement's config constraints once against the + # scan-global applied config; a requirement whose configurable checks ran + # with a config too loose to trust is forced to FAIL. + 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 +76,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..30cf04360c5 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,10 @@ def get_generic_compliance_table( pass_count = [] fail_count = [] muted_count = [] + # The applied config is scan-global (the provider's audit_config). Evaluate + # each requirement's config constraints once against it (memoised by Id). + 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 +34,28 @@ def get_generic_compliance_table( and compliance.Version in compliance_framework.upper() and compliance.Provider.upper() in compliance_framework.upper() ): + # A requirement whose configurable checks ran with an invalid + # config can't be trusted: treat the finding as FAIL if any of + # the requirements it maps to has an invalid config. + 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..462fa749085 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,21 @@ def transform( Returns: - None """ + # Evaluate each requirement's config constraints once against the + # scan-global applied config; a requirement whose configurable checks + # ran with a config too loose to trust is forced to FAIL. + 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 +67,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..07c53673c96 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,21 @@ def transform( Returns: - None """ + # Evaluate each requirement's config constraints once against the + # scan-global applied config; a requirement whose configurable checks + # ran with a config too loose to trust is forced to FAIL. + 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 +67,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..433b0e81344 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,21 @@ def transform( Returns: - None """ + # Evaluate each requirement's config constraints once against the + # scan-global applied config; a requirement whose configurable checks + # ran with a config too loose to trust is forced to FAIL. + 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 +67,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..e0f54a8f02e 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,21 @@ def transform( Returns: - None """ + # Evaluate each requirement's config constraints once against the + # scan-global applied config; a requirement whose configurable checks + # ran with a config too loose to trust is forced to FAIL. + 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 +67,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..5ea78838033 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,21 @@ def transform( Returns: - None """ + # Evaluate each requirement's config constraints once against the + # scan-global applied config; a requirement whose configurable checks + # ran with a config too loose to trust is forced to FAIL. + 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 +67,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..6599ca1340c 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,21 @@ def transform( Returns: - None """ + # Evaluate each requirement's config constraints once against the + # scan-global applied config; a requirement whose configurable checks + # ran with a config too loose to trust is forced to FAIL. + 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 +67,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..c6e8a6b0edb 100644 --- a/prowler/lib/outputs/compliance/kisa_ismsp/kisa_ismsp.py +++ b/prowler/lib/outputs/compliance/kisa_ismsp/kisa_ismsp.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_kisa_ismsp_table( @@ -25,6 +30,10 @@ def get_kisa_ismsp_table( pass_count = [] fail_count = [] muted_count = [] + # The applied config is scan-global (the provider's audit_config). Evaluate + # each requirement's config constraints once against it (memoised by Id). + 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 +44,14 @@ def get_kisa_ismsp_table( ): provider = compliance.Provider for requirement in compliance.Requirements: + # A requirement whose configurable checks ran with an invalid + # config can't be trusted: treat the finding as FAIL. + 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 @@ -52,10 +69,10 @@ def get_kisa_ismsp_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) @@ -65,9 +82,9 @@ def get_kisa_ismsp_table( section_seen[section].add(index) if finding.muted: sections[section]["Muted"] += 1 - elif finding.status == "FAIL": + elif effective_status == "FAIL": sections[section]["Status"]["FAIL"] += 1 - elif finding.status == "PASS": + elif effective_status == "PASS": sections[section]["Status"]["PASS"] += 1 # Add results to table 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..dcde83c059d 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,22 @@ def transform( Returns: - None """ + # Evaluate each requirement's config constraints once against the + # scan-global applied config; a requirement whose configurable checks + # ran with a config too loose to trust is forced to FAIL. + 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 +71,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..5fc56b251c1 100644 --- a/prowler/lib/outputs/compliance/mitre_attack/mitre_attack.py +++ b/prowler/lib/outputs/compliance/mitre_attack/mitre_attack.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_mitre_attack_table( @@ -24,6 +29,10 @@ def get_mitre_attack_table( pass_count = [] fail_count = [] muted_count = [] + # The applied config is scan-global (the provider's audit_config). Evaluate + # each requirement's config constraints once against it (memoised by Id). + 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,14 @@ def get_mitre_attack_table( ): provider = compliance.Provider for requirement in compliance.Requirements: + # A requirement whose configurable checks ran with an invalid + # config can't be trusted: treat the finding as FAIL. + config_status = resolve_requirement_config_status( + requirement, audit_config, config_status_cache + ) + effective_status = get_effective_status( + finding.status, config_status + ) for tactic in requirement.Tactics: if tactic not in tactics: tactics[tactic] = {"FAIL": 0, "PASS": 0, "Muted": 0} @@ -43,10 +60,10 @@ def get_mitre_attack_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) @@ -56,9 +73,9 @@ def get_mitre_attack_table( tactic_seen[tactic].add(index) if finding.muted: tactics[tactic]["Muted"] += 1 - elif finding.status == "FAIL": + elif effective_status == "FAIL": tactics[tactic]["FAIL"] += 1 - elif finding.status == "PASS": + elif effective_status == "PASS": tactics[tactic]["PASS"] += 1 # Add results to table tactics = dict(sorted(tactics.items())) 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..8530fae9c80 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,22 @@ def transform( Returns: - None """ + # Evaluate each requirement's config constraints once against the + # scan-global applied config; a requirement whose configurable checks + # ran with a config too loose to trust is forced to FAIL. + 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 +82,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..6b06806bf16 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,22 @@ def transform( Returns: - None """ + # Evaluate each requirement's config constraints once against the + # scan-global applied config; a requirement whose configurable checks + # ran with a config too loose to trust is forced to FAIL. + 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 +83,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..b317e7c7f44 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,22 @@ def transform( Returns: - None """ + # Evaluate each requirement's config constraints once against the + # scan-global applied config; a requirement whose configurable checks + # ran with a config too loose to trust is forced to FAIL. + 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 +82,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/prowler_threatscore/prowler_threatscore.py b/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore.py index b17307f04af..1c6b879a71a 100644 --- a/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore.py +++ b/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore.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, +) from prowler.lib.check.compliance_models import Compliance @@ -32,6 +37,10 @@ def get_prowler_threatscore_table( score_per_pillar = {} max_score_per_pillar = {} counted_findings_per_pillar = {} + # The applied config is scan-global (the provider's audit_config). Evaluate + # each requirement's config constraints once against it (memoised by Id). + 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,15 @@ def get_prowler_threatscore_table( if compliance.Framework == "ProwlerThreatScore": provider = compliance.Provider for requirement in compliance.Requirements: + # A requirement whose configurable checks ran with an invalid + # config can't be trusted: treat the finding as FAIL (it stops + # contributing to the pillar/generic score and counts as FAIL). + 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 @@ -57,7 +75,7 @@ def get_prowler_threatscore_table( index not in counted_findings_per_pillar[pillar] and not finding.muted ): - if finding.status == "PASS": + if effective_status == "PASS": score_per_pillar[pillar] += ( attribute.LevelOfRisk * attribute.Weight ) @@ -74,10 +92,10 @@ def get_prowler_threatscore_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) @@ -87,14 +105,14 @@ def get_prowler_threatscore_table( pillar_seen[pillar].add(index) if finding.muted: pillars[pillar]["Muted"] += 1 - elif finding.status == "FAIL": + elif effective_status == "FAIL": pillars[pillar]["FAIL"] += 1 - elif finding.status == "PASS": + elif effective_status == "PASS": pillars[pillar]["PASS"] += 1 # Generic score if index not in counted_findings_generic and not finding.muted: - if finding.status == "PASS": + if effective_status == "PASS": generic_score += ( attribute.LevelOfRisk * attribute.Weight ) 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..e11fa9c278d 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,22 @@ def transform( Returns: - None """ + # Evaluate each requirement's config constraints once against the + # scan-global applied config; a requirement whose configurable checks + # ran with a config too loose to trust is forced to FAIL. + 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 +72,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..42215f02671 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,22 @@ def transform( Returns: - None """ + # Evaluate each requirement's config constraints once against the + # scan-global applied config; a requirement whose configurable checks + # ran with a config too loose to trust is forced to FAIL. + 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 +72,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..a50008a7b48 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,22 @@ def transform( Returns: - None """ + # Evaluate each requirement's config constraints once against the + # scan-global applied config; a requirement whose configurable checks + # ran with a config too loose to trust is forced to FAIL. + 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 +72,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..72d49b1da6a 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,22 @@ def transform( Returns: - None """ + # Evaluate each requirement's config constraints once against the + # scan-global applied config; a requirement whose configurable checks + # ran with a config too loose to trust is forced to FAIL. + 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 +72,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..f2e80c5028b 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,22 @@ def transform( Returns: - None """ + # Evaluate each requirement's config constraints once against the + # scan-global applied config; a requirement whose configurable checks + # ran with a config too loose to trust is forced to FAIL. + 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 +72,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..a6bafa99cef 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,22 @@ def transform( Returns: - None """ + # Evaluate each requirement's config constraints once against the + # scan-global applied config; a requirement whose configurable checks + # ran with a config too loose to trust is forced to FAIL. + 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 +72,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..fed5f13457a 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,22 @@ def _transform( for check_id in all_checks: check_req_map.setdefault(check_id, []).append(req) + # The applied config is scan-global (the provider's audit_config). A + # requirement whose config constraints aren't satisfied is FAILed even + # when its findings PASS, mirroring the CSV/CLI behaviour. + requirement_config_status = build_requirement_config_status( + framework.requirements + ) + 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 +255,17 @@ def _build_compliance_finding( framework: ComplianceFramework, requirement, compliance_name: str, + config_status: tuple = (True, ""), ) -> ComplianceFinding: try: + # If the requirement's config isn't valid, the requirement FAILs even + # though the check (and finding) passed. The Check keeps the real + # finding status; the Compliance status reflects the requirement. + 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 @@ -293,7 +315,7 @@ def _build_compliance_finding( else None ), ), - message=finding.status_extended, + message=message, metadata=Metadata( event_code=finding.check_id, product=Product( diff --git a/prowler/lib/outputs/compliance/universal/universal_table.py b/prowler/lib/outputs/compliance/universal/universal_table.py index e54ad5155c2..f9165626fe2 100644 --- a/prowler/lib/outputs/compliance/universal/universal_table.py +++ b/prowler/lib/outputs/compliance/universal/universal_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, +) from prowler.lib.check.compliance_models import ComplianceFramework @@ -167,6 +172,10 @@ def _render_grouped( pass_count = [] fail_count = [] muted_count = [] + # A requirement whose configurable checks ran with an invalid config can't be + # trusted: treat the finding as FAIL (config is scan-global, memoised by id). + audit_config = get_scan_audit_config() + config_status_cache = {} for index, finding in enumerate(findings): check_id = finding.check_metadata.CheckID @@ -174,6 +183,12 @@ 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 + ), + ) 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} @@ -183,10 +198,10 @@ def _render_grouped( 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) @@ -196,9 +211,9 @@ def _render_grouped( group_seen[group_key].add(index) if finding.muted: groups[group_key]["Muted"] += 1 - elif finding.status == "FAIL": + elif effective_status == "FAIL": groups[group_key]["FAIL"] += 1 - elif finding.status == "PASS": + elif effective_status == "PASS": groups[group_key]["PASS"] += 1 if not _print_overview( @@ -275,6 +290,10 @@ def _render_split( pass_count = [] fail_count = [] muted_count = [] + # A requirement whose configurable checks ran with an invalid config can't be + # trusted: treat the finding as FAIL (config is scan-global, memoised by id). + audit_config = get_scan_audit_config() + config_status_cache = {} for index, finding in enumerate(findings): check_id = finding.check_metadata.CheckID @@ -282,6 +301,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 + ), + ) for group_key in _get_group_key(req, group_by): if group_key not in groups: groups[group_key] = { @@ -303,16 +328,16 @@ def _render_split( group_muted_seen[group_key].add(index) groups[group_key]["Muted"] += 1 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) 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": + if effective_status == "FAIL": groups[group_key][sv]["FAIL"] += 1 else: groups[group_key][sv]["PASS"] += 1 @@ -397,6 +422,10 @@ def _render_scored( generic_score = 0 max_generic_score = 0 counted_generic = [] + # A requirement whose configurable checks ran with an invalid config can't be + # trusted: treat the finding as FAIL (config is scan-global, memoised by id). + audit_config = get_scan_audit_config() + config_status_cache = {} for index, finding in enumerate(findings): check_id = finding.check_metadata.CheckID @@ -404,6 +433,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 + ), + ) for group_key in _get_group_key(req, group_by): attrs = req.attributes risk = attrs.get(risk_field, 0) @@ -417,7 +452,7 @@ def _render_scored( counted_per_group[group_key] = [] if index not in counted_per_group[group_key] and not finding.muted: - if finding.status == "PASS": + if effective_status == "PASS": score_per_group[group_key] += risk * weight max_score_per_group[group_key] += risk * weight counted_per_group[group_key].append(index) @@ -426,10 +461,10 @@ def _render_scored( 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) @@ -439,13 +474,13 @@ def _render_scored( group_seen[group_key].add(index) if finding.muted: groups[group_key]["Muted"] += 1 - elif finding.status == "FAIL": + elif effective_status == "FAIL": groups[group_key]["FAIL"] += 1 - elif finding.status == "PASS": + elif effective_status == "PASS": groups[group_key]["PASS"] += 1 if index not in counted_generic and not finding.muted: - if finding.status == "PASS": + if effective_status == "PASS": generic_score += risk * weight max_generic_score += risk * weight counted_generic.append(index) 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..30bfdd9faef --- /dev/null +++ b/tests/lib/check/compliance_config_constraint_model_test.py @@ -0,0 +1,113 @@ +"""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" + + +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 + ) + + 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") + + +class Test_ConfigRequirements_On_Compliance: + def test_requirements_without_constraints_default_to_none(self): + compliance = Compliance(**json.load(open(_CIS_6_0))) + # 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(**json.load(open(_CIS_6_0))) + 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(**json.load(open(_CIS_6_0))) + 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 a list of dicts with the same keys. + sample = next(r for r in universal.requirements if r.config_requirements) + entry = sample.config_requirements[0] + assert isinstance(entry, dict) + assert set(entry) == {"Check", "ConfigKey", "Operator", "Value"} + + def test_requirements_without_constraints_are_none_in_universal(self): + legacy = Compliance(**json.load(open(_CIS_6_0))) + 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..8a1edc2d0ea --- /dev/null +++ b/tests/lib/check/compliance_config_eval_test.py @@ -0,0 +1,240 @@ +from types import SimpleNamespace + +from prowler.lib.check.compliance_config_eval import ( + CONFIG_NOT_VALID_PREFIX, + apply_config_status, + build_requirement_config_status, + evaluate_config_constraints, + get_effective_status, + get_scan_audit_config, + 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 + assert "max_unused_access_keys_days" in reason + assert "120" 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 + assert "b.k2" in reason + + +# 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_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_prefixes_reason(self): + status, extended = apply_config_status("PASS", "ext", (False, "bad config")) + assert status == "FAIL" + assert extended.startswith(CONFIG_NOT_VALID_PREFIX) + assert "bad config" 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 in this unit-test context → safe empty mapping. + assert get_scan_audit_config() == {} 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..5309ceea6b6 --- /dev/null +++ b/tests/lib/check/compliance_config_requirements_data_test.py @@ -0,0 +1,161 @@ +"""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): + assert set(constraint) == { + "Check", + "ConfigKey", + "Operator", + "Value", + }, 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" + + +@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/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..56c5f51674f --- /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("CONFIG 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("CONFIG 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("CONFIG 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..99c6268c43a --- /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("CONFIG 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/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..336af73df23 --- /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("CONFIG 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("CONFIG NOT VALID" not in r.StatusExtended for r in rows) 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..18e706077c0 --- /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 ``[CONFIG NOT VALID]`` prefix. 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 "[CONFIG 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 "[CONFIG 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/universal_table_config_requirements_test.py b/tests/lib/outputs/compliance/universal/universal_table_config_requirements_test.py new file mode 100644 index 00000000000..a684a79e6a0 --- /dev/null +++ b/tests/lib/outputs/compliance/universal/universal_table_config_requirements_test.py @@ -0,0 +1,90 @@ +"""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): + 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="/tmp", + 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): + out = _render({"mute_non_default_regions": True}, capsys) + assert "FAIL(2)" in out + assert "PASS(2)" not in out + + def test_valid_config_keeps_pass_count(self, capsys): + out = _render({"mute_non_default_regions": False}, capsys) + assert "PASS(2)" in out + assert "FAIL(2)" not in out + + def test_absent_config_keeps_pass_count(self, capsys): + out = _render({}, capsys) + assert "PASS(2)" in out + assert "FAIL(2)" not in out From 9cd1e9260f1fda8b9ec3722099da5807bcfd24e9 Mon Sep 17 00:00:00 2001 From: pedrooot Date: Tue, 23 Jun 2026 12:14:27 +0200 Subject: [PATCH 02/18] chore(changelog): update with latest changes --- prowler/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index baac7b8625e..0660b5697ae 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -53,6 +53,7 @@ All notable changes to the **Prowler SDK** are documented in this file. - `acmpca_certificate_authority_pqc_key_algorithm` check and new `acmpca` service for AWS provider to verify AWS Private CA certificate authorities use a post-quantum (ML-DSA) key algorithm [(#11318)](https://github.com/prowler-cloud/prowler/pull/11318) - `rolesanywhere_trust_anchor_pqc_pki` check and new `rolesanywhere` service for AWS provider to verify IAM Roles Anywhere trust anchors are backed by a post-quantum (ML-DSA) PKI [(#11319)](https://github.com/prowler-cloud/prowler/pull/11319) - Kubernetes core checks for container CPU limits, CPU requests, memory limits, memory requests, fixed image tags, liveness probes, and readiness probes [(#11373)](https://github.com/prowler-cloud/prowler/pull/11373) +- 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) [(#11667)](https://github.com/prowler-cloud/prowler/pull/11667) ### 🔄 Changed From 075408951b0cae712361b4c441f1f80f3daae48a Mon Sep 17 00:00:00 2001 From: pedrooot Date: Tue, 23 Jun 2026 13:12:14 +0200 Subject: [PATCH 03/18] chore(compliance): handle scan config for multi-providers in universal compliance --- prowler/compliance/csa_ccm_4.0.json | 23 +++++++ prowler/compliance/dora_2022_2554.json | 20 +++++++ prowler/lib/check/compliance_config_eval.py | 58 ++++++++++++++---- prowler/lib/check/compliance_models.py | 9 +++ ...compliance_config_constraint_model_test.py | 25 +++++++- .../lib/check/compliance_config_eval_test.py | 60 +++++++++++++++++++ ...ompliance_config_requirements_data_test.py | 40 +++++++++++-- 7 files changed, 218 insertions(+), 17 deletions(-) diff --git a/prowler/compliance/csa_ccm_4.0.json b/prowler/compliance/csa_ccm_4.0.json index df541573af5..1cb7428e517 100644 --- a/prowler/compliance/csa_ccm_4.0.json +++ b/prowler/compliance/csa_ccm_4.0.json @@ -233,6 +233,7 @@ "config_requirements": [ { "Check": "securityhub_enabled", + "Provider": "aws", "ConfigKey": "mute_non_default_regions", "Operator": "eq", "Value": false @@ -346,12 +347,14 @@ "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 @@ -1004,6 +1007,7 @@ "config_requirements": [ { "Check": "drs_job_exist", + "Provider": "aws", "ConfigKey": "mute_non_default_regions", "Operator": "eq", "Value": false @@ -1450,18 +1454,21 @@ "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 @@ -1713,6 +1720,7 @@ "config_requirements": [ { "Check": "storage_smb_channel_encryption_with_secure_algorithm", + "Provider": "azure", "ConfigKey": "recommended_smb_channel_encryption_algorithms", "Operator": "subset", "Value": [ @@ -1866,6 +1874,7 @@ "config_requirements": [ { "Check": "acm_certificates_with_secure_key_algorithms", + "Provider": "aws", "ConfigKey": "insecure_key_algorithms", "Operator": "superset", "Value": [ @@ -1875,6 +1884,7 @@ }, { "Check": "sqlserver_recommended_minimal_tls_version", + "Provider": "azure", "ConfigKey": "recommended_minimal_tls_versions", "Operator": "subset", "Value": [ @@ -2429,6 +2439,7 @@ "config_requirements": [ { "Check": "config_recorder_all_regions_enabled", + "Provider": "aws", "ConfigKey": "mute_non_default_regions", "Operator": "eq", "Value": false @@ -2675,6 +2686,7 @@ "config_requirements": [ { "Check": "config_recorder_all_regions_enabled", + "Provider": "aws", "ConfigKey": "mute_non_default_regions", "Operator": "eq", "Value": false @@ -3097,6 +3109,7 @@ "config_requirements": [ { "Check": "sqlserver_recommended_minimal_tls_version", + "Provider": "azure", "ConfigKey": "recommended_minimal_tls_versions", "Operator": "subset", "Value": [ @@ -3514,12 +3527,14 @@ "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 @@ -6380,6 +6395,7 @@ "config_requirements": [ { "Check": "guardduty_is_enabled", + "Provider": "aws", "ConfigKey": "mute_non_default_regions", "Operator": "eq", "Value": false @@ -6691,12 +6707,14 @@ "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 @@ -7749,6 +7767,7 @@ "config_requirements": [ { "Check": "guardduty_is_enabled", + "Provider": "aws", "ConfigKey": "mute_non_default_regions", "Operator": "eq", "Value": false @@ -8035,12 +8054,14 @@ "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 @@ -8630,6 +8651,7 @@ "config_requirements": [ { "Check": "guardduty_is_enabled", + "Provider": "aws", "ConfigKey": "mute_non_default_regions", "Operator": "eq", "Value": false @@ -8906,6 +8928,7 @@ "config_requirements": [ { "Check": "guardduty_is_enabled", + "Provider": "aws", "ConfigKey": "mute_non_default_regions", "Operator": "eq", "Value": false diff --git a/prowler/compliance/dora_2022_2554.json b/prowler/compliance/dora_2022_2554.json index e57557edee0..3b828059da0 100644 --- a/prowler/compliance/dora_2022_2554.json +++ b/prowler/compliance/dora_2022_2554.json @@ -219,24 +219,28 @@ "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 @@ -329,6 +333,7 @@ "config_requirements": [ { "Check": "acm_certificates_with_secure_key_algorithms", + "Provider": "aws", "ConfigKey": "insecure_key_algorithms", "Operator": "superset", "Value": [ @@ -338,6 +343,7 @@ }, { "Check": "sqlserver_recommended_minimal_tls_version", + "Provider": "azure", "ConfigKey": "recommended_minimal_tls_versions", "Operator": "subset", "Value": [ @@ -347,6 +353,7 @@ }, { "Check": "storage_smb_channel_encryption_with_secure_algorithm", + "Provider": "azure", "ConfigKey": "recommended_smb_channel_encryption_algorithms", "Operator": "subset", "Value": [ @@ -402,6 +409,7 @@ "config_requirements": [ { "Check": "accessanalyzer_enabled", + "Provider": "aws", "ConfigKey": "mute_non_default_regions", "Operator": "eq", "Value": false @@ -646,12 +654,14 @@ "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 @@ -812,6 +822,7 @@ "config_requirements": [ { "Check": "securityhub_enabled", + "Provider": "aws", "ConfigKey": "mute_non_default_regions", "Operator": "eq", "Value": false @@ -989,12 +1000,14 @@ "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 @@ -1119,6 +1132,7 @@ "config_requirements": [ { "Check": "securityhub_enabled", + "Provider": "aws", "ConfigKey": "mute_non_default_regions", "Operator": "eq", "Value": false @@ -1189,12 +1203,14 @@ "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 @@ -1268,6 +1284,7 @@ "config_requirements": [ { "Check": "accessanalyzer_enabled", + "Provider": "aws", "ConfigKey": "mute_non_default_regions", "Operator": "eq", "Value": false @@ -1332,6 +1349,7 @@ "config_requirements": [ { "Check": "accessanalyzer_enabled", + "Provider": "aws", "ConfigKey": "mute_non_default_regions", "Operator": "eq", "Value": false @@ -1385,12 +1403,14 @@ "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/lib/check/compliance_config_eval.py b/prowler/lib/check/compliance_config_eval.py index 2eefe086932..d4cabf49c45 100644 --- a/prowler/lib/check/compliance_config_eval.py +++ b/prowler/lib/check/compliance_config_eval.py @@ -54,17 +54,24 @@ def _check_operator(applied: Any, operator: str, expected: Any) -> bool: def evaluate_config_constraints( config_requirements: Optional[list], audit_config: Optional[dict], + 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`` - and ``Value``. ``None``/empty means the requirement has no config - expectations. + 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 @@ -86,11 +93,17 @@ def evaluate_config_constraints( 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) + + if provider and provider_type and provider != provider_type: + # Constraint scoped to another provider → not applicable to this scan. + continue if config_key not in audit_config: # Config not explicitly set → default is assumed adequate. @@ -124,6 +137,21 @@ def get_scan_audit_config() -> dict: 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 + ``""`` if no provider is set, which disables provider scoping). + """ + try: + from prowler.providers.common.provider import Provider + + return Provider.get_global_provider().type or "" + except Exception: + 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) @@ -145,6 +173,7 @@ def _requirement_constraints(requirement: Any) -> Optional[list]: def build_requirement_config_status( requirements: list, audit_config: Optional[dict] = None, + provider_type: Optional[str] = None, ) -> dict: """Map every requirement id to its ``(is_compliant, reason)`` config verdict. @@ -155,15 +184,19 @@ def build_requirement_config_status( 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. """ 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 + constraints, audit_config, provider_type ) return status @@ -172,21 +205,26 @@ def resolve_requirement_config_status( requirement: Any, audit_config: dict, 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. ``cache`` is keyed by requirement id and reused - across the whole table build. + across the whole table build. ``provider_type`` scopes the constraints to the + provider being scanned; resolved via ``get_scan_provider_type`` when omitted. """ req_id = _requirement_id(requirement) if req_id not in cache: constraints = _requirement_constraints(requirement) - cache[req_id] = ( - evaluate_config_constraints(constraints, audit_config) - if constraints - else (True, "") - ) + 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] diff --git a/prowler/lib/check/compliance_models.py b/prowler/lib/check/compliance_models.py index 090283c1d78..48e3e8d6b05 100644 --- a/prowler/lib/check/compliance_models.py +++ b/prowler/lib/check/compliance_models.py @@ -311,6 +311,12 @@ class Compliance_Requirement_ConfigConstraint(BaseModel): ``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). @@ -330,6 +336,9 @@ class Compliance_Requirement_ConfigConstraint(BaseModel): # ``mute_non_default_regions == false`` constraint) instead of coercing # them to 0/1. Value: Union[bool, int, float, str, list] + # Provider this constraint applies to (e.g. ``aws``). ``None`` means it + # applies whenever the requirement runs (single-provider frameworks). + Provider: Optional[str] = None # TODO: move this to compliance folder diff --git a/tests/lib/check/compliance_config_constraint_model_test.py b/tests/lib/check/compliance_config_constraint_model_test.py index 30bfdd9faef..64223e1bdbd 100644 --- a/tests/lib/check/compliance_config_constraint_model_test.py +++ b/tests/lib/check/compliance_config_constraint_model_test.py @@ -70,6 +70,24 @@ 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): @@ -99,11 +117,14 @@ def test_config_requirements_carried_to_universal(self): assert legacy_with == universal_with assert universal_with, "expected at least one requirement with constraints" - # The constraint payload survives as a list of dicts with the same keys. + # The constraint payload survives as a list of dicts with the same keys + # (``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, dict) - assert set(entry) == {"Check", "ConfigKey", "Operator", "Value"} + assert set(entry) == {"Check", "Provider", "ConfigKey", "Operator", "Value"} + assert entry["Provider"] is None def test_requirements_without_constraints_are_none_in_universal(self): legacy = Compliance(**json.load(open(_CIS_6_0))) diff --git a/tests/lib/check/compliance_config_eval_test.py b/tests/lib/check/compliance_config_eval_test.py index 8a1edc2d0ea..16aa74cdfbf 100644 --- a/tests/lib/check/compliance_config_eval_test.py +++ b/tests/lib/check/compliance_config_eval_test.py @@ -1,4 +1,5 @@ from types import SimpleNamespace +from unittest.mock import patch from prowler.lib.check.compliance_config_eval import ( CONFIG_NOT_VALID_PREFIX, @@ -7,6 +8,7 @@ evaluate_config_constraints, get_effective_status, get_scan_audit_config, + get_scan_provider_type, resolve_requirement_config_status, ) @@ -146,6 +148,47 @@ def test_multiple_constraints_first_violation_reported(self): assert "b.k2" 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_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 = [ { @@ -238,3 +281,20 @@ class Test_get_scan_audit_config: def test_returns_empty_without_global_provider(self): # No global provider set in this unit-test context → safe empty mapping. assert get_scan_audit_config() == {} + + +class Test_get_scan_provider_type: + def test_returns_empty_when_no_global_provider(self): + # Unresolvable provider → scoping disabled (empty string). + with patch( + "prowler.providers.common.provider.Provider.get_global_provider", + side_effect=Exception("no provider"), + ): + 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 index 5309ceea6b6..3b3ba5757fe 100644 --- a/tests/lib/check/compliance_config_requirements_data_test.py +++ b/tests/lib/check/compliance_config_requirements_data_test.py @@ -87,11 +87,11 @@ def test_there_are_constraints_to_validate(): ) class Test_Constraint_Wellformed: def test_has_required_keys(self, fname, req_id, checks, constraint): - assert set(constraint) == { - "Check", - "ConfigKey", - "Operator", - "Value", + 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): @@ -150,6 +150,36 @@ def test_region_mute_constraints_use_eq_false(self): ), 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] ) From 6f17c87945679dfd1a33659399d183ac8e70e3ba Mon Sep 17 00:00:00 2001 From: pedrooot Date: Tue, 23 Jun 2026 13:12:54 +0200 Subject: [PATCH 04/18] docs(scan-config): add info about scan config applied to compliance --- docs/developer-guide/configurable-checks.mdx | 4 + .../security-compliance-framework.mdx | 190 +++++++++++++++++- 2 files changed, 186 insertions(+), 8 deletions(-) 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..cb397fe6ae5 100644 --- a/docs/developer-guide/security-compliance-framework.mdx +++ b/docs/developer-guide/security-compliance-framework.mdx @@ -23,7 +23,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 +70,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 +192,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 +259,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 +281,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 +474,185 @@ 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 `status_extended` is prefixed with `[CONFIG NOT VALID]` plus the reason (via `apply_config_status()`). 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: [CONFIG NOT VALID] config not valid for requirement: + iam_user_accesskey_unused.max_unused_access_keys_days=120 + does not satisfy lte 45. +``` + +The `[CONFIG NOT VALID]` prefix 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 +783,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`. From 603630726f4dcd9806574529a1917b60329d6d35 Mon Sep 17 00:00:00 2001 From: pedrooot Date: Tue, 23 Jun 2026 14:09:39 +0200 Subject: [PATCH 05/18] chore(revision): solve comments --- prowler/lib/check/compliance_config_eval.py | 121 +++++++++++--- prowler/lib/check/compliance_models.py | 4 +- .../asd_essential_eight.py | 42 ++--- .../asd_essential_eight_aws.py | 3 - .../aws_well_architected.py | 3 - prowler/lib/outputs/compliance/c5/c5.py | 42 ++--- prowler/lib/outputs/compliance/c5/c5_aws.py | 3 - prowler/lib/outputs/compliance/c5/c5_azure.py | 3 - prowler/lib/outputs/compliance/c5/c5_gcp.py | 3 - prowler/lib/outputs/compliance/ccc/ccc.py | 42 ++--- prowler/lib/outputs/compliance/ccc/ccc_aws.py | 3 - .../lib/outputs/compliance/ccc/ccc_azure.py | 3 - prowler/lib/outputs/compliance/ccc/ccc_gcp.py | 3 - prowler/lib/outputs/compliance/cis/cis.py | 62 +++---- .../compliance/cis/cis_alibabacloud.py | 3 - prowler/lib/outputs/compliance/cis/cis_aws.py | 5 - .../lib/outputs/compliance/cis/cis_azure.py | 3 - prowler/lib/outputs/compliance/cis/cis_gcp.py | 3 - .../lib/outputs/compliance/cis/cis_github.py | 3 - .../compliance/cis/cis_googleworkspace.py | 3 - .../outputs/compliance/cis/cis_kubernetes.py | 3 - .../lib/outputs/compliance/cis/cis_m365.py | 3 - .../outputs/compliance/cis/cis_oraclecloud.py | 3 - .../cisa_scuba/cisa_scuba_googleworkspace.py | 3 - prowler/lib/outputs/compliance/ens/ens.py | 21 +-- prowler/lib/outputs/compliance/ens/ens_aws.py | 3 - .../lib/outputs/compliance/ens/ens_azure.py | 3 - prowler/lib/outputs/compliance/ens/ens_gcp.py | 3 - .../lib/outputs/compliance/generic/generic.py | 3 - .../compliance/generic/generic_table.py | 5 - .../compliance/iso27001/iso27001_aws.py | 3 - .../compliance/iso27001/iso27001_azure.py | 3 - .../compliance/iso27001/iso27001_gcp.py | 3 - .../iso27001/iso27001_kubernetes.py | 3 - .../compliance/iso27001/iso27001_m365.py | 3 - .../compliance/iso27001/iso27001_nhn.py | 3 - .../compliance/kisa_ismsp/kisa_ismsp.py | 45 +++-- .../compliance/kisa_ismsp/kisa_ismsp_aws.py | 3 - .../compliance/mitre_attack/mitre_attack.py | 43 ++--- .../mitre_attack/mitre_attack_aws.py | 3 - .../mitre_attack/mitre_attack_azure.py | 3 - .../mitre_attack/mitre_attack_gcp.py | 3 - .../prowler_threatscore.py | 99 +++++------ .../prowler_threatscore_alibaba.py | 3 - .../prowler_threatscore_aws.py | 3 - .../prowler_threatscore_azure.py | 3 - .../prowler_threatscore_gcp.py | 3 - .../prowler_threatscore_kubernetes.py | 3 - .../prowler_threatscore_m365.py | 3 - .../compliance/universal/ocsf_compliance.py | 6 - .../compliance/universal/universal_table.py | 157 ++++++++---------- ...compliance_config_constraint_model_test.py | 14 +- .../lib/check/compliance_config_eval_test.py | 70 +++++++- ...niversal_table_config_requirements_test.py | 81 ++++++++- 54 files changed, 476 insertions(+), 494 deletions(-) diff --git a/prowler/lib/check/compliance_config_eval.py b/prowler/lib/check/compliance_config_eval.py index d4cabf49c45..a25c0440293 100644 --- a/prowler/lib/check/compliance_config_eval.py +++ b/prowler/lib/check/compliance_config_eval.py @@ -52,8 +52,8 @@ def _check_operator(applied: Any, operator: str, expected: Any) -> bool: def evaluate_config_constraints( - config_requirements: Optional[list], - audit_config: Optional[dict], + 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. @@ -120,20 +120,25 @@ def evaluate_config_constraints( return True, "" -def get_scan_audit_config() -> dict: +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 - ``{}`` if no provider is set or audit_config is unavailable). + 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 Exception: + except (AttributeError, ImportError): + # No global provider set, or provider package unavailable. return {} @@ -141,14 +146,19 @@ 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 - ``""`` if no provider is set, which disables provider scoping). + 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 Exception: + except (AttributeError, ImportError): + # No global provider set, or provider package unavailable. return "" @@ -171,10 +181,10 @@ def _requirement_constraints(requirement: Any) -> Optional[list]: def build_requirement_config_status( - requirements: list, - audit_config: Optional[dict] = None, + requirements: list[Any], + audit_config: Optional[dict[str, Any]] = None, provider_type: Optional[str] = None, -) -> dict: +) -> 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 @@ -186,6 +196,10 @@ def build_requirement_config_status( 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() @@ -203,16 +217,26 @@ def build_requirement_config_status( def resolve_requirement_config_status( requirement: Any, - audit_config: dict, + 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. ``cache`` is keyed by requirement id and reused - across the whole table build. ``provider_type`` scopes the constraints to the - provider being scanned; resolved via ``get_scan_provider_type`` when omitted. + 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: @@ -231,14 +255,25 @@ def resolve_requirement_config_status( def apply_config_status( status: str, status_extended: str, - config_status: Optional[tuple], + 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``. ``config_status`` is the ``(ok, reason)`` - tuple from ``build_requirement_config_status`` (``None`` → no constraints). + 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 @@ -250,9 +285,53 @@ def apply_config_status( def get_effective_status( status: str, - config_status: Optional[tuple], + config_status: Optional[tuple[bool, str]], ) -> str: - """Return the effective status for table aggregation (``FAIL`` if config invalid).""" + """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 48e3e8d6b05..d4ba50ed184 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 Literal, Optional, Union +from typing import Any, Literal, Optional, Union from pydantic.v1 import BaseModel, Field, ValidationError, root_validator @@ -335,7 +335,7 @@ class Compliance_Requirement_ConfigConstraint(BaseModel): # ``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] + Value: Union[bool, int, float, str, list[Any]] # Provider this constraint applies to (e.g. ``aws``). ``None`` means it # applies whenever the requirement runs (single-provider frameworks). Provider: Optional[str] = None 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 0d47ea43b5d..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 @@ -3,6 +3,8 @@ 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, @@ -24,13 +26,11 @@ 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 = "" - # The applied config is scan-global (the provider's audit_config). Evaluate - # each requirement's config constraints once against it (memoised by Id). audit_config = get_scan_audit_config() config_status_cache = {} for index, finding in enumerate(findings): @@ -40,8 +40,6 @@ def get_asd_essential_eight_table( if compliance.Framework == "ASD-Essential-Eight": provider = compliance.Provider for requirement in compliance.Requirements: - # A requirement whose configurable checks ran with an invalid - # config can't be trusted: treat the finding as FAIL. config_status = resolve_requirement_config_status( requirement, audit_config, config_status_cache ) @@ -56,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 effective_status == "FAIL": - if index not in fail_count: - fail_count.append(index) - elif effective_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 effective_status == "FAIL": - sections[section]["FAIL"] += 1 - elif effective_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 e326ae12822..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 @@ -40,9 +40,6 @@ def transform( Returns: - None """ - # Evaluate each requirement's config constraints once against the - # scan-global applied config; a requirement whose configurable checks - # ran with a config too loose to trust is forced to FAIL. requirement_config_status = build_requirement_config_status( compliance.Requirements ) 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 9f440e23fb6..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 @@ -40,9 +40,6 @@ def transform( Returns: - None """ - # Evaluate each requirement's config constraints once against the - # scan-global applied config; a requirement whose configurable checks - # ran with a config too loose to trust is forced to FAIL. requirement_config_status = build_requirement_config_status( compliance.Requirements ) diff --git a/prowler/lib/outputs/compliance/c5/c5.py b/prowler/lib/outputs/compliance/c5/c5.py index 5f3d3b2f800..003b2664cfe 100644 --- a/prowler/lib/outputs/compliance/c5/c5.py +++ b/prowler/lib/outputs/compliance/c5/c5.py @@ -3,6 +3,8 @@ 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, @@ -23,14 +25,12 @@ 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 = "" - # The applied config is scan-global (the provider's audit_config). Evaluate - # each requirement's config constraints once against it (memoised by Id). audit_config = get_scan_audit_config() config_status_cache = {} for index, finding in enumerate(findings): @@ -40,8 +40,6 @@ def get_c5_table( if compliance.Framework == "C5": provider = compliance.Provider for requirement in compliance.Requirements: - # A requirement whose configurable checks ran with an invalid - # config can't be trusted: treat the finding as FAIL. config_status = resolve_requirement_config_status( requirement, audit_config, config_status_cache ) @@ -53,29 +51,15 @@ def get_c5_table( if section not in sections: sections[section] = {"FAIL": 0, "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 effective_status == "FAIL": - if index not in fail_count: - fail_count.append(index) - elif effective_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 effective_status == "FAIL": - sections[section]["FAIL"] += 1 - elif effective_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 d62566b6d1d..b8a41154820 100644 --- a/prowler/lib/outputs/compliance/c5/c5_aws.py +++ b/prowler/lib/outputs/compliance/c5/c5_aws.py @@ -38,9 +38,6 @@ def transform( Returns: - None """ - # Evaluate each requirement's config constraints once against the - # scan-global applied config; a requirement whose configurable checks - # ran with a config too loose to trust is forced to FAIL. requirement_config_status = build_requirement_config_status( compliance.Requirements ) diff --git a/prowler/lib/outputs/compliance/c5/c5_azure.py b/prowler/lib/outputs/compliance/c5/c5_azure.py index 976d37e9be9..0899ff6b15e 100644 --- a/prowler/lib/outputs/compliance/c5/c5_azure.py +++ b/prowler/lib/outputs/compliance/c5/c5_azure.py @@ -38,9 +38,6 @@ def transform( Returns: - None """ - # Evaluate each requirement's config constraints once against the - # scan-global applied config; a requirement whose configurable checks - # ran with a config too loose to trust is forced to FAIL. requirement_config_status = build_requirement_config_status( compliance.Requirements ) diff --git a/prowler/lib/outputs/compliance/c5/c5_gcp.py b/prowler/lib/outputs/compliance/c5/c5_gcp.py index ae69df8dfe9..c8b874f622f 100644 --- a/prowler/lib/outputs/compliance/c5/c5_gcp.py +++ b/prowler/lib/outputs/compliance/c5/c5_gcp.py @@ -38,9 +38,6 @@ def transform( Returns: - None """ - # Evaluate each requirement's config constraints once against the - # scan-global applied config; a requirement whose configurable checks - # ran with a config too loose to trust is forced to FAIL. requirement_config_status = build_requirement_config_status( compliance.Requirements ) diff --git a/prowler/lib/outputs/compliance/ccc/ccc.py b/prowler/lib/outputs/compliance/ccc/ccc.py index 547319bd38d..48b2086e781 100644 --- a/prowler/lib/outputs/compliance/ccc/ccc.py +++ b/prowler/lib/outputs/compliance/ccc/ccc.py @@ -3,6 +3,8 @@ 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, @@ -23,14 +25,12 @@ 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 = "" - # The applied config is scan-global (the provider's audit_config). Evaluate - # each requirement's config constraints once against it (memoised by Id). audit_config = get_scan_audit_config() config_status_cache = {} for index, finding in enumerate(findings): @@ -40,8 +40,6 @@ def get_ccc_table( if compliance.Framework == "CCC": provider = compliance.Provider for requirement in compliance.Requirements: - # A requirement whose configurable checks ran with an invalid - # config can't be trusted: treat the finding as FAIL. config_status = resolve_requirement_config_status( requirement, audit_config, config_status_cache ) @@ -53,29 +51,15 @@ def get_ccc_table( if section not in sections: sections[section] = {"FAIL": 0, "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 effective_status == "FAIL": - if index not in fail_count: - fail_count.append(index) - elif effective_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 effective_status == "FAIL": - sections[section]["FAIL"] += 1 - elif effective_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 82f5a58eab9..20d42b15083 100644 --- a/prowler/lib/outputs/compliance/ccc/ccc_aws.py +++ b/prowler/lib/outputs/compliance/ccc/ccc_aws.py @@ -38,9 +38,6 @@ def transform( Returns: - None """ - # Evaluate each requirement's config constraints once against the - # scan-global applied config; a requirement whose configurable checks - # ran with a config too loose to trust is forced to FAIL. requirement_config_status = build_requirement_config_status( compliance.Requirements ) diff --git a/prowler/lib/outputs/compliance/ccc/ccc_azure.py b/prowler/lib/outputs/compliance/ccc/ccc_azure.py index 450c2b335ed..4dcc8c5ca80 100644 --- a/prowler/lib/outputs/compliance/ccc/ccc_azure.py +++ b/prowler/lib/outputs/compliance/ccc/ccc_azure.py @@ -38,9 +38,6 @@ def transform( Returns: - None """ - # Evaluate each requirement's config constraints once against the - # scan-global applied config; a requirement whose configurable checks - # ran with a config too loose to trust is forced to FAIL. requirement_config_status = build_requirement_config_status( compliance.Requirements ) diff --git a/prowler/lib/outputs/compliance/ccc/ccc_gcp.py b/prowler/lib/outputs/compliance/ccc/ccc_gcp.py index 19ba65227d0..ed7c709c248 100644 --- a/prowler/lib/outputs/compliance/ccc/ccc_gcp.py +++ b/prowler/lib/outputs/compliance/ccc/ccc_gcp.py @@ -38,9 +38,6 @@ def transform( Returns: - None """ - # Evaluate each requirement's config constraints once against the - # scan-global applied config; a requirement whose configurable checks - # ran with a config too loose to trust is forced to FAIL. requirement_config_status = build_requirement_config_status( compliance.Requirements ) diff --git a/prowler/lib/outputs/compliance/cis/cis.py b/prowler/lib/outputs/compliance/cis/cis.py index a76d79d7924..de3120f689b 100644 --- a/prowler/lib/outputs/compliance/cis/cis.py +++ b/prowler/lib/outputs/compliance/cis/cis.py @@ -3,6 +3,8 @@ 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, @@ -28,11 +30,9 @@ def get_cis_table( "Level 2": [], "Muted": [], } - pass_count = [] - fail_count = [] - muted_count = [] - # The applied config is scan-global (the provider's audit_config). Evaluate - # each requirement's config constraints once against it (memoised by Id). + pass_count = set() + fail_count = set() + muted_count = set() audit_config = get_scan_audit_config() config_status_cache = {} for index, finding in enumerate(findings): @@ -43,8 +43,6 @@ def get_cis_table( if compliance.Framework == "CIS" and version_in_name in compliance.Version: provider = compliance.Provider for requirement in compliance.Requirements: - # A requirement whose configurable checks ran with an invalid - # config can't be trusted: treat the finding as FAIL. config_status = resolve_requirement_config_status( requirement, audit_config, config_status_cache ) @@ -63,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 effective_status == "FAIL" and index not in fail_count: - fail_count.append(index) - elif effective_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 effective_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 effective_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 80023215179..77bf02ee981 100644 --- a/prowler/lib/outputs/compliance/cis/cis_alibabacloud.py +++ b/prowler/lib/outputs/compliance/cis/cis_alibabacloud.py @@ -38,9 +38,6 @@ def transform( Returns: - None """ - # Evaluate each requirement's config constraints once against the - # scan-global applied config; a requirement whose configurable checks - # ran with a config too loose to trust is forced to FAIL. requirement_config_status = build_requirement_config_status( compliance.Requirements ) diff --git a/prowler/lib/outputs/compliance/cis/cis_aws.py b/prowler/lib/outputs/compliance/cis/cis_aws.py index f3b62056298..58bf7fbc27f 100644 --- a/prowler/lib/outputs/compliance/cis/cis_aws.py +++ b/prowler/lib/outputs/compliance/cis/cis_aws.py @@ -38,8 +38,6 @@ def transform( Returns: - None """ - # The applied config is scan-global (the provider's audit_config), so - # evaluate each requirement's config constraints once against it. requirement_config_status = build_requirement_config_status( compliance.Requirements ) @@ -48,9 +46,6 @@ def transform( 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: - # A requirement whose configurable checks ran with an invalid - # config can't be trusted: force FAIL regardless of the - # finding's own status. row_status, row_status_extended = apply_config_status( finding.status, finding.status_extended, diff --git a/prowler/lib/outputs/compliance/cis/cis_azure.py b/prowler/lib/outputs/compliance/cis/cis_azure.py index 0ce742f864b..53b1cbdd329 100644 --- a/prowler/lib/outputs/compliance/cis/cis_azure.py +++ b/prowler/lib/outputs/compliance/cis/cis_azure.py @@ -38,9 +38,6 @@ def transform( Returns: - None """ - # Evaluate each requirement's config constraints once against the - # scan-global applied config; a requirement whose configurable checks - # ran with a config too loose to trust is forced to FAIL. requirement_config_status = build_requirement_config_status( compliance.Requirements ) diff --git a/prowler/lib/outputs/compliance/cis/cis_gcp.py b/prowler/lib/outputs/compliance/cis/cis_gcp.py index f6a3453c556..c2a11dae77a 100644 --- a/prowler/lib/outputs/compliance/cis/cis_gcp.py +++ b/prowler/lib/outputs/compliance/cis/cis_gcp.py @@ -38,9 +38,6 @@ def transform( Returns: - None """ - # Evaluate each requirement's config constraints once against the - # scan-global applied config; a requirement whose configurable checks - # ran with a config too loose to trust is forced to FAIL. requirement_config_status = build_requirement_config_status( compliance.Requirements ) diff --git a/prowler/lib/outputs/compliance/cis/cis_github.py b/prowler/lib/outputs/compliance/cis/cis_github.py index fad959a7e4c..039bd1b9938 100644 --- a/prowler/lib/outputs/compliance/cis/cis_github.py +++ b/prowler/lib/outputs/compliance/cis/cis_github.py @@ -38,9 +38,6 @@ def transform( Returns: - None """ - # Evaluate each requirement's config constraints once against the - # scan-global applied config; a requirement whose configurable checks - # ran with a config too loose to trust is forced to FAIL. requirement_config_status = build_requirement_config_status( compliance.Requirements ) diff --git a/prowler/lib/outputs/compliance/cis/cis_googleworkspace.py b/prowler/lib/outputs/compliance/cis/cis_googleworkspace.py index a0b841f40ca..cf2d3755c72 100644 --- a/prowler/lib/outputs/compliance/cis/cis_googleworkspace.py +++ b/prowler/lib/outputs/compliance/cis/cis_googleworkspace.py @@ -38,9 +38,6 @@ def transform( Returns: - None """ - # Evaluate each requirement's config constraints once against the - # scan-global applied config; a requirement whose configurable checks - # ran with a config too loose to trust is forced to FAIL. requirement_config_status = build_requirement_config_status( compliance.Requirements ) diff --git a/prowler/lib/outputs/compliance/cis/cis_kubernetes.py b/prowler/lib/outputs/compliance/cis/cis_kubernetes.py index 0a559225d00..23c482310e1 100644 --- a/prowler/lib/outputs/compliance/cis/cis_kubernetes.py +++ b/prowler/lib/outputs/compliance/cis/cis_kubernetes.py @@ -38,9 +38,6 @@ def transform( Returns: - None """ - # Evaluate each requirement's config constraints once against the - # scan-global applied config; a requirement whose configurable checks - # ran with a config too loose to trust is forced to FAIL. requirement_config_status = build_requirement_config_status( compliance.Requirements ) diff --git a/prowler/lib/outputs/compliance/cis/cis_m365.py b/prowler/lib/outputs/compliance/cis/cis_m365.py index 4be14f04103..8dc06155e7c 100644 --- a/prowler/lib/outputs/compliance/cis/cis_m365.py +++ b/prowler/lib/outputs/compliance/cis/cis_m365.py @@ -38,9 +38,6 @@ def transform( Returns: - None """ - # Evaluate each requirement's config constraints once against the - # scan-global applied config; a requirement whose configurable checks - # ran with a config too loose to trust is forced to FAIL. requirement_config_status = build_requirement_config_status( compliance.Requirements ) diff --git a/prowler/lib/outputs/compliance/cis/cis_oraclecloud.py b/prowler/lib/outputs/compliance/cis/cis_oraclecloud.py index d67d3c99409..d8110d72db5 100644 --- a/prowler/lib/outputs/compliance/cis/cis_oraclecloud.py +++ b/prowler/lib/outputs/compliance/cis/cis_oraclecloud.py @@ -38,9 +38,6 @@ def transform( Returns: - None """ - # Evaluate each requirement's config constraints once against the - # scan-global applied config; a requirement whose configurable checks - # ran with a config too loose to trust is forced to FAIL. requirement_config_status = build_requirement_config_status( compliance.Requirements ) 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 9254d411352..d2f6faa2125 100644 --- a/prowler/lib/outputs/compliance/cisa_scuba/cisa_scuba_googleworkspace.py +++ b/prowler/lib/outputs/compliance/cisa_scuba/cisa_scuba_googleworkspace.py @@ -40,9 +40,6 @@ def transform( Returns: - None """ - # Evaluate each requirement's config constraints once against the - # scan-global applied config; a requirement whose configurable checks - # ran with a config too loose to trust is forced to FAIL. requirement_config_status = build_requirement_config_status( compliance.Requirements ) diff --git a/prowler/lib/outputs/compliance/ens/ens.py b/prowler/lib/outputs/compliance/ens/ens.py index 9e95a0cc7ad..4fcca922c74 100644 --- a/prowler/lib/outputs/compliance/ens/ens.py +++ b/prowler/lib/outputs/compliance/ens/ens.py @@ -30,11 +30,9 @@ def get_ens_table( "Opcional": [], "Muted": [], } - pass_count = [] - fail_count = [] - muted_count = [] - # The applied config is scan-global (the provider's audit_config). Evaluate - # each requirement's config constraints once against it (memoised by Id). + pass_count = set() + fail_count = set() + muted_count = set() audit_config = get_scan_audit_config() config_status_cache = {} for index, finding in enumerate(findings): @@ -44,8 +42,6 @@ def get_ens_table( if compliance.Framework == "ENS": provider = compliance.Provider for requirement in compliance.Requirements: - # A requirement whose configurable checks ran with an invalid - # config can't be trusted: treat the finding as FAIL. config_status = resolve_requirement_config_status( requirement, audit_config, config_status_cache ) @@ -67,8 +63,7 @@ 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]: @@ -77,15 +72,15 @@ def get_ens_table( else: 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 effective_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 ed416b3533b..543799f7b9b 100644 --- a/prowler/lib/outputs/compliance/ens/ens_aws.py +++ b/prowler/lib/outputs/compliance/ens/ens_aws.py @@ -38,9 +38,6 @@ def transform( Returns: - None """ - # Evaluate each requirement's config constraints once against the - # scan-global applied config; a requirement whose configurable checks - # ran with a config too loose to trust is forced to FAIL. requirement_config_status = build_requirement_config_status( compliance.Requirements ) diff --git a/prowler/lib/outputs/compliance/ens/ens_azure.py b/prowler/lib/outputs/compliance/ens/ens_azure.py index a78debb539f..23055da2a2d 100644 --- a/prowler/lib/outputs/compliance/ens/ens_azure.py +++ b/prowler/lib/outputs/compliance/ens/ens_azure.py @@ -38,9 +38,6 @@ def transform( Returns: - None """ - # Evaluate each requirement's config constraints once against the - # scan-global applied config; a requirement whose configurable checks - # ran with a config too loose to trust is forced to FAIL. requirement_config_status = build_requirement_config_status( compliance.Requirements ) diff --git a/prowler/lib/outputs/compliance/ens/ens_gcp.py b/prowler/lib/outputs/compliance/ens/ens_gcp.py index 893b1411420..f616e48f831 100644 --- a/prowler/lib/outputs/compliance/ens/ens_gcp.py +++ b/prowler/lib/outputs/compliance/ens/ens_gcp.py @@ -38,9 +38,6 @@ def transform( Returns: - None """ - # Evaluate each requirement's config constraints once against the - # scan-global applied config; a requirement whose configurable checks - # ran with a config too loose to trust is forced to FAIL. requirement_config_status = build_requirement_config_status( compliance.Requirements ) diff --git a/prowler/lib/outputs/compliance/generic/generic.py b/prowler/lib/outputs/compliance/generic/generic.py index 25d640dddbe..b3f1ad7ec94 100644 --- a/prowler/lib/outputs/compliance/generic/generic.py +++ b/prowler/lib/outputs/compliance/generic/generic.py @@ -39,9 +39,6 @@ def transform( - None """ - # Evaluate each requirement's config constraints once against the - # scan-global applied config; a requirement whose configurable checks ran - # with a config too loose to trust is forced to FAIL. requirement_config_status = build_requirement_config_status( compliance.Requirements ) diff --git a/prowler/lib/outputs/compliance/generic/generic_table.py b/prowler/lib/outputs/compliance/generic/generic_table.py index 30cf04360c5..97e54088388 100644 --- a/prowler/lib/outputs/compliance/generic/generic_table.py +++ b/prowler/lib/outputs/compliance/generic/generic_table.py @@ -20,8 +20,6 @@ def get_generic_compliance_table( pass_count = [] fail_count = [] muted_count = [] - # The applied config is scan-global (the provider's audit_config). Evaluate - # each requirement's config constraints once against it (memoised by Id). audit_config = get_scan_audit_config() config_status_cache = {} for index, finding in enumerate(findings): @@ -34,9 +32,6 @@ def get_generic_compliance_table( and compliance.Version in compliance_framework.upper() and compliance.Provider.upper() in compliance_framework.upper() ): - # A requirement whose configurable checks ran with an invalid - # config can't be trusted: treat the finding as FAIL if any of - # the requirements it maps to has an invalid config. effective_status = finding.status for requirement in compliance.Requirements: if finding.check_id in requirement.Checks: diff --git a/prowler/lib/outputs/compliance/iso27001/iso27001_aws.py b/prowler/lib/outputs/compliance/iso27001/iso27001_aws.py index 462fa749085..3376b8c5ade 100644 --- a/prowler/lib/outputs/compliance/iso27001/iso27001_aws.py +++ b/prowler/lib/outputs/compliance/iso27001/iso27001_aws.py @@ -38,9 +38,6 @@ def transform( Returns: - None """ - # Evaluate each requirement's config constraints once against the - # scan-global applied config; a requirement whose configurable checks - # ran with a config too loose to trust is forced to FAIL. requirement_config_status = build_requirement_config_status( compliance.Requirements ) diff --git a/prowler/lib/outputs/compliance/iso27001/iso27001_azure.py b/prowler/lib/outputs/compliance/iso27001/iso27001_azure.py index 07c53673c96..f1f964c7265 100644 --- a/prowler/lib/outputs/compliance/iso27001/iso27001_azure.py +++ b/prowler/lib/outputs/compliance/iso27001/iso27001_azure.py @@ -38,9 +38,6 @@ def transform( Returns: - None """ - # Evaluate each requirement's config constraints once against the - # scan-global applied config; a requirement whose configurable checks - # ran with a config too loose to trust is forced to FAIL. requirement_config_status = build_requirement_config_status( compliance.Requirements ) diff --git a/prowler/lib/outputs/compliance/iso27001/iso27001_gcp.py b/prowler/lib/outputs/compliance/iso27001/iso27001_gcp.py index 433b0e81344..9a5de17bfab 100644 --- a/prowler/lib/outputs/compliance/iso27001/iso27001_gcp.py +++ b/prowler/lib/outputs/compliance/iso27001/iso27001_gcp.py @@ -38,9 +38,6 @@ def transform( Returns: - None """ - # Evaluate each requirement's config constraints once against the - # scan-global applied config; a requirement whose configurable checks - # ran with a config too loose to trust is forced to FAIL. requirement_config_status = build_requirement_config_status( compliance.Requirements ) diff --git a/prowler/lib/outputs/compliance/iso27001/iso27001_kubernetes.py b/prowler/lib/outputs/compliance/iso27001/iso27001_kubernetes.py index e0f54a8f02e..5ca81e82d56 100644 --- a/prowler/lib/outputs/compliance/iso27001/iso27001_kubernetes.py +++ b/prowler/lib/outputs/compliance/iso27001/iso27001_kubernetes.py @@ -38,9 +38,6 @@ def transform( Returns: - None """ - # Evaluate each requirement's config constraints once against the - # scan-global applied config; a requirement whose configurable checks - # ran with a config too loose to trust is forced to FAIL. requirement_config_status = build_requirement_config_status( compliance.Requirements ) diff --git a/prowler/lib/outputs/compliance/iso27001/iso27001_m365.py b/prowler/lib/outputs/compliance/iso27001/iso27001_m365.py index 5ea78838033..84c50720025 100644 --- a/prowler/lib/outputs/compliance/iso27001/iso27001_m365.py +++ b/prowler/lib/outputs/compliance/iso27001/iso27001_m365.py @@ -38,9 +38,6 @@ def transform( Returns: - None """ - # Evaluate each requirement's config constraints once against the - # scan-global applied config; a requirement whose configurable checks - # ran with a config too loose to trust is forced to FAIL. requirement_config_status = build_requirement_config_status( compliance.Requirements ) diff --git a/prowler/lib/outputs/compliance/iso27001/iso27001_nhn.py b/prowler/lib/outputs/compliance/iso27001/iso27001_nhn.py index 6599ca1340c..ad8ea6dcabe 100644 --- a/prowler/lib/outputs/compliance/iso27001/iso27001_nhn.py +++ b/prowler/lib/outputs/compliance/iso27001/iso27001_nhn.py @@ -38,9 +38,6 @@ def transform( Returns: - None """ - # Evaluate each requirement's config constraints once against the - # scan-global applied config; a requirement whose configurable checks - # ran with a config too loose to trust is forced to FAIL. requirement_config_status = build_requirement_config_status( compliance.Requirements ) diff --git a/prowler/lib/outputs/compliance/kisa_ismsp/kisa_ismsp.py b/prowler/lib/outputs/compliance/kisa_ismsp/kisa_ismsp.py index c6e8a6b0edb..dda83342b66 100644 --- a/prowler/lib/outputs/compliance/kisa_ismsp/kisa_ismsp.py +++ b/prowler/lib/outputs/compliance/kisa_ismsp/kisa_ismsp.py @@ -3,6 +3,7 @@ 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, @@ -27,11 +28,9 @@ def get_kisa_ismsp_table( "Status": [], "Muted": [], } - pass_count = [] - fail_count = [] - muted_count = [] - # The applied config is scan-global (the provider's audit_config). Evaluate - # each requirement's config constraints once against it (memoised by Id). + pass_count = set() + fail_count = set() + muted_count = set() audit_config = get_scan_audit_config() config_status_cache = {} for index, finding in enumerate(findings): @@ -44,8 +43,6 @@ def get_kisa_ismsp_table( ): provider = compliance.Provider for requirement in compliance.Requirements: - # A requirement whose configurable checks ran with an invalid - # config can't be trusted: treat the finding as FAIL. config_status = resolve_requirement_config_status( requirement, audit_config, config_status_cache ) @@ -63,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 effective_status == "FAIL": - if index not in fail_count: - fail_count.append(index) - elif effective_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 effective_status == "FAIL": + elif status == "FAIL": sections[section]["Status"]["FAIL"] += 1 - elif effective_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 dcde83c059d..3d05632d262 100644 --- a/prowler/lib/outputs/compliance/kisa_ismsp/kisa_ismsp_aws.py +++ b/prowler/lib/outputs/compliance/kisa_ismsp/kisa_ismsp_aws.py @@ -38,9 +38,6 @@ def transform( Returns: - None """ - # Evaluate each requirement's config constraints once against the - # scan-global applied config; a requirement whose configurable checks - # ran with a config too loose to trust is forced to FAIL. requirement_config_status = build_requirement_config_status( compliance.Requirements ) diff --git a/prowler/lib/outputs/compliance/mitre_attack/mitre_attack.py b/prowler/lib/outputs/compliance/mitre_attack/mitre_attack.py index 5fc56b251c1..a352acfa6e6 100644 --- a/prowler/lib/outputs/compliance/mitre_attack/mitre_attack.py +++ b/prowler/lib/outputs/compliance/mitre_attack/mitre_attack.py @@ -3,6 +3,8 @@ 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, @@ -26,11 +28,9 @@ def get_mitre_attack_table( "Status": [], "Muted": [], } - pass_count = [] - fail_count = [] - muted_count = [] - # The applied config is scan-global (the provider's audit_config). Evaluate - # each requirement's config constraints once against it (memoised by Id). + pass_count = set() + fail_count = set() + muted_count = set() audit_config = get_scan_audit_config() config_status_cache = {} for index, finding in enumerate(findings): @@ -43,40 +43,23 @@ def get_mitre_attack_table( ): provider = compliance.Provider for requirement in compliance.Requirements: - # A requirement whose configurable checks ran with an invalid - # config can't be trusted: treat the finding as FAIL. 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 effective_status == "FAIL": - if index not in fail_count: - fail_count.append(index) - elif effective_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 effective_status == "FAIL": - tactics[tactic]["FAIL"] += 1 - elif effective_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 8530fae9c80..92f4ac0e5bf 100644 --- a/prowler/lib/outputs/compliance/mitre_attack/mitre_attack_aws.py +++ b/prowler/lib/outputs/compliance/mitre_attack/mitre_attack_aws.py @@ -39,9 +39,6 @@ def transform( Returns: - None """ - # Evaluate each requirement's config constraints once against the - # scan-global applied config; a requirement whose configurable checks - # ran with a config too loose to trust is forced to FAIL. requirement_config_status = build_requirement_config_status( compliance.Requirements ) 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 6b06806bf16..a43e27e775e 100644 --- a/prowler/lib/outputs/compliance/mitre_attack/mitre_attack_azure.py +++ b/prowler/lib/outputs/compliance/mitre_attack/mitre_attack_azure.py @@ -39,9 +39,6 @@ def transform( Returns: - None """ - # Evaluate each requirement's config constraints once against the - # scan-global applied config; a requirement whose configurable checks - # ran with a config too loose to trust is forced to FAIL. requirement_config_status = build_requirement_config_status( compliance.Requirements ) 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 b317e7c7f44..b8efdc52504 100644 --- a/prowler/lib/outputs/compliance/mitre_attack/mitre_attack_gcp.py +++ b/prowler/lib/outputs/compliance/mitre_attack/mitre_attack_gcp.py @@ -39,9 +39,6 @@ def transform( Returns: - None """ - # Evaluate each requirement's config constraints once against the - # scan-global applied config; a requirement whose configurable checks - # ran with a config too loose to trust is forced to FAIL. requirement_config_status = build_requirement_config_status( compliance.Requirements ) diff --git a/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore.py b/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore.py index 1c6b879a71a..cb23d67ffb6 100644 --- a/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore.py +++ b/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore.py @@ -3,6 +3,8 @@ 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, @@ -25,20 +27,18 @@ 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 = {} - # The applied config is scan-global (the provider's audit_config). Evaluate - # each requirement's config constraints once against it (memoised by Id). audit_config = get_scan_audit_config() config_status_cache = {} for index, finding in enumerate(findings): @@ -48,9 +48,6 @@ def get_prowler_threatscore_table( if compliance.Framework == "ProwlerThreatScore": provider = compliance.Provider for requirement in compliance.Requirements: - # A requirement whose configurable checks ran with an invalid - # config can't be trusted: treat the finding as FAIL (it stops - # contributing to the pillar/generic score and counts as FAIL). config_status = resolve_requirement_config_status( requirement, audit_config, config_status_cache ) @@ -69,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 effective_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 effective_status == "FAIL": - if index not in fail_count: - fail_count.append(index) - elif effective_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 effective_status == "FAIL": - pillars[pillar]["FAIL"] += 1 - elif effective_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 effective_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 e11fa9c278d..7d682a3e62f 100644 --- a/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_alibaba.py +++ b/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_alibaba.py @@ -40,9 +40,6 @@ def transform( Returns: - None """ - # Evaluate each requirement's config constraints once against the - # scan-global applied config; a requirement whose configurable checks - # ran with a config too loose to trust is forced to FAIL. requirement_config_status = build_requirement_config_status( compliance.Requirements ) 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 42215f02671..f1280808e39 100644 --- a/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_aws.py +++ b/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_aws.py @@ -40,9 +40,6 @@ def transform( Returns: - None """ - # Evaluate each requirement's config constraints once against the - # scan-global applied config; a requirement whose configurable checks - # ran with a config too loose to trust is forced to FAIL. requirement_config_status = build_requirement_config_status( compliance.Requirements ) 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 a50008a7b48..51185113699 100644 --- a/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_azure.py +++ b/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_azure.py @@ -40,9 +40,6 @@ def transform( Returns: - None """ - # Evaluate each requirement's config constraints once against the - # scan-global applied config; a requirement whose configurable checks - # ran with a config too loose to trust is forced to FAIL. requirement_config_status = build_requirement_config_status( compliance.Requirements ) 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 72d49b1da6a..39f9c238505 100644 --- a/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_gcp.py +++ b/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_gcp.py @@ -40,9 +40,6 @@ def transform( Returns: - None """ - # Evaluate each requirement's config constraints once against the - # scan-global applied config; a requirement whose configurable checks - # ran with a config too loose to trust is forced to FAIL. requirement_config_status = build_requirement_config_status( compliance.Requirements ) 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 f2e80c5028b..88bf41582a2 100644 --- a/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_kubernetes.py +++ b/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_kubernetes.py @@ -40,9 +40,6 @@ def transform( Returns: - None """ - # Evaluate each requirement's config constraints once against the - # scan-global applied config; a requirement whose configurable checks - # ran with a config too loose to trust is forced to FAIL. requirement_config_status = build_requirement_config_status( compliance.Requirements ) 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 a6bafa99cef..b7b533c72ae 100644 --- a/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_m365.py +++ b/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_m365.py @@ -40,9 +40,6 @@ def transform( Returns: - None """ - # Evaluate each requirement's config constraints once against the - # scan-global applied config; a requirement whose configurable checks - # ran with a config too loose to trust is forced to FAIL. requirement_config_status = build_requirement_config_status( compliance.Requirements ) diff --git a/prowler/lib/outputs/compliance/universal/ocsf_compliance.py b/prowler/lib/outputs/compliance/universal/ocsf_compliance.py index fed5f13457a..8e5b623e491 100644 --- a/prowler/lib/outputs/compliance/universal/ocsf_compliance.py +++ b/prowler/lib/outputs/compliance/universal/ocsf_compliance.py @@ -185,9 +185,6 @@ def _transform( for check_id in all_checks: check_req_map.setdefault(check_id, []).append(req) - # The applied config is scan-global (the provider's audit_config). A - # requirement whose config constraints aren't satisfied is FAILed even - # when its findings PASS, mirroring the CSV/CLI behaviour. requirement_config_status = build_requirement_config_status( framework.requirements ) @@ -258,9 +255,6 @@ def _build_compliance_finding( config_status: tuple = (True, ""), ) -> ComplianceFinding: try: - # If the requirement's config isn't valid, the requirement FAILs even - # though the check (and finding) passed. The Check keeps the real - # finding status; the Compliance status reflects the requirement. effective_status, message = apply_config_status( finding.status, finding.status_extended, config_status ) diff --git a/prowler/lib/outputs/compliance/universal/universal_table.py b/prowler/lib/outputs/compliance/universal/universal_table.py index f9165626fe2..38c06eb489f 100644 --- a/prowler/lib/outputs/compliance/universal/universal_table.py +++ b/prowler/lib/outputs/compliance/universal/universal_table.py @@ -3,6 +3,8 @@ 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, @@ -169,11 +171,9 @@ def _render_grouped( check_map = _build_requirement_check_map(framework, provider) groups = {} group_seen = {} - pass_count = [] - fail_count = [] - muted_count = [] - # A requirement whose configurable checks ran with an invalid config can't be - # trusted: treat the finding as FAIL (config is scan-global, memoised by id). + pass_count = set() + fail_count = set() + muted_count = set() audit_config = get_scan_audit_config() config_status_cache = {} @@ -192,29 +192,15 @@ def _render_grouped( 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 effective_status == "FAIL": - if index not in fail_count: - fail_count.append(index) - elif effective_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 effective_status == "FAIL": - groups[group_key]["FAIL"] += 1 - elif effective_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 @@ -287,11 +273,9 @@ def _render_split( groups = {} group_muted_seen = {} group_split_seen = {} - pass_count = [] - fail_count = [] - muted_count = [] - # A requirement whose configurable checks ran with an invalid config can't be - # trusted: treat the finding as FAIL (config is scan-global, memoised by id). + pass_count = set() + fail_count = set() + muted_count = set() audit_config = get_scan_audit_config() config_status_cache = {} @@ -314,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 effective_status == "FAIL" and index not in fail_count: - fail_count.append(index) - elif effective_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 effective_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 @@ -412,18 +396,16 @@ 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 = [] - # A requirement whose configurable checks ran with an invalid config can't be - # trusted: treat the finding as FAIL (config is scan-global, memoised by id). + counted_generic = {} audit_config = get_scan_audit_config() config_status_cache = {} @@ -446,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 effective_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 effective_status == "FAIL": - if index not in fail_count: - fail_count.append(index) - elif effective_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 effective_status == "FAIL": - groups[group_key]["FAIL"] += 1 - elif effective_status == "PASS": - groups[group_key]["PASS"] += 1 - - if index not in counted_generic and not finding.muted: - if effective_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 index 64223e1bdbd..164e9e1fa0c 100644 --- a/tests/lib/check/compliance_config_constraint_model_test.py +++ b/tests/lib/check/compliance_config_constraint_model_test.py @@ -23,6 +23,12 @@ _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", @@ -91,14 +97,14 @@ def test_provider_scopes_constraint(self): class Test_ConfigRequirements_On_Compliance: def test_requirements_without_constraints_default_to_none(self): - compliance = Compliance(**json.load(open(_CIS_6_0))) + 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(**json.load(open(_CIS_6_0))) + 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] @@ -109,7 +115,7 @@ def test_requirement_with_constraints_parses(self): class Test_Adapt_Legacy_To_Universal: def test_config_requirements_carried_to_universal(self): - legacy = Compliance(**json.load(open(_CIS_6_0))) + legacy = Compliance(**_load_cis()) universal = adapt_legacy_to_universal(legacy) legacy_with = {r.Id for r in legacy.Requirements if r.ConfigRequirements} @@ -127,7 +133,7 @@ def test_config_requirements_carried_to_universal(self): assert entry["Provider"] is None def test_requirements_without_constraints_are_none_in_universal(self): - legacy = Compliance(**json.load(open(_CIS_6_0))) + legacy = Compliance(**_load_cis()) universal = adapt_legacy_to_universal(legacy) without = [r for r in universal.requirements if not r.config_requirements] assert without diff --git a/tests/lib/check/compliance_config_eval_test.py b/tests/lib/check/compliance_config_eval_test.py index 16aa74cdfbf..e10a6a22152 100644 --- a/tests/lib/check/compliance_config_eval_test.py +++ b/tests/lib/check/compliance_config_eval_test.py @@ -3,6 +3,8 @@ 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, @@ -253,6 +255,69 @@ def test_requirement_without_constraints_is_ok(self): 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} + + class Test_apply_config_status: def test_none_config_status_keeps_finding(self): assert apply_config_status("PASS", "ext", None) == ("PASS", "ext") @@ -285,10 +350,11 @@ def test_returns_empty_without_global_provider(self): class Test_get_scan_provider_type: def test_returns_empty_when_no_global_provider(self): - # Unresolvable provider → scoping disabled (empty string). + # 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", - side_effect=Exception("no provider"), + return_value=None, ): assert get_scan_provider_type() == "" 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 index a684a79e6a0..3ac8dc6d8bf 100644 --- a/tests/lib/outputs/compliance/universal/universal_table_config_requirements_test.py +++ b/tests/lib/outputs/compliance/universal/universal_table_config_requirements_test.py @@ -57,7 +57,7 @@ def _framework(): ) -def _render(audit_config, capsys): +def _render(audit_config, capsys, output_directory): with patch(_MODULE) as mock_gp: mock_gp.return_value.audit_config = audit_config get_universal_table( @@ -65,7 +65,7 @@ def _render(audit_config, capsys): bulk_checks_metadata={}, compliance_framework_name="testfw_1.0_aws", output_filename="out", - output_directory="/tmp", + output_directory=str(output_directory), compliance_overview=False, framework=_framework(), provider="aws", @@ -74,17 +74,82 @@ def _render(audit_config, capsys): class Test_Universal_Table_Config_Requirements: - def test_violating_config_counts_pass_finding_as_fail(self, capsys): - out = _render({"mute_non_default_regions": True}, capsys) + 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): - out = _render({"mute_non_default_regions": False}, capsys) + 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): - out = _render({}, capsys) + 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 From daa7c5d59ee5e9f3438fd2618c8a7f2e55c57a4e Mon Sep 17 00:00:00 2001 From: pedrooot Date: Tue, 23 Jun 2026 14:14:28 +0200 Subject: [PATCH 06/18] chore(revision): solve comments --- .../security-compliance-framework.mdx | 4 ++++ prowler/lib/check/compliance_config_eval.py | 11 +++++++++-- prowler/lib/check/compliance_models.py | 5 +++-- tests/lib/check/compliance_config_eval_test.py | 17 +++++++++++++++++ 4 files changed, 33 insertions(+), 4 deletions(-) diff --git a/docs/developer-guide/security-compliance-framework.mdx b/docs/developer-guide/security-compliance-framework.mdx index cb397fe6ae5..999b09e6c0d 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 @@ -483,6 +485,8 @@ Loader-error behaviour differs between the two entry points: ## 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. diff --git a/prowler/lib/check/compliance_config_eval.py b/prowler/lib/check/compliance_config_eval.py index a25c0440293..b335ba38509 100644 --- a/prowler/lib/check/compliance_config_eval.py +++ b/prowler/lib/check/compliance_config_eval.py @@ -101,8 +101,15 @@ def evaluate_config_constraints( expected = getattr(constraint, "Value", None) provider = getattr(constraint, "Provider", None) - if provider and provider_type and provider != provider_type: - # Constraint scoped to another provider → not applicable to this scan. + # 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: diff --git a/prowler/lib/check/compliance_models.py b/prowler/lib/check/compliance_models.py index d4ba50ed184..db903262353 100644 --- a/prowler/lib/check/compliance_models.py +++ b/prowler/lib/check/compliance_models.py @@ -336,8 +336,9 @@ class Compliance_Requirement_ConfigConstraint(BaseModel): # ``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``). ``None`` means it - # applies whenever the requirement runs (single-provider frameworks). + # Provider this constraint applies to (e.g. ``aws``), matched + # case-insensitively. ``None`` applies whenever the requirement runs + # (single-provider frameworks). Provider: Optional[str] = None diff --git a/tests/lib/check/compliance_config_eval_test.py b/tests/lib/check/compliance_config_eval_test.py index e10a6a22152..bc84b8fb711 100644 --- a/tests/lib/check/compliance_config_eval_test.py +++ b/tests/lib/check/compliance_config_eval_test.py @@ -183,6 +183,23 @@ def test_none_provider_type_disables_scoping(self): ) 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( From 0615e57e03e8268ea2cc74cde6b83f08b8e1ed08 Mon Sep 17 00:00:00 2001 From: pedrooot Date: Tue, 23 Jun 2026 14:16:52 +0200 Subject: [PATCH 07/18] chore: move to 5.32 --- docs/developer-guide/security-compliance-framework.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/developer-guide/security-compliance-framework.mdx b/docs/developer-guide/security-compliance-framework.mdx index 999b09e6c0d..9c831551405 100644 --- a/docs/developer-guide/security-compliance-framework.mdx +++ b/docs/developer-guide/security-compliance-framework.mdx @@ -485,7 +485,7 @@ Loader-error behaviour differs between the two entry points: ## 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. From 55d79714557789f9f9ff0bfbccf6e251441e7540 Mon Sep 17 00:00:00 2001 From: pedrooot Date: Tue, 23 Jun 2026 14:23:44 +0200 Subject: [PATCH 08/18] chore(revision): solve comments --- tests/lib/check/compliance_config_eval_test.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/lib/check/compliance_config_eval_test.py b/tests/lib/check/compliance_config_eval_test.py index bc84b8fb711..9e4775b65eb 100644 --- a/tests/lib/check/compliance_config_eval_test.py +++ b/tests/lib/check/compliance_config_eval_test.py @@ -1,6 +1,8 @@ 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, @@ -334,6 +336,14 @@ def test_works_with_fail_pass_only_counts(self): 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): From 179b09df735a51a4481a1f84fec555065341e237 Mon Sep 17 00:00:00 2001 From: pedrooot Date: Tue, 23 Jun 2026 16:20:54 +0200 Subject: [PATCH 09/18] chore(sdk): add missing ConfigRequirements --- .../alibabacloud/cis_2.0_alibabacloud.json | 8 + .../prowler_threatscore_alibabacloud.json | 16 + .../secnumcloud_3.2_alibabacloud.json | 32 ++ .../aws/asd_essential_eight_aws.json | 28 ++ .../aws/aws_ai_security_framework_aws.json | 64 ++++ ...ndational_security_best_practices_aws.json | 24 +- ...aws_foundational_technical_review_aws.json | 14 + ...itected_framework_security_pillar_aws.json | 34 ++ prowler/compliance/aws/c5_aws.json | 120 +++++++ prowler/compliance/aws/ccc_aws.json | 44 ++- prowler/compliance/aws/cisa_aws.json | 20 ++ prowler/compliance/aws/ens_rd2022_aws.json | 42 +++ .../aws/fedramp_20x_ksi_low_aws.json | 46 +++ .../aws/fedramp_low_revision_4_aws.json | 38 ++ .../aws/fedramp_moderate_revision_4_aws.json | 8 + prowler/compliance/aws/ffiec_aws.json | 30 ++ prowler/compliance/aws/gdpr_aws.json | 12 + .../aws/gxp_21_cfr_part_11_aws.json | 30 ++ prowler/compliance/aws/hipaa_aws.json | 28 ++ prowler/compliance/aws/iso27001_2022_aws.json | 76 ++++ .../compliance/aws/kisa_isms_p_2023_aws.json | 106 ++++++ .../aws/kisa_isms_p_2023_korean_aws.json | 106 ++++++ prowler/compliance/aws/mitre_attack_aws.json | 30 ++ prowler/compliance/aws/nis2_aws.json | 124 +++++++ .../aws/nist_800_171_revision_2_aws.json | 46 +++ .../aws/nist_800_53_revision_4_aws.json | 16 + .../aws/nist_800_53_revision_5_aws.json | 48 +++ prowler/compliance/aws/nist_csf_1.1_aws.json | 40 +++ prowler/compliance/aws/nist_csf_2.0_aws.json | 74 ++++ prowler/compliance/aws/pci_3.2.1_aws.json | 50 +++ prowler/compliance/aws/pci_4.0_aws.json | 40 +++ .../aws/prowler_threatscore_aws.json | 8 + .../aws/rbi_cyber_security_framework_aws.json | 6 + .../compliance/aws/secnumcloud_3.2_aws.json | 74 ++++ prowler/compliance/aws/soc2_aws.json | 48 +++ prowler/compliance/azure/c5_azure.json | 8 + prowler/compliance/csa_ccm_4.0.json | 125 ++++++- prowler/compliance/dora_2022_2554.json | 121 ++++++- prowler/compliance/gcp/c5_gcp.json | 16 + .../gcp/fedramp_20x_ksi_low_gcp.json | 14 + prowler/compliance/gcp/hipaa_gcp.json | 8 + prowler/compliance/gcp/mitre_attack_gcp.json | 14 + prowler/compliance/gcp/nis2_gcp.json | 14 + .../gcp/rbi_cyber_security_framework_gcp.json | 8 + .../compliance/gcp/secnumcloud_3.2_gcp.json | 22 ++ prowler/compliance/gcp/soc2_gcp.json | 8 + prowler/compliance/github/cis_1.0_github.json | 8 + .../kubernetes/cis_1.10_kubernetes.json | 16 + .../kubernetes/cis_1.11_kubernetes.json | 16 + .../kubernetes/cis_1.12_kubernetes.json | 16 + .../kubernetes/cis_1.8_kubernetes.json | 16 + .../kubernetes/iso27001_2022_kubernetes.json | 20 ++ .../prowler_threatscore_kubernetes.json | 16 + prowler/compliance/m365/cis_4.0_m365.json | 8 + prowler/compliance/m365/cis_6.0_m365.json | 8 + .../m365/prowler_threatscore_m365.json | 8 + .../okta/okta_idaas_stig_v1r2_okta.json | 16 + ...config_requirements_coverage_baseline.json | 331 ++++++++++++++++++ ...iance_config_requirements_coverage_test.py | 298 ++++++++++++++++ ...01_aws_numeric_config_requirements_test.py | 73 ++++ 60 files changed, 2713 insertions(+), 25 deletions(-) create mode 100644 tests/lib/check/compliance_config_requirements_coverage_baseline.json create mode 100644 tests/lib/check/compliance_config_requirements_coverage_test.py create mode 100644 tests/lib/outputs/compliance/iso27001/iso27001_aws_numeric_config_requirements_test.py diff --git a/prowler/compliance/alibabacloud/cis_2.0_alibabacloud.json b/prowler/compliance/alibabacloud/cis_2.0_alibabacloud.json index 1cac54b318c..9a05c1997f4 100644 --- a/prowler/compliance/alibabacloud/cis_2.0_alibabacloud.json +++ b/prowler/compliance/alibabacloud/cis_2.0_alibabacloud.json @@ -1575,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 9f16b6cc4dd..382b9fac8d9 100644 --- a/prowler/compliance/alibabacloud/prowler_threatscore_alibabacloud.json +++ b/prowler/compliance/alibabacloud/prowler_threatscore_alibabacloud.json @@ -407,6 +407,14 @@ "LevelOfRisk": 3, "Weight": 10 } + ], + "ConfigRequirements": [ + { + "Check": "cs_kubernetes_cluster_check_weekly", + "ConfigKey": "max_cluster_check_days", + "Operator": "lte", + "Value": 7 + } ] }, { @@ -883,6 +891,14 @@ "LevelOfRisk": 2, "Weight": 8 } + ], + "ConfigRequirements": [ + { + "Check": "sls_logstore_retention_period", + "ConfigKey": "min_log_retention_days", + "Operator": "gte", + "Value": 365 + } ] }, { diff --git a/prowler/compliance/alibabacloud/secnumcloud_3.2_alibabacloud.json b/prowler/compliance/alibabacloud/secnumcloud_3.2_alibabacloud.json index e71dc8b8698..f5a40f7c7dc 100644 --- a/prowler/compliance/alibabacloud/secnumcloud_3.2_alibabacloud.json +++ b/prowler/compliance/alibabacloud/secnumcloud_3.2_alibabacloud.json @@ -290,6 +290,14 @@ ], "Checks": [ "ram_user_console_access_unused" + ], + "ConfigRequirements": [ + { + "Check": "ram_user_console_access_unused", + "ConfigKey": "max_console_access_days", + "Operator": "lte", + "Value": 45 + } ] }, { @@ -660,6 +668,14 @@ "Checks": [ "actiontrail_multi_region_enabled", "sls_logstore_retention_period" + ], + "ConfigRequirements": [ + { + "Check": "sls_logstore_retention_period", + "ConfigKey": "min_log_retention_days", + "Operator": "gte", + "Value": 365 + } ] }, { @@ -728,6 +744,14 @@ "rds_instance_sql_audit_enabled", "rds_instance_sql_audit_retention", "sls_logstore_retention_period" + ], + "ConfigRequirements": [ + { + "Check": "rds_instance_sql_audit_retention", + "ConfigKey": "min_rds_audit_retention_days", + "Operator": "gte", + "Value": 180 + } ] }, { @@ -1016,6 +1040,14 @@ "Checks": [ "securitycenter_vulnerability_scan_enabled", "cs_kubernetes_cluster_check_weekly" + ], + "ConfigRequirements": [ + { + "Check": "cs_kubernetes_cluster_check_weekly", + "ConfigKey": "max_cluster_check_days", + "Operator": "lte", + "Value": 7 + } ] }, { diff --git a/prowler/compliance/aws/asd_essential_eight_aws.json b/prowler/compliance/aws/asd_essential_eight_aws.json index dd39c442681..0b430189836 100644 --- a/prowler/compliance/aws/asd_essential_eight_aws.json +++ b/prowler/compliance/aws/asd_essential_eight_aws.json @@ -479,6 +479,14 @@ "AdditionalInformation": "ASD Essential Eight ML1 - Patch operating systems - clause 8.", "References": "https://www.cyber.gov.au/resources-business-and-government/essential-cyber-security/essential-eight/essential-eight-maturity-model" } + ], + "ConfigRequirements": [ + { + "Check": "ec2_instance_older_than_specific_days", + "ConfigKey": "max_ec2_instance_age_in_days", + "Operator": "lte", + "Value": 180 + } ] }, { @@ -1155,6 +1163,26 @@ "AdditionalInformation": "ASD Essential Eight ML1 - Regular backups - clause 1.", "References": "https://www.cyber.gov.au/resources-business-and-government/essential-cyber-security/essential-eight/essential-eight-maturity-model" } + ], + "ConfigRequirements": [ + { + "Check": "documentdb_cluster_backup_enabled", + "ConfigKey": "minimum_backup_retention_period", + "Operator": "gte", + "Value": 7 + }, + { + "Check": "elasticache_redis_cluster_backup_enabled", + "ConfigKey": "minimum_snapshot_retention_period", + "Operator": "gte", + "Value": 7 + }, + { + "Check": "neptune_cluster_backup_enabled", + "ConfigKey": "minimum_backup_retention_period", + "Operator": "gte", + "Value": 7 + } ] }, { diff --git a/prowler/compliance/aws/aws_ai_security_framework_aws.json b/prowler/compliance/aws/aws_ai_security_framework_aws.json index 9b87f7464d4..01399c52c8a 100644 --- a/prowler/compliance/aws/aws_ai_security_framework_aws.json +++ b/prowler/compliance/aws/aws_ai_security_framework_aws.json @@ -263,6 +263,20 @@ "iam_user_two_active_access_key", "iam_user_console_access_unused", "bedrock_api_key_no_long_term_credentials" + ], + "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 + } ] }, { @@ -301,6 +315,20 @@ "iam_role_access_not_stale_to_bedrock", "iam_user_access_not_stale_to_bedrock", "iam_role_cross_account_readonlyaccess_policy" + ], + "ConfigRequirements": [ + { + "Check": "iam_role_access_not_stale_to_bedrock", + "ConfigKey": "max_unused_bedrock_access_days", + "Operator": "lte", + "Value": 60 + }, + { + "Check": "iam_user_access_not_stale_to_bedrock", + "ConfigKey": "max_unused_bedrock_access_days", + "Operator": "lte", + "Value": 60 + } ] }, { @@ -432,6 +460,20 @@ "secretsmanager_has_restrictive_resource_policy", "secretsmanager_not_publicly_accessible", "secretsmanager_secret_unused" + ], + "ConfigRequirements": [ + { + "Check": "secretsmanager_secret_rotated_periodically", + "ConfigKey": "max_days_secret_unrotated", + "Operator": "lte", + "Value": 90 + }, + { + "Check": "secretsmanager_secret_unused", + "ConfigKey": "max_days_secret_unused", + "Operator": "lte", + "Value": 90 + } ] }, { @@ -591,6 +633,14 @@ ], "Checks": [ "cloudtrail_threat_detection_llm_jacking" + ], + "ConfigRequirements": [ + { + "Check": "cloudtrail_threat_detection_llm_jacking", + "ConfigKey": "threat_detection_llm_jacking_threshold", + "Operator": "lte", + "Value": 0.4 + } ] }, { @@ -900,6 +950,20 @@ "cloudtrail_threat_detection_llm_jacking", "cloudtrail_threat_detection_privilege_escalation", "cloudtrail_threat_detection_enumeration" + ], + "ConfigRequirements": [ + { + "Check": "cloudtrail_threat_detection_enumeration", + "ConfigKey": "threat_detection_enumeration_threshold", + "Operator": "lte", + "Value": 0.3 + }, + { + "Check": "cloudtrail_threat_detection_privilege_escalation", + "ConfigKey": "threat_detection_privilege_escalation_threshold", + "Operator": "lte", + "Value": 0.2 + } ] }, { 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 f38ee93d28f..419dac59ee8 100644 --- a/prowler/compliance/aws/aws_foundational_security_best_practices_aws.json +++ b/prowler/compliance/aws/aws_foundational_security_best_practices_aws.json @@ -12,14 +12,6 @@ "Checks": [ "acm_certificates_expiration_check" ], - "ConfigRequirements": [ - { - "Check": "acm_certificates_expiration_check", - "ConfigKey": "days_to_expire_threshold", - "Operator": "gte", - "Value": 30 - } - ], "Attributes": [ { "ItemId": "ACM.1", @@ -1154,6 +1146,14 @@ "SectionDescription": "This section contains recommendations for configuring EC2 resources.", "Service": "EC2" } + ], + "ConfigRequirements": [ + { + "Check": "ec2_instance_older_than_specific_days", + "ConfigKey": "max_ec2_instance_age_in_days", + "Operator": "lte", + "Value": 180 + } ] }, { @@ -2242,6 +2242,14 @@ "SectionDescription": "This section contains recommendations for configuring ElastiCache resources.", "Service": "ElastiCache" } + ], + "ConfigRequirements": [ + { + "Check": "elasticache_redis_cluster_backup_enabled", + "ConfigKey": "minimum_snapshot_retention_period", + "Operator": "gte", + "Value": 7 + } ] }, { diff --git a/prowler/compliance/aws/aws_foundational_technical_review_aws.json b/prowler/compliance/aws/aws_foundational_technical_review_aws.json index 9d8e3cfdc81..9e2486799cf 100644 --- a/prowler/compliance/aws/aws_foundational_technical_review_aws.json +++ b/prowler/compliance/aws/aws_foundational_technical_review_aws.json @@ -183,6 +183,12 @@ "ConfigKey": "mute_non_default_regions", "Operator": "eq", "Value": false + }, + { + "Check": "iam_user_accesskey_unused", + "ConfigKey": "max_unused_access_keys_days", + "Operator": "lte", + "Value": 45 } ] }, @@ -390,6 +396,14 @@ "ec2_securitygroup_default_restrict_traffic", "ec2_securitygroup_not_used", "ec2_securitygroup_with_many_ingress_egress_rules" + ], + "ConfigRequirements": [ + { + "Check": "ec2_securitygroup_with_many_ingress_egress_rules", + "ConfigKey": "max_security_group_rules", + "Operator": "lte", + "Value": 50 + } ] }, { 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 a025bb3a3c3..393552c592f 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 @@ -444,6 +444,32 @@ "ecr_repositories_lifecycle_policy_enabled", "elbv2_listeners_underneath", "iam_password_policy_expires_passwords_within_90_days_or_less" + ], + "ConfigRequirements": [ + { + "Check": "appstream_fleet_maximum_session_duration", + "ConfigKey": "max_session_duration_seconds", + "Operator": "lte", + "Value": 36000 + }, + { + "Check": "appstream_fleet_session_disconnect_timeout", + "ConfigKey": "max_disconnect_timeout_in_seconds", + "Operator": "lte", + "Value": 300 + }, + { + "Check": "appstream_fleet_session_idle_disconnect_timeout", + "ConfigKey": "max_idle_disconnect_timeout_in_seconds", + "Operator": "lte", + "Value": 600 + }, + { + "Check": "cloudwatch_log_group_retention_policy_specific_days_enabled", + "ConfigKey": "log_group_retention_days", + "Operator": "gte", + "Value": 365 + } ] }, { @@ -778,6 +804,14 @@ "shield_advanced_protection_in_global_accelerators", "shield_advanced_protection_in_internet_facing_load_balancers", "shield_advanced_protection_in_route53_hosted_zones" + ], + "ConfigRequirements": [ + { + "Check": "ec2_securitygroup_with_many_ingress_egress_rules", + "ConfigKey": "max_security_group_rules", + "Operator": "lte", + "Value": 50 + } ] }, { diff --git a/prowler/compliance/aws/c5_aws.json b/prowler/compliance/aws/c5_aws.json index 269a5ce3082..9964da55109 100644 --- a/prowler/compliance/aws/c5_aws.json +++ b/prowler/compliance/aws/c5_aws.json @@ -428,6 +428,14 @@ "cloudtrail_threat_detection_enumeration", "ec2_securitygroup_allow_ingress_from_internet_to_high_risk_tcp_ports", "wellarchitected_workload_no_high_or_medium_risks" + ], + "ConfigRequirements": [ + { + "Check": "cloudtrail_threat_detection_enumeration", + "ConfigKey": "threat_detection_enumeration_threshold", + "Operator": "lte", + "Value": 0.3 + } ] }, { @@ -582,6 +590,14 @@ ], "Checks": [ "secretsmanager_secret_rotated_periodically" + ], + "ConfigRequirements": [ + { + "Check": "secretsmanager_secret_rotated_periodically", + "ConfigKey": "max_days_secret_unrotated", + "Operator": "lte", + "Value": 90 + } ] }, { @@ -599,6 +615,14 @@ "Checks": [ "guardduty_no_high_severity_findings", "cloudtrail_threat_detection_privilege_escalation" + ], + "ConfigRequirements": [ + { + "Check": "cloudtrail_threat_detection_privilege_escalation", + "ConfigKey": "threat_detection_privilege_escalation_threshold", + "Operator": "lte", + "Value": 0.2 + } ] }, { @@ -1542,6 +1566,14 @@ "waf_global_webacl_logging_enabled", "wafv2_webacl_logging_enabled", "wafv2_webacl_rule_logging_enabled" + ], + "ConfigRequirements": [ + { + "Check": "cloudwatch_log_group_retention_policy_specific_days_enabled", + "ConfigKey": "log_group_retention_days", + "Operator": "gte", + "Value": 365 + } ] }, { @@ -1755,6 +1787,14 @@ "rds_cluster_deletion_protection", "rds_instance_deletion_protection", "s3_bucket_no_mfa_delete" + ], + "ConfigRequirements": [ + { + "Check": "kinesis_stream_data_retention_period", + "ConfigKey": "min_kinesis_stream_retention_hours", + "Operator": "gte", + "Value": 168 + } ] }, { @@ -2992,6 +3032,14 @@ "iam_user_mfa_enabled_console_access", "iam_user_console_access_unused", "iam_user_no_setup_initial_access_key" + ], + "ConfigRequirements": [ + { + "Check": "iam_user_console_access_unused", + "ConfigKey": "max_console_access_days", + "Operator": "lte", + "Value": 45 + } ] }, { @@ -3062,6 +3110,26 @@ "rds_cluster_protected_by_backup_plan", "rds_instance_backup_enabled", "rds_instance_protected_by_backup_plan" + ], + "ConfigRequirements": [ + { + "Check": "documentdb_cluster_backup_enabled", + "ConfigKey": "minimum_backup_retention_period", + "Operator": "gte", + "Value": 7 + }, + { + "Check": "elasticache_redis_cluster_backup_enabled", + "ConfigKey": "minimum_snapshot_retention_period", + "Operator": "gte", + "Value": 7 + }, + { + "Check": "neptune_cluster_backup_enabled", + "ConfigKey": "minimum_backup_retention_period", + "Operator": "gte", + "Value": 7 + } ] }, { @@ -5380,6 +5448,38 @@ "iam_user_accesskey_unused", "iam_user_console_access_unused", "secretsmanager_secret_unused" + ], + "ConfigRequirements": [ + { + "Check": "iam_role_access_not_stale_to_bedrock", + "ConfigKey": "max_unused_bedrock_access_days", + "Operator": "lte", + "Value": 60 + }, + { + "Check": "iam_user_access_not_stale_to_bedrock", + "ConfigKey": "max_unused_bedrock_access_days", + "Operator": "lte", + "Value": 60 + }, + { + "Check": "iam_user_access_not_stale_to_sagemaker", + "ConfigKey": "max_unused_sagemaker_access_days", + "Operator": "lte", + "Value": 90 + }, + { + "Check": "iam_user_accesskey_unused", + "ConfigKey": "max_unused_access_keys_days", + "Operator": "lte", + "Value": 45 + }, + { + "Check": "secretsmanager_secret_unused", + "ConfigKey": "max_days_secret_unused", + "Operator": "lte", + "Value": 90 + } ] }, { @@ -10488,6 +10588,26 @@ "appstream_fleet_session_idle_disconnect_timeout", "appstream_fleet_session_disconnect_timeout", "appstream_fleet_maximum_session_duration" + ], + "ConfigRequirements": [ + { + "Check": "appstream_fleet_maximum_session_duration", + "ConfigKey": "max_session_duration_seconds", + "Operator": "lte", + "Value": 36000 + }, + { + "Check": "appstream_fleet_session_disconnect_timeout", + "ConfigKey": "max_disconnect_timeout_in_seconds", + "Operator": "lte", + "Value": 300 + }, + { + "Check": "appstream_fleet_session_idle_disconnect_timeout", + "ConfigKey": "max_idle_disconnect_timeout_in_seconds", + "Operator": "lte", + "Value": 600 + } ] }, { diff --git a/prowler/compliance/aws/ccc_aws.json b/prowler/compliance/aws/ccc_aws.json index 7935424193d..a8ffefa0190 100644 --- a/prowler/compliance/aws/ccc_aws.json +++ b/prowler/compliance/aws/ccc_aws.json @@ -1049,6 +1049,20 @@ "rds_instance_backup_enabled", "neptune_cluster_backup_enabled", "documentdb_cluster_backup_enabled" + ], + "ConfigRequirements": [ + { + "Check": "documentdb_cluster_backup_enabled", + "ConfigKey": "minimum_backup_retention_period", + "Operator": "gte", + "Value": 7 + }, + { + "Check": "neptune_cluster_backup_enabled", + "ConfigKey": "minimum_backup_retention_period", + "Operator": "gte", + "Value": 7 + } ] }, { @@ -1836,6 +1850,12 @@ "ConfigKey": "mute_non_default_regions", "Operator": "eq", "Value": false + }, + { + "Check": "cloudtrail_threat_detection_enumeration", + "ConfigKey": "threat_detection_enumeration_threshold", + "Operator": "lte", + "Value": 0.3 } ] }, @@ -2621,6 +2641,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 + } ] }, { @@ -4357,14 +4385,6 @@ ], "Checks": [ "acm_certificates_expiration_check" - ], - "ConfigRequirements": [ - { - "Check": "acm_certificates_expiration_check", - "ConfigKey": "days_to_expire_threshold", - "Operator": "gte", - "Value": 30 - } ] }, { @@ -4798,6 +4818,14 @@ "Checks": [ "secretsmanager_automatic_rotation_enabled", "secretsmanager_secret_rotated_periodically" + ], + "ConfigRequirements": [ + { + "Check": "secretsmanager_secret_rotated_periodically", + "ConfigKey": "max_days_secret_unrotated", + "Operator": "lte", + "Value": 90 + } ] }, { diff --git a/prowler/compliance/aws/cisa_aws.json b/prowler/compliance/aws/cisa_aws.json index 27b06a1ea48..ebf7c09d37b 100644 --- a/prowler/compliance/aws/cisa_aws.json +++ b/prowler/compliance/aws/cisa_aws.json @@ -21,6 +21,14 @@ "ec2_instance_older_than_specific_days", "ssm_managed_compliant_patching", "ec2_elastic_ip_unassigned" + ], + "ConfigRequirements": [ + { + "Check": "ec2_instance_older_than_specific_days", + "ConfigKey": "max_ec2_instance_age_in_days", + "Operator": "lte", + "Value": 180 + } ] }, { @@ -149,6 +157,18 @@ "ConfigKey": "mute_non_default_regions", "Operator": "eq", "Value": false + }, + { + "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 } ] }, diff --git a/prowler/compliance/aws/ens_rd2022_aws.json b/prowler/compliance/aws/ens_rd2022_aws.json index 144437ce527..9345ac925fe 100644 --- a/prowler/compliance/aws/ens_rd2022_aws.json +++ b/prowler/compliance/aws/ens_rd2022_aws.json @@ -348,6 +348,20 @@ "iam_user_accesskey_unused", "iam_user_console_access_unused", "iam_rotate_access_key_90_days" + ], + "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 + } ] }, { @@ -547,6 +561,26 @@ "iam_user_access_not_stale_to_sagemaker", "iam_user_accesskey_unused", "iam_user_console_access_unused" + ], + "ConfigRequirements": [ + { + "Check": "iam_role_access_not_stale_to_bedrock", + "ConfigKey": "max_unused_bedrock_access_days", + "Operator": "lte", + "Value": 60 + }, + { + "Check": "iam_user_access_not_stale_to_bedrock", + "ConfigKey": "max_unused_bedrock_access_days", + "Operator": "lte", + "Value": 60 + }, + { + "Check": "iam_user_access_not_stale_to_sagemaker", + "ConfigKey": "max_unused_sagemaker_access_days", + "Operator": "lte", + "Value": 90 + } ] }, { @@ -1374,6 +1408,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 + } ] }, { diff --git a/prowler/compliance/aws/fedramp_20x_ksi_low_aws.json b/prowler/compliance/aws/fedramp_20x_ksi_low_aws.json index 15763ef48ed..089fce6d37f 100644 --- a/prowler/compliance/aws/fedramp_20x_ksi_low_aws.json +++ b/prowler/compliance/aws/fedramp_20x_ksi_low_aws.json @@ -44,6 +44,12 @@ "ConfigKey": "mute_non_default_regions", "Operator": "eq", "Value": false + }, + { + "Check": "ec2_instance_older_than_specific_days", + "ConfigKey": "max_ec2_instance_age_in_days", + "Operator": "lte", + "Value": 180 } ] }, @@ -125,6 +131,38 @@ "iam_user_two_active_access_key", "organizations_scp_check_deny_regions", "organizations_opt_out_ai_services_policy" + ], + "ConfigRequirements": [ + { + "Check": "iam_role_access_not_stale_to_bedrock", + "ConfigKey": "max_unused_bedrock_access_days", + "Operator": "lte", + "Value": 60 + }, + { + "Check": "iam_user_access_not_stale_to_bedrock", + "ConfigKey": "max_unused_bedrock_access_days", + "Operator": "lte", + "Value": 60 + }, + { + "Check": "iam_user_access_not_stale_to_sagemaker", + "ConfigKey": "max_unused_sagemaker_access_days", + "Operator": "lte", + "Value": 90 + }, + { + "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 + } ] }, { @@ -203,6 +241,14 @@ "s3_bucket_server_access_logging_enabled", "vpc_flow_logs_enabled", "wafv2_webacl_logging_enabled" + ], + "ConfigRequirements": [ + { + "Check": "cloudwatch_log_group_retention_policy_specific_days_enabled", + "ConfigKey": "log_group_retention_days", + "Operator": "gte", + "Value": 365 + } ] }, { diff --git a/prowler/compliance/aws/fedramp_low_revision_4_aws.json b/prowler/compliance/aws/fedramp_low_revision_4_aws.json index 059de696754..d1b5925f0a6 100644 --- a/prowler/compliance/aws/fedramp_low_revision_4_aws.json +++ b/prowler/compliance/aws/fedramp_low_revision_4_aws.json @@ -59,6 +59,36 @@ "ConfigKey": "mute_non_default_regions", "Operator": "eq", "Value": false + }, + { + "Check": "iam_role_access_not_stale_to_bedrock", + "ConfigKey": "max_unused_bedrock_access_days", + "Operator": "lte", + "Value": 60 + }, + { + "Check": "iam_user_access_not_stale_to_bedrock", + "ConfigKey": "max_unused_bedrock_access_days", + "Operator": "lte", + "Value": 60 + }, + { + "Check": "iam_user_access_not_stale_to_sagemaker", + "ConfigKey": "max_unused_sagemaker_access_days", + "Operator": "lte", + "Value": 90 + }, + { + "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 } ] }, @@ -283,6 +313,14 @@ "ec2_networkacl_allow_ingress_any_port", "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_22", "ec2_networkacl_allow_ingress_any_port" + ], + "ConfigRequirements": [ + { + "Check": "ec2_instance_older_than_specific_days", + "ConfigKey": "max_ec2_instance_age_in_days", + "Operator": "lte", + "Value": 180 + } ] }, { diff --git a/prowler/compliance/aws/fedramp_moderate_revision_4_aws.json b/prowler/compliance/aws/fedramp_moderate_revision_4_aws.json index eaa3ea25dce..e34d190907c 100644 --- a/prowler/compliance/aws/fedramp_moderate_revision_4_aws.json +++ b/prowler/compliance/aws/fedramp_moderate_revision_4_aws.json @@ -805,6 +805,14 @@ "ec2_networkacl_allow_ingress_any_port", "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_22", "ec2_networkacl_allow_ingress_any_port" + ], + "ConfigRequirements": [ + { + "Check": "ec2_instance_older_than_specific_days", + "ConfigKey": "max_ec2_instance_age_in_days", + "Operator": "lte", + "Value": 180 + } ] }, { diff --git a/prowler/compliance/aws/ffiec_aws.json b/prowler/compliance/aws/ffiec_aws.json index 8a50b799259..4246d2b2fc4 100644 --- a/prowler/compliance/aws/ffiec_aws.json +++ b/prowler/compliance/aws/ffiec_aws.json @@ -21,6 +21,14 @@ "ec2_instance_managed_by_ssm", "ec2_instance_older_than_specific_days", "ec2_elastic_ip_unassigned" + ], + "ConfigRequirements": [ + { + "Check": "ec2_instance_older_than_specific_days", + "ConfigKey": "max_ec2_instance_age_in_days", + "Operator": "lte", + "Value": 180 + } ] }, { @@ -125,6 +133,14 @@ "redshift_cluster_audit_logging", "s3_bucket_server_access_logging_enabled", "vpc_flow_logs_enabled" + ], + "ConfigRequirements": [ + { + "Check": "cloudwatch_log_group_retention_policy_specific_days_enabled", + "ConfigKey": "log_group_retention_days", + "Operator": "gte", + "Value": 365 + } ] }, { @@ -728,6 +744,20 @@ "iam_user_mfa_enabled_console_access", "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 + } ] }, { diff --git a/prowler/compliance/aws/gdpr_aws.json b/prowler/compliance/aws/gdpr_aws.json index a97a11e3dcd..ddc0b2c7abb 100644 --- a/prowler/compliance/aws/gdpr_aws.json +++ b/prowler/compliance/aws/gdpr_aws.json @@ -66,6 +66,18 @@ "ConfigKey": "mute_non_default_regions", "Operator": "eq", "Value": false + }, + { + "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 } ] }, 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 871af9e726e..a9cca175afc 100644 --- a/prowler/compliance/aws/gxp_21_cfr_part_11_aws.json +++ b/prowler/compliance/aws/gxp_21_cfr_part_11_aws.json @@ -30,6 +30,14 @@ "s3_bucket_object_versioning", "ssm_managed_compliant_patching", "ssm_managed_compliant_patching" + ], + "ConfigRequirements": [ + { + "Check": "ec2_instance_older_than_specific_days", + "ConfigKey": "max_ec2_instance_age_in_days", + "Operator": "lte", + "Value": 180 + } ] }, { @@ -58,6 +66,14 @@ "s3_bucket_object_versioning", "sagemaker_notebook_instance_without_direct_internet_access_configured", "sagemaker_notebook_instance_encryption_enabled" + ], + "ConfigRequirements": [ + { + "Check": "cloudwatch_log_group_retention_policy_specific_days_enabled", + "ConfigKey": "log_group_retention_days", + "Operator": "gte", + "Value": 365 + } ] }, { @@ -110,6 +126,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": "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 + } ] }, { diff --git a/prowler/compliance/aws/hipaa_aws.json b/prowler/compliance/aws/hipaa_aws.json index 9eb243e6ccf..5aa5f531e89 100644 --- a/prowler/compliance/aws/hipaa_aws.json +++ b/prowler/compliance/aws/hipaa_aws.json @@ -86,6 +86,14 @@ "ec2_networkacl_allow_ingress_any_port", "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_22", "ec2_networkacl_allow_ingress_any_port" + ], + "ConfigRequirements": [ + { + "Check": "ec2_instance_older_than_specific_days", + "ConfigKey": "max_ec2_instance_age_in_days", + "Operator": "lte", + "Value": 180 + } ] }, { @@ -223,6 +231,20 @@ "iam_no_root_access_key", "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 + } ] }, { @@ -760,6 +782,12 @@ "ConfigKey": "mute_non_default_regions", "Operator": "eq", "Value": false + }, + { + "Check": "cloudwatch_log_group_retention_policy_specific_days_enabled", + "ConfigKey": "log_group_retention_days", + "Operator": "gte", + "Value": 365 } ] }, diff --git a/prowler/compliance/aws/iso27001_2022_aws.json b/prowler/compliance/aws/iso27001_2022_aws.json index 563b856317a..fa7aee996a6 100644 --- a/prowler/compliance/aws/iso27001_2022_aws.json +++ b/prowler/compliance/aws/iso27001_2022_aws.json @@ -123,6 +123,26 @@ "cloudtrail_threat_detection_enumeration", "cloudtrail_threat_detection_llm_jacking", "cloudtrail_threat_detection_privilege_escalation" + ], + "ConfigRequirements": [ + { + "Check": "cloudtrail_threat_detection_enumeration", + "ConfigKey": "threat_detection_enumeration_threshold", + "Operator": "lte", + "Value": 0.3 + }, + { + "Check": "cloudtrail_threat_detection_llm_jacking", + "ConfigKey": "threat_detection_llm_jacking_threshold", + "Operator": "lte", + "Value": 0.4 + }, + { + "Check": "cloudtrail_threat_detection_privilege_escalation", + "ConfigKey": "threat_detection_privilege_escalation_threshold", + "Operator": "lte", + "Value": 0.2 + } ] }, { @@ -269,6 +289,38 @@ "iam_password_policy_uppercase", "iam_user_mfa_enabled_console_access", "iam_rotate_access_key_90_days" + ], + "ConfigRequirements": [ + { + "Check": "iam_role_access_not_stale_to_bedrock", + "ConfigKey": "max_unused_bedrock_access_days", + "Operator": "lte", + "Value": 60 + }, + { + "Check": "iam_user_access_not_stale_to_bedrock", + "ConfigKey": "max_unused_bedrock_access_days", + "Operator": "lte", + "Value": 60 + }, + { + "Check": "iam_user_access_not_stale_to_sagemaker", + "ConfigKey": "max_unused_sagemaker_access_days", + "Operator": "lte", + "Value": 90 + }, + { + "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 + } ] }, { @@ -1217,6 +1269,14 @@ "s3_bucket_lifecycle_enabled", "s3_bucket_object_versioning", "ec2_instance_older_than_specific_days" + ], + "ConfigRequirements": [ + { + "Check": "ec2_instance_older_than_specific_days", + "ConfigKey": "max_ec2_instance_age_in_days", + "Operator": "lte", + "Value": 180 + } ] }, { @@ -1446,6 +1506,14 @@ "waf_global_webacl_logging_enabled", "wafv2_webacl_logging_enabled", "wafv2_webacl_rule_logging_enabled" + ], + "ConfigRequirements": [ + { + "Check": "cloudwatch_log_group_retention_policy_specific_days_enabled", + "ConfigKey": "log_group_retention_days", + "Operator": "gte", + "Value": 365 + } ] }, { @@ -1629,6 +1697,14 @@ "ec2_securitygroup_not_used", "ec2_securitygroup_with_many_ingress_egress_rules", "ec2_transitgateway_auto_accept_vpc_attachments" + ], + "ConfigRequirements": [ + { + "Check": "ec2_securitygroup_with_many_ingress_egress_rules", + "ConfigKey": "max_security_group_rules", + "Operator": "lte", + "Value": 50 + } ] }, { diff --git a/prowler/compliance/aws/kisa_isms_p_2023_aws.json b/prowler/compliance/aws/kisa_isms_p_2023_aws.json index 7b0446ac3f0..de4c0d44c79 100644 --- a/prowler/compliance/aws/kisa_isms_p_2023_aws.json +++ b/prowler/compliance/aws/kisa_isms_p_2023_aws.json @@ -1343,6 +1343,32 @@ "Case 2: In the login process for information systems and personal information processing systems, detailed messages are displayed about whether the ID exists or the password is incorrect, and there is no limit on login failure attempts." ] } + ], + "ConfigRequirements": [ + { + "Check": "appstream_fleet_maximum_session_duration", + "ConfigKey": "max_session_duration_seconds", + "Operator": "lte", + "Value": 36000 + }, + { + "Check": "appstream_fleet_session_disconnect_timeout", + "ConfigKey": "max_disconnect_timeout_in_seconds", + "Operator": "lte", + "Value": 300 + }, + { + "Check": "appstream_fleet_session_idle_disconnect_timeout", + "ConfigKey": "max_idle_disconnect_timeout_in_seconds", + "Operator": "lte", + "Value": 600 + }, + { + "Check": "iam_user_console_access_unused", + "ConfigKey": "max_console_access_days", + "Operator": "lte", + "Value": 45 + } ] }, { @@ -2200,6 +2226,26 @@ "Case 3: The encryption key applied in the development system is the same as the one applied in the production system, making it easy to decrypt actual data through the development system." ] } + ], + "ConfigRequirements": [ + { + "Check": "iam_user_accesskey_unused", + "ConfigKey": "max_unused_access_keys_days", + "Operator": "lte", + "Value": 45 + }, + { + "Check": "secretsmanager_secret_rotated_periodically", + "ConfigKey": "max_days_secret_unrotated", + "Operator": "lte", + "Value": 90 + }, + { + "Check": "secretsmanager_secret_unused", + "ConfigKey": "max_days_secret_unused", + "Operator": "lte", + "Value": 90 + } ] }, { @@ -2505,6 +2551,14 @@ "Case 4: Inconsistencies exist between fault handling procedures and fault type-specific response methods, or there is a lack of rationale for estimating response times, making swift, accurate, and systematic responses difficult." ] } + ], + "ConfigRequirements": [ + { + "Check": "ec2_instance_older_than_specific_days", + "ConfigKey": "max_ec2_instance_age_in_days", + "Operator": "lte", + "Value": 180 + } ] }, { @@ -2563,6 +2617,26 @@ "Case 4: Although higher-level or internal guidelines stipulate that recovery tests for backup media should be conducted periodically, recovery tests have not been performed for an extended period." ] } + ], + "ConfigRequirements": [ + { + "Check": "documentdb_cluster_backup_enabled", + "ConfigKey": "minimum_backup_retention_period", + "Operator": "gte", + "Value": 7 + }, + { + "Check": "elasticache_redis_cluster_backup_enabled", + "ConfigKey": "minimum_snapshot_retention_period", + "Operator": "gte", + "Value": 7 + }, + { + "Check": "neptune_cluster_backup_enabled", + "ConfigKey": "minimum_backup_retention_period", + "Operator": "gte", + "Value": 7 + } ] }, { @@ -2659,6 +2733,32 @@ "Case 6: A personal information processing system handling personal information of 100,000 data subjects is only retaining access logs for one year." ] } + ], + "ConfigRequirements": [ + { + "Check": "cloudtrail_threat_detection_enumeration", + "ConfigKey": "threat_detection_enumeration_threshold", + "Operator": "lte", + "Value": 0.3 + }, + { + "Check": "cloudtrail_threat_detection_llm_jacking", + "ConfigKey": "threat_detection_llm_jacking_threshold", + "Operator": "lte", + "Value": 0.4 + }, + { + "Check": "cloudtrail_threat_detection_privilege_escalation", + "ConfigKey": "threat_detection_privilege_escalation_threshold", + "Operator": "lte", + "Value": 0.2 + }, + { + "Check": "cloudwatch_log_group_retention_policy_specific_days_enabled", + "ConfigKey": "log_group_retention_days", + "Operator": "gte", + "Value": 365 + } ] }, { @@ -3407,6 +3507,12 @@ "RSA-1024", "P-192" ] + }, + { + "Check": "ec2_securitygroup_with_many_ingress_egress_rules", + "ConfigKey": "max_security_group_rules", + "Operator": "lte", + "Value": 50 } ], "Attributes": [ 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 40b338ce41f..d6aea0ba17e 100644 --- a/prowler/compliance/aws/kisa_isms_p_2023_korean_aws.json +++ b/prowler/compliance/aws/kisa_isms_p_2023_korean_aws.json @@ -1343,6 +1343,32 @@ "사례 2 : 정보시스템 및 개인정보처리시스템 로그인 실패 시 해당 ID가 존재하지 않거나 비밀번호가 틀림을 자세히 표시해 주고 있으며, 로그인 실패횟수에 대한 제한이 없는 경우" ] } + ], + "ConfigRequirements": [ + { + "Check": "appstream_fleet_maximum_session_duration", + "ConfigKey": "max_session_duration_seconds", + "Operator": "lte", + "Value": 36000 + }, + { + "Check": "appstream_fleet_session_disconnect_timeout", + "ConfigKey": "max_disconnect_timeout_in_seconds", + "Operator": "lte", + "Value": 300 + }, + { + "Check": "appstream_fleet_session_idle_disconnect_timeout", + "ConfigKey": "max_idle_disconnect_timeout_in_seconds", + "Operator": "lte", + "Value": 600 + }, + { + "Check": "iam_user_console_access_unused", + "ConfigKey": "max_console_access_days", + "Operator": "lte", + "Value": 45 + } ] }, { @@ -2202,6 +2228,26 @@ "사례 3 : 개발시스템에 적용되어 있는 암호키와 운영시스템에 적용된 암호키가 동일하여, 암호화된 실데이터가 개발시스템을 통해 쉽게 복호화가 가능한 경우" ] } + ], + "ConfigRequirements": [ + { + "Check": "iam_user_accesskey_unused", + "ConfigKey": "max_unused_access_keys_days", + "Operator": "lte", + "Value": 45 + }, + { + "Check": "secretsmanager_secret_rotated_periodically", + "ConfigKey": "max_days_secret_unrotated", + "Operator": "lte", + "Value": 90 + }, + { + "Check": "secretsmanager_secret_unused", + "ConfigKey": "max_days_secret_unused", + "Operator": "lte", + "Value": 90 + } ] }, { @@ -2509,6 +2555,14 @@ "사례 4 : 장애처리절차와 장애유형별 조치방법 간 일관성이 없거나 예상소요시간 산정에 대한 근거가 부족하여 신속·정확하고 체계적인 대응이 어려운 경우" ] } + ], + "ConfigRequirements": [ + { + "Check": "ec2_instance_older_than_specific_days", + "ConfigKey": "max_ec2_instance_age_in_days", + "Operator": "lte", + "Value": 180 + } ] }, { @@ -2567,6 +2621,26 @@ "사례 4 : 상위 지침 또는 내부 지침에는 주기적으로 백업매체에 대한 복구 테스트를 수행하도록 정하고 있으나 복구테스트를 장기간 실시하지 않은 경우" ] } + ], + "ConfigRequirements": [ + { + "Check": "documentdb_cluster_backup_enabled", + "ConfigKey": "minimum_backup_retention_period", + "Operator": "gte", + "Value": 7 + }, + { + "Check": "elasticache_redis_cluster_backup_enabled", + "ConfigKey": "minimum_snapshot_retention_period", + "Operator": "gte", + "Value": 7 + }, + { + "Check": "neptune_cluster_backup_enabled", + "ConfigKey": "minimum_backup_retention_period", + "Operator": "gte", + "Value": 7 + } ] }, { @@ -2662,6 +2736,32 @@ "사례 6 : 개인정보처리자가 정보주체 10만 명의 개인정보를 처리하는 개인정보처리시스템의 개인정보취급자 접속기록을 1년간만 보관하고 있는 경우" ] } + ], + "ConfigRequirements": [ + { + "Check": "cloudtrail_threat_detection_enumeration", + "ConfigKey": "threat_detection_enumeration_threshold", + "Operator": "lte", + "Value": 0.3 + }, + { + "Check": "cloudtrail_threat_detection_llm_jacking", + "ConfigKey": "threat_detection_llm_jacking_threshold", + "Operator": "lte", + "Value": 0.4 + }, + { + "Check": "cloudtrail_threat_detection_privilege_escalation", + "ConfigKey": "threat_detection_privilege_escalation_threshold", + "Operator": "lte", + "Value": 0.2 + }, + { + "Check": "cloudwatch_log_group_retention_policy_specific_days_enabled", + "ConfigKey": "log_group_retention_days", + "Operator": "gte", + "Value": 365 + } ] }, { @@ -3410,6 +3510,12 @@ "RSA-1024", "P-192" ] + }, + { + "Check": "ec2_securitygroup_with_many_ingress_egress_rules", + "ConfigKey": "max_security_group_rules", + "Operator": "lte", + "Value": 50 } ], "Attributes": [ diff --git a/prowler/compliance/aws/mitre_attack_aws.json b/prowler/compliance/aws/mitre_attack_aws.json index 3ac8cf04323..e17e3fba9f9 100644 --- a/prowler/compliance/aws/mitre_attack_aws.json +++ b/prowler/compliance/aws/mitre_attack_aws.json @@ -244,6 +244,36 @@ "ConfigKey": "mute_non_default_regions", "Operator": "eq", "Value": false + }, + { + "Check": "iam_role_access_not_stale_to_bedrock", + "ConfigKey": "max_unused_bedrock_access_days", + "Operator": "lte", + "Value": 60 + }, + { + "Check": "iam_user_access_not_stale_to_bedrock", + "ConfigKey": "max_unused_bedrock_access_days", + "Operator": "lte", + "Value": 60 + }, + { + "Check": "iam_user_access_not_stale_to_sagemaker", + "ConfigKey": "max_unused_sagemaker_access_days", + "Operator": "lte", + "Value": 90 + }, + { + "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": [ diff --git a/prowler/compliance/aws/nis2_aws.json b/prowler/compliance/aws/nis2_aws.json index 3a4d567d256..31e577ab893 100644 --- a/prowler/compliance/aws/nis2_aws.json +++ b/prowler/compliance/aws/nis2_aws.json @@ -50,6 +50,14 @@ "SubSection": "1.1 Policy on the security of network and information systems", "Service": "generic" } + ], + "ConfigRequirements": [ + { + "Check": "secretsmanager_secret_rotated_periodically", + "ConfigKey": "max_days_secret_unrotated", + "Operator": "lte", + "Value": 90 + } ] }, { @@ -66,6 +74,20 @@ "SubSection": "1.1 Policy on the security of network and information systems", "Service": "generic" } + ], + "ConfigRequirements": [ + { + "Check": "cloudwatch_log_group_retention_policy_specific_days_enabled", + "ConfigKey": "log_group_retention_days", + "Operator": "gte", + "Value": 365 + }, + { + "Check": "kinesis_stream_data_retention_period", + "ConfigKey": "min_kinesis_stream_retention_hours", + "Operator": "gte", + "Value": 168 + } ] }, { @@ -726,6 +748,26 @@ "SubSection": "3.4 Event assessment and classification", "Service": "cloudtrail" } + ], + "ConfigRequirements": [ + { + "Check": "cloudtrail_threat_detection_enumeration", + "ConfigKey": "threat_detection_enumeration_threshold", + "Operator": "lte", + "Value": 0.3 + }, + { + "Check": "cloudtrail_threat_detection_llm_jacking", + "ConfigKey": "threat_detection_llm_jacking_threshold", + "Operator": "lte", + "Value": 0.4 + }, + { + "Check": "cloudtrail_threat_detection_privilege_escalation", + "ConfigKey": "threat_detection_privilege_escalation_threshold", + "Operator": "lte", + "Value": 0.2 + } ] }, { @@ -856,6 +898,26 @@ "SubSection": "3.6 Post-incident reviews", "Service": "generic" } + ], + "ConfigRequirements": [ + { + "Check": "documentdb_cluster_backup_enabled", + "ConfigKey": "minimum_backup_retention_period", + "Operator": "gte", + "Value": 7 + }, + { + "Check": "elasticache_redis_cluster_backup_enabled", + "ConfigKey": "minimum_snapshot_retention_period", + "Operator": "gte", + "Value": 7 + }, + { + "Check": "neptune_cluster_backup_enabled", + "ConfigKey": "minimum_backup_retention_period", + "Operator": "gte", + "Value": 7 + } ] }, { @@ -1595,6 +1657,14 @@ "SubSection": "9.2 Cryptography", "Service": "secretsmanager" } + ], + "ConfigRequirements": [ + { + "Check": "secretsmanager_secret_unused", + "ConfigKey": "max_days_secret_unused", + "Operator": "lte", + "Value": 90 + } ] }, { @@ -1876,6 +1946,14 @@ "SubSection": "11.3 Privileged accounts and system administration accounts", "Service": "iam" } + ], + "ConfigRequirements": [ + { + "Check": "iam_user_console_access_unused", + "ConfigKey": "max_console_access_days", + "Operator": "lte", + "Value": 45 + } ] }, { @@ -1977,6 +2055,32 @@ "SubSection": "11.5 Identification", "Service": "iam" } + ], + "ConfigRequirements": [ + { + "Check": "iam_role_access_not_stale_to_bedrock", + "ConfigKey": "max_unused_bedrock_access_days", + "Operator": "lte", + "Value": 60 + }, + { + "Check": "iam_user_access_not_stale_to_bedrock", + "ConfigKey": "max_unused_bedrock_access_days", + "Operator": "lte", + "Value": 60 + }, + { + "Check": "iam_user_access_not_stale_to_sagemaker", + "ConfigKey": "max_unused_sagemaker_access_days", + "Operator": "lte", + "Value": 90 + }, + { + "Check": "iam_user_accesskey_unused", + "ConfigKey": "max_unused_access_keys_days", + "Operator": "lte", + "Value": 45 + } ] }, { @@ -2062,6 +2166,26 @@ "SubSection": "11.6 Authentication", "Service": "appstream" } + ], + "ConfigRequirements": [ + { + "Check": "appstream_fleet_maximum_session_duration", + "ConfigKey": "max_session_duration_seconds", + "Operator": "lte", + "Value": 36000 + }, + { + "Check": "appstream_fleet_session_disconnect_timeout", + "ConfigKey": "max_disconnect_timeout_in_seconds", + "Operator": "lte", + "Value": 300 + }, + { + "Check": "appstream_fleet_session_idle_disconnect_timeout", + "ConfigKey": "max_idle_disconnect_timeout_in_seconds", + "Operator": "lte", + "Value": 600 + } ] }, { 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 921bd33a537..5bb5c604c53 100644 --- a/prowler/compliance/aws/nist_800_171_revision_2_aws.json +++ b/prowler/compliance/aws/nist_800_171_revision_2_aws.json @@ -48,6 +48,38 @@ "ec2_networkacl_allow_ingress_any_port", "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_22", "ec2_networkacl_allow_ingress_any_port" + ], + "ConfigRequirements": [ + { + "Check": "iam_role_access_not_stale_to_bedrock", + "ConfigKey": "max_unused_bedrock_access_days", + "Operator": "lte", + "Value": 60 + }, + { + "Check": "iam_user_access_not_stale_to_bedrock", + "ConfigKey": "max_unused_bedrock_access_days", + "Operator": "lte", + "Value": 60 + }, + { + "Check": "iam_user_access_not_stale_to_sagemaker", + "ConfigKey": "max_unused_sagemaker_access_days", + "Operator": "lte", + "Value": 90 + }, + { + "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 + } ] }, { @@ -348,6 +380,12 @@ "ConfigKey": "mute_non_default_regions", "Operator": "eq", "Value": false + }, + { + "Check": "cloudwatch_log_group_retention_policy_specific_days_enabled", + "ConfigKey": "log_group_retention_days", + "Operator": "gte", + "Value": 365 } ] }, @@ -507,6 +545,14 @@ "ssm_managed_compliant_patching", "ec2_elastic_ip_unassigned", "ec2_networkacl_allow_ingress_any_port" + ], + "ConfigRequirements": [ + { + "Check": "ec2_instance_older_than_specific_days", + "ConfigKey": "max_ec2_instance_age_in_days", + "Operator": "lte", + "Value": 180 + } ] }, { 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 8bd36a3910e..7caa64fc8b0 100644 --- a/prowler/compliance/aws/nist_800_53_revision_4_aws.json +++ b/prowler/compliance/aws/nist_800_53_revision_4_aws.json @@ -615,6 +615,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 + } ] }, { @@ -698,6 +706,14 @@ "elbv2_deletion_protection", "ssm_managed_compliant_patching", "ec2_networkacl_allow_ingress_any_port" + ], + "ConfigRequirements": [ + { + "Check": "ec2_instance_older_than_specific_days", + "ConfigKey": "max_ec2_instance_age_in_days", + "Operator": "lte", + "Value": 180 + } ] }, { 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 e0ef9362292..69da4dab7d8 100644 --- a/prowler/compliance/aws/nist_800_53_revision_5_aws.json +++ b/prowler/compliance/aws/nist_800_53_revision_5_aws.json @@ -35,6 +35,38 @@ "iam_user_accesskey_unused", "iam_user_console_access_unused", "secretsmanager_automatic_rotation_enabled" + ], + "ConfigRequirements": [ + { + "Check": "iam_role_access_not_stale_to_bedrock", + "ConfigKey": "max_unused_bedrock_access_days", + "Operator": "lte", + "Value": 60 + }, + { + "Check": "iam_user_access_not_stale_to_bedrock", + "ConfigKey": "max_unused_bedrock_access_days", + "Operator": "lte", + "Value": 60 + }, + { + "Check": "iam_user_access_not_stale_to_sagemaker", + "ConfigKey": "max_unused_sagemaker_access_days", + "Operator": "lte", + "Value": 90 + }, + { + "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 + } ] }, { @@ -1374,6 +1406,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 + } ] }, { @@ -2715,6 +2755,14 @@ "elbv2_deletion_protection", "ssm_managed_compliant_patching", "ec2_networkacl_allow_ingress_any_port" + ], + "ConfigRequirements": [ + { + "Check": "ec2_instance_older_than_specific_days", + "ConfigKey": "max_ec2_instance_age_in_days", + "Operator": "lte", + "Value": 180 + } ] }, { diff --git a/prowler/compliance/aws/nist_csf_1.1_aws.json b/prowler/compliance/aws/nist_csf_1.1_aws.json index 9921efce568..a9cd04f0ac6 100644 --- a/prowler/compliance/aws/nist_csf_1.1_aws.json +++ b/prowler/compliance/aws/nist_csf_1.1_aws.json @@ -817,6 +817,38 @@ "iam_user_accesskey_unused", "iam_user_console_access_unused", "secretsmanager_automatic_rotation_enabled" + ], + "ConfigRequirements": [ + { + "Check": "iam_role_access_not_stale_to_bedrock", + "ConfigKey": "max_unused_bedrock_access_days", + "Operator": "lte", + "Value": 60 + }, + { + "Check": "iam_user_access_not_stale_to_bedrock", + "ConfigKey": "max_unused_bedrock_access_days", + "Operator": "lte", + "Value": 60 + }, + { + "Check": "iam_user_access_not_stale_to_sagemaker", + "ConfigKey": "max_unused_sagemaker_access_days", + "Operator": "lte", + "Value": 90 + }, + { + "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 + } ] }, { @@ -1114,6 +1146,14 @@ "elbv2_deletion_protection", "ssm_managed_compliant_patching", "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_22" + ], + "ConfigRequirements": [ + { + "Check": "ec2_instance_older_than_specific_days", + "ConfigKey": "max_ec2_instance_age_in_days", + "Operator": "lte", + "Value": 180 + } ] }, { diff --git a/prowler/compliance/aws/nist_csf_2.0_aws.json b/prowler/compliance/aws/nist_csf_2.0_aws.json index c06eeec11d3..df9ae9f280b 100644 --- a/prowler/compliance/aws/nist_csf_2.0_aws.json +++ b/prowler/compliance/aws/nist_csf_2.0_aws.json @@ -405,6 +405,24 @@ "ConfigKey": "mute_non_default_regions", "Operator": "eq", "Value": false + }, + { + "Check": "cloudtrail_threat_detection_enumeration", + "ConfigKey": "threat_detection_enumeration_threshold", + "Operator": "lte", + "Value": 0.3 + }, + { + "Check": "cloudtrail_threat_detection_llm_jacking", + "ConfigKey": "threat_detection_llm_jacking_threshold", + "Operator": "lte", + "Value": 0.4 + }, + { + "Check": "cloudtrail_threat_detection_privilege_escalation", + "ConfigKey": "threat_detection_privilege_escalation_threshold", + "Operator": "lte", + "Value": 0.2 } ] }, @@ -840,6 +858,36 @@ "ConfigKey": "mute_non_default_regions", "Operator": "eq", "Value": false + }, + { + "Check": "iam_role_access_not_stale_to_bedrock", + "ConfigKey": "max_unused_bedrock_access_days", + "Operator": "lte", + "Value": 60 + }, + { + "Check": "iam_user_access_not_stale_to_bedrock", + "ConfigKey": "max_unused_bedrock_access_days", + "Operator": "lte", + "Value": 60 + }, + { + "Check": "iam_user_access_not_stale_to_sagemaker", + "ConfigKey": "max_unused_sagemaker_access_days", + "Operator": "lte", + "Value": 90 + }, + { + "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 } ] }, @@ -1319,6 +1367,26 @@ "rds_cluster_protected_by_backup_plan", "rds_instance_backup_enabled", "rds_instance_protected_by_backup_plan" + ], + "ConfigRequirements": [ + { + "Check": "documentdb_cluster_backup_enabled", + "ConfigKey": "minimum_backup_retention_period", + "Operator": "gte", + "Value": 7 + }, + { + "Check": "elasticache_redis_cluster_backup_enabled", + "ConfigKey": "minimum_snapshot_retention_period", + "Operator": "gte", + "Value": 7 + }, + { + "Check": "neptune_cluster_backup_enabled", + "ConfigKey": "minimum_backup_retention_period", + "Operator": "gte", + "Value": 7 + } ] }, { @@ -1354,6 +1422,12 @@ "ConfigKey": "mute_non_default_regions", "Operator": "eq", "Value": false + }, + { + "Check": "secretsmanager_secret_rotated_periodically", + "ConfigKey": "max_days_secret_unrotated", + "Operator": "lte", + "Value": 90 } ] }, diff --git a/prowler/compliance/aws/pci_3.2.1_aws.json b/prowler/compliance/aws/pci_3.2.1_aws.json index ca8e968bf9f..6f3fd6b2bb4 100644 --- a/prowler/compliance/aws/pci_3.2.1_aws.json +++ b/prowler/compliance/aws/pci_3.2.1_aws.json @@ -689,6 +689,14 @@ "Section": "Requirement 3: Protect stored cardholder data", "SubSection": "Requirement 3: Protect stored cardholder data" } + ], + "ConfigRequirements": [ + { + "Check": "elasticache_redis_cluster_backup_enabled", + "ConfigKey": "minimum_snapshot_retention_period", + "Operator": "gte", + "Value": 7 + } ] }, { @@ -1555,6 +1563,20 @@ "Section": "Requirement 8: Identify and authenticate access to system components", "SubSection": "Requirement 8: Identify and authenticate access to system components" } + ], + "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 + } ] }, { @@ -1590,6 +1612,26 @@ "Section": "Requirement 8: Identify and authenticate access to system components", "SubSection": "8.1 Define and implement policies and procedures to ensure proper user identification management for non-consumer users and administrators" } + ], + "ConfigRequirements": [ + { + "Check": "iam_role_access_not_stale_to_bedrock", + "ConfigKey": "max_unused_bedrock_access_days", + "Operator": "lte", + "Value": 60 + }, + { + "Check": "iam_user_access_not_stale_to_bedrock", + "ConfigKey": "max_unused_bedrock_access_days", + "Operator": "lte", + "Value": 60 + }, + { + "Check": "iam_user_access_not_stale_to_sagemaker", + "ConfigKey": "max_unused_sagemaker_access_days", + "Operator": "lte", + "Value": 90 + } ] }, { @@ -2118,6 +2160,14 @@ "Section": "Requirement 10: Track and monitor all access to network resources and cardholder data", "SubSection": "Requirement 10: Track and monitor all access to network resources and cardholder data" } + ], + "ConfigRequirements": [ + { + "Check": "cloudwatch_log_group_retention_policy_specific_days_enabled", + "ConfigKey": "log_group_retention_days", + "Operator": "gte", + "Value": 365 + } ] }, { diff --git a/prowler/compliance/aws/pci_4.0_aws.json b/prowler/compliance/aws/pci_4.0_aws.json index e21b5435568..4404386be1e 100644 --- a/prowler/compliance/aws/pci_4.0_aws.json +++ b/prowler/compliance/aws/pci_4.0_aws.json @@ -9000,6 +9000,14 @@ "Section": "10.3.3: Audit logs are protected from destruction and unauthorized modifications. ", "Service": "elasticache" } + ], + "ConfigRequirements": [ + { + "Check": "elasticache_redis_cluster_backup_enabled", + "ConfigKey": "minimum_snapshot_retention_period", + "Operator": "gte", + "Value": 7 + } ] }, { @@ -9028,6 +9036,14 @@ "Section": "10.3.3: Audit logs are protected from destruction and unauthorized modifications. ", "Service": "neptune" } + ], + "ConfigRequirements": [ + { + "Check": "neptune_cluster_backup_enabled", + "ConfigKey": "minimum_backup_retention_period", + "Operator": "gte", + "Value": 7 + } ] }, { @@ -9610,6 +9626,14 @@ "Section": "10.5.1: Audit log history is retained and available for analysis. ", "Service": "documentdb" } + ], + "ConfigRequirements": [ + { + "Check": "documentdb_cluster_backup_enabled", + "ConfigKey": "minimum_backup_retention_period", + "Operator": "gte", + "Value": 7 + } ] }, { @@ -17029,6 +17053,14 @@ "Section": "7.2.4: Access to system components and data is appropriately defined and assigned. ", "Service": "iam" } + ], + "ConfigRequirements": [ + { + "Check": "iam_user_accesskey_unused", + "ConfigKey": "max_unused_access_keys_days", + "Operator": "lte", + "Value": 45 + } ] }, { @@ -17043,6 +17075,14 @@ "Section": "7.2.4: Access to system components and data is appropriately defined and assigned. ", "Service": "secretsmanager" } + ], + "ConfigRequirements": [ + { + "Check": "secretsmanager_secret_unused", + "ConfigKey": "max_days_secret_unused", + "Operator": "lte", + "Value": 90 + } ] }, { diff --git a/prowler/compliance/aws/prowler_threatscore_aws.json b/prowler/compliance/aws/prowler_threatscore_aws.json index c8c093907a4..25c2dfae16b 100644 --- a/prowler/compliance/aws/prowler_threatscore_aws.json +++ b/prowler/compliance/aws/prowler_threatscore_aws.json @@ -1555,6 +1555,14 @@ "LevelOfRisk": 2, "Weight": 8 } + ], + "ConfigRequirements": [ + { + "Check": "cloudwatch_log_group_retention_policy_specific_days_enabled", + "ConfigKey": "log_group_retention_days", + "Operator": "gte", + "Value": 365 + } ] }, { diff --git a/prowler/compliance/aws/rbi_cyber_security_framework_aws.json b/prowler/compliance/aws/rbi_cyber_security_framework_aws.json index 5de1f5ca8a2..af8aca27033 100644 --- a/prowler/compliance/aws/rbi_cyber_security_framework_aws.json +++ b/prowler/compliance/aws/rbi_cyber_security_framework_aws.json @@ -192,6 +192,12 @@ "ConfigKey": "mute_non_default_regions", "Operator": "eq", "Value": false + }, + { + "Check": "cloudwatch_log_group_retention_policy_specific_days_enabled", + "ConfigKey": "log_group_retention_days", + "Operator": "gte", + "Value": 365 } ] }, diff --git a/prowler/compliance/aws/secnumcloud_3.2_aws.json b/prowler/compliance/aws/secnumcloud_3.2_aws.json index 701f931b05c..edb21502c5c 100644 --- a/prowler/compliance/aws/secnumcloud_3.2_aws.json +++ b/prowler/compliance/aws/secnumcloud_3.2_aws.json @@ -307,6 +307,38 @@ "iam_user_accesskey_unused", "iam_user_console_access_unused", "iam_no_expired_server_certificates_stored" + ], + "ConfigRequirements": [ + { + "Check": "iam_role_access_not_stale_to_bedrock", + "ConfigKey": "max_unused_bedrock_access_days", + "Operator": "lte", + "Value": 60 + }, + { + "Check": "iam_user_access_not_stale_to_bedrock", + "ConfigKey": "max_unused_bedrock_access_days", + "Operator": "lte", + "Value": 60 + }, + { + "Check": "iam_user_access_not_stale_to_sagemaker", + "ConfigKey": "max_unused_sagemaker_access_days", + "Operator": "lte", + "Value": 90 + }, + { + "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 + } ] }, { @@ -562,6 +594,20 @@ "codebuild_project_no_secrets_in_variables", "ssm_document_secrets", "cloudwatch_log_group_no_secrets_in_logs" + ], + "ConfigRequirements": [ + { + "Check": "secretsmanager_secret_rotated_periodically", + "ConfigKey": "max_days_secret_unrotated", + "Operator": "lte", + "Value": 90 + }, + { + "Check": "secretsmanager_secret_unused", + "ConfigKey": "max_days_secret_unused", + "Operator": "lte", + "Value": 90 + } ] }, { @@ -849,6 +895,26 @@ "documentdb_cluster_backup_enabled", "elasticache_redis_cluster_backup_enabled", "redshift_cluster_automated_snapshot" + ], + "ConfigRequirements": [ + { + "Check": "documentdb_cluster_backup_enabled", + "ConfigKey": "minimum_backup_retention_period", + "Operator": "gte", + "Value": 7 + }, + { + "Check": "elasticache_redis_cluster_backup_enabled", + "ConfigKey": "minimum_snapshot_retention_period", + "Operator": "gte", + "Value": 7 + }, + { + "Check": "neptune_cluster_backup_enabled", + "ConfigKey": "minimum_backup_retention_period", + "Operator": "gte", + "Value": 7 + } ] }, { @@ -885,6 +951,14 @@ "codebuild_project_logging_enabled", "ecs_task_definitions_logging_enabled", "glue_etl_jobs_logging_enabled" + ], + "ConfigRequirements": [ + { + "Check": "cloudwatch_log_group_retention_policy_specific_days_enabled", + "ConfigKey": "log_group_retention_days", + "Operator": "gte", + "Value": 365 + } ] }, { diff --git a/prowler/compliance/aws/soc2_aws.json b/prowler/compliance/aws/soc2_aws.json index c0041a9daeb..291e849796c 100644 --- a/prowler/compliance/aws/soc2_aws.json +++ b/prowler/compliance/aws/soc2_aws.json @@ -24,6 +24,20 @@ "iam_inline_policy_no_administrative_privileges", "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 + } ] }, { @@ -136,6 +150,26 @@ "cloudtrail_threat_detection_privilege_escalation", "cloudtrail_threat_detection_enumeration", "cloudtrail_threat_detection_llm_jacking" + ], + "ConfigRequirements": [ + { + "Check": "cloudtrail_threat_detection_enumeration", + "ConfigKey": "threat_detection_enumeration_threshold", + "Operator": "lte", + "Value": 0.3 + }, + { + "Check": "cloudtrail_threat_detection_llm_jacking", + "ConfigKey": "threat_detection_llm_jacking_threshold", + "Operator": "lte", + "Value": 0.4 + }, + { + "Check": "cloudtrail_threat_detection_privilege_escalation", + "ConfigKey": "threat_detection_privilege_escalation_threshold", + "Operator": "lte", + "Value": 0.2 + } ] }, { @@ -460,6 +494,12 @@ "ConfigKey": "mute_non_default_regions", "Operator": "eq", "Value": false + }, + { + "Check": "cloudwatch_log_group_retention_policy_specific_days_enabled", + "ConfigKey": "log_group_retention_days", + "Operator": "gte", + "Value": 365 } ] }, @@ -680,6 +720,14 @@ "s3_bucket_object_versioning", "cloudwatch_log_group_retention_policy_specific_days_enabled", "kinesis_stream_data_retention_period" + ], + "ConfigRequirements": [ + { + "Check": "kinesis_stream_data_retention_period", + "ConfigKey": "min_kinesis_stream_retention_hours", + "Operator": "gte", + "Value": 168 + } ] }, { diff --git a/prowler/compliance/azure/c5_azure.json b/prowler/compliance/azure/c5_azure.json index 6fff7a36915..a19a89e6e5a 100644 --- a/prowler/compliance/azure/c5_azure.json +++ b/prowler/compliance/azure/c5_azure.json @@ -6845,6 +6845,14 @@ ], "Checks": [ "apim_threat_detection_llm_jacking" + ], + "ConfigRequirements": [ + { + "Check": "apim_threat_detection_llm_jacking", + "ConfigKey": "apim_threat_detection_llm_jacking_threshold", + "Operator": "lte", + "Value": 0.1 + } ] }, { diff --git a/prowler/compliance/csa_ccm_4.0.json b/prowler/compliance/csa_ccm_4.0.json index 1cb7428e517..46a6797bef2 100644 --- a/prowler/compliance/csa_ccm_4.0.json +++ b/prowler/compliance/csa_ccm_4.0.json @@ -914,7 +914,30 @@ "oraclecloud": [ "objectstorage_bucket_versioning_enabled" ] - } + }, + "config_requirements": [ + { + "Check": "documentdb_cluster_backup_enabled", + "ConfigKey": "minimum_backup_retention_period", + "Operator": "gte", + "Value": 7, + "Provider": "aws" + }, + { + "Check": "elasticache_redis_cluster_backup_enabled", + "ConfigKey": "minimum_snapshot_retention_period", + "Operator": "gte", + "Value": 7, + "Provider": "aws" + }, + { + "Check": "neptune_cluster_backup_enabled", + "ConfigKey": "minimum_backup_retention_period", + "Operator": "gte", + "Value": 7, + "Provider": "aws" + } + ] }, { "id": "BCR-09", @@ -2210,7 +2233,16 @@ "identity_user_customer_secret_keys_rotated_90_days", "identity_user_db_passwords_rotated_90_days" ] - } + }, + "config_requirements": [ + { + "Check": "secretsmanager_secret_rotated_periodically", + "ConfigKey": "max_days_secret_unrotated", + "Operator": "lte", + "Value": 90, + "Provider": "aws" + } + ] }, { "id": "CEK-14", @@ -3252,7 +3284,44 @@ "oraclecloud": [ "audit_log_retention_period_365_days" ] - } + }, + "config_requirements": [ + { + "Check": "cloudstorage_bucket_sufficient_retention_period", + "ConfigKey": "storage_min_retention_days", + "Operator": "gte", + "Value": 90, + "Provider": "gcp" + }, + { + "Check": "cloudwatch_log_group_retention_policy_specific_days_enabled", + "ConfigKey": "log_group_retention_days", + "Operator": "gte", + "Value": 365, + "Provider": "aws" + }, + { + "Check": "kinesis_stream_data_retention_period", + "ConfigKey": "min_kinesis_stream_retention_hours", + "Operator": "gte", + "Value": 168, + "Provider": "aws" + }, + { + "Check": "rds_instance_sql_audit_retention", + "ConfigKey": "min_rds_audit_retention_days", + "Operator": "gte", + "Value": 180, + "Provider": "alibabacloud" + }, + { + "Check": "sls_logstore_retention_period", + "ConfigKey": "min_log_retention_days", + "Operator": "gte", + "Value": 365, + "Provider": "alibabacloud" + } + ] }, { "id": "DSP-17", @@ -3859,7 +3928,44 @@ "identity_user_customer_secret_keys_rotated_90_days", "identity_user_valid_email_address" ] - } + }, + "config_requirements": [ + { + "Check": "iam_sa_user_managed_key_unused", + "ConfigKey": "max_unused_account_days", + "Operator": "lte", + "Value": 180, + "Provider": "gcp" + }, + { + "Check": "iam_service_account_unused", + "ConfigKey": "max_unused_account_days", + "Operator": "lte", + "Value": 180, + "Provider": "gcp" + }, + { + "Check": "iam_user_accesskey_unused", + "ConfigKey": "max_unused_access_keys_days", + "Operator": "lte", + "Value": 45, + "Provider": "aws" + }, + { + "Check": "iam_user_console_access_unused", + "ConfigKey": "max_console_access_days", + "Operator": "lte", + "Value": 45, + "Provider": "aws" + }, + { + "Check": "ram_user_console_access_unused", + "ConfigKey": "max_console_access_days", + "Operator": "lte", + "Value": 45, + "Provider": "alibabacloud" + } + ] }, { "id": "IAM-04", @@ -4385,7 +4491,16 @@ "identity_user_customer_secret_keys_rotated_90_days", "identity_user_db_passwords_rotated_90_days" ] - } + }, + "config_requirements": [ + { + "Check": "secretsmanager_secret_unused", + "ConfigKey": "max_days_secret_unused", + "Operator": "lte", + "Value": 90, + "Provider": "aws" + } + ] }, { "id": "IAM-09", diff --git a/prowler/compliance/dora_2022_2554.json b/prowler/compliance/dora_2022_2554.json index 3b828059da0..faa006275cb 100644 --- a/prowler/compliance/dora_2022_2554.json +++ b/prowler/compliance/dora_2022_2554.json @@ -161,7 +161,23 @@ "ram_policy_attached_only_to_group_or_roles", "ram_policy_no_administrative_privileges" ] - } + }, + "config_requirements": [ + { + "Check": "iam_service_account_unused", + "ConfigKey": "max_unused_account_days", + "Operator": "lte", + "Value": 180, + "Provider": "gcp" + }, + { + "Check": "ram_user_console_access_unused", + "ConfigKey": "max_console_access_days", + "Operator": "lte", + "Value": 45, + "Provider": "alibabacloud" + } + ] }, { "id": "DORA-Art6", @@ -413,6 +429,20 @@ "ConfigKey": "mute_non_default_regions", "Operator": "eq", "Value": false + }, + { + "Check": "iam_sa_user_managed_key_unused", + "ConfigKey": "max_unused_account_days", + "Operator": "lte", + "Value": 180, + "Provider": "gcp" + }, + { + "Check": "secretsmanager_secret_unused", + "ConfigKey": "max_days_secret_unused", + "Operator": "lte", + "Value": 90, + "Provider": "aws" } ] }, @@ -665,6 +695,34 @@ "ConfigKey": "mute_non_default_regions", "Operator": "eq", "Value": false + }, + { + "Check": "apim_threat_detection_llm_jacking", + "ConfigKey": "apim_threat_detection_llm_jacking_threshold", + "Operator": "lte", + "Value": 0.1, + "Provider": "azure" + }, + { + "Check": "cloudtrail_threat_detection_enumeration", + "ConfigKey": "threat_detection_enumeration_threshold", + "Operator": "lte", + "Value": 0.3, + "Provider": "aws" + }, + { + "Check": "cloudtrail_threat_detection_llm_jacking", + "ConfigKey": "threat_detection_llm_jacking_threshold", + "Operator": "lte", + "Value": 0.4, + "Provider": "aws" + }, + { + "Check": "cloudtrail_threat_detection_privilege_escalation", + "ConfigKey": "threat_detection_privilege_escalation_threshold", + "Operator": "lte", + "Value": 0.2, + "Provider": "aws" } ] }, @@ -714,7 +772,23 @@ "cs_kubernetes_cluster_check_recent", "cs_kubernetes_cluster_check_weekly" ] - } + }, + "config_requirements": [ + { + "Check": "cs_kubernetes_cluster_check_recent", + "ConfigKey": "max_cluster_check_days", + "Operator": "lte", + "Value": 7, + "Provider": "alibabacloud" + }, + { + "Check": "cs_kubernetes_cluster_check_weekly", + "ConfigKey": "max_cluster_check_days", + "Operator": "lte", + "Value": 7, + "Provider": "alibabacloud" + } + ] }, { "id": "DORA-Art12", @@ -778,7 +852,23 @@ "compute_snapshot_not_outdated", "compute_instance_suspended_without_persistent_disks" ] - } + }, + "config_requirements": [ + { + "Check": "cloudstorage_bucket_sufficient_retention_period", + "ConfigKey": "storage_min_retention_days", + "Operator": "gte", + "Value": 90, + "Provider": "gcp" + }, + { + "Check": "compute_snapshot_not_outdated", + "ConfigKey": "max_snapshot_age_days", + "Operator": "lte", + "Value": 90, + "Provider": "gcp" + } + ] }, { "id": "DORA-Art13", @@ -953,7 +1043,30 @@ "rds_instance_postgresql_log_disconnections_enabled", "rds_instance_postgresql_log_duration_enabled" ] - } + }, + "config_requirements": [ + { + "Check": "cloudwatch_log_group_retention_policy_specific_days_enabled", + "ConfigKey": "log_group_retention_days", + "Operator": "gte", + "Value": 365, + "Provider": "aws" + }, + { + "Check": "rds_instance_sql_audit_retention", + "ConfigKey": "min_rds_audit_retention_days", + "Operator": "gte", + "Value": 180, + "Provider": "alibabacloud" + }, + { + "Check": "sls_logstore_retention_period", + "ConfigKey": "min_log_retention_days", + "Operator": "gte", + "Value": 365, + "Provider": "alibabacloud" + } + ] }, { "id": "DORA-Art18", diff --git a/prowler/compliance/gcp/c5_gcp.json b/prowler/compliance/gcp/c5_gcp.json index e9d8b15b176..0d2f43ed01f 100644 --- a/prowler/compliance/gcp/c5_gcp.json +++ b/prowler/compliance/gcp/c5_gcp.json @@ -80,6 +80,14 @@ "iam_role_kms_enforce_separation_of_duties", "iam_service_account_unused", "iam_sa_no_user_managed_keys" + ], + "ConfigRequirements": [ + { + "Check": "iam_service_account_unused", + "ConfigKey": "max_unused_account_days", + "Operator": "lte", + "Value": 180 + } ] }, { @@ -2717,6 +2725,14 @@ "iam_sa_user_managed_key_unused", "iam_service_account_unused", "iam_sa_no_administrative_privileges" + ], + "ConfigRequirements": [ + { + "Check": "iam_sa_user_managed_key_unused", + "ConfigKey": "max_unused_account_days", + "Operator": "lte", + "Value": 180 + } ] }, { diff --git a/prowler/compliance/gcp/fedramp_20x_ksi_low_gcp.json b/prowler/compliance/gcp/fedramp_20x_ksi_low_gcp.json index 5638b0c51e9..a7df7403655 100644 --- a/prowler/compliance/gcp/fedramp_20x_ksi_low_gcp.json +++ b/prowler/compliance/gcp/fedramp_20x_ksi_low_gcp.json @@ -87,6 +87,20 @@ "iam_sa_user_managed_key_rotate_90_days", "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": 180 + }, + { + "Check": "iam_service_account_unused", + "ConfigKey": "max_unused_account_days", + "Operator": "lte", + "Value": 180 + } ] }, { diff --git a/prowler/compliance/gcp/hipaa_gcp.json b/prowler/compliance/gcp/hipaa_gcp.json index 37d9838e591..9794eae1c22 100644 --- a/prowler/compliance/gcp/hipaa_gcp.json +++ b/prowler/compliance/gcp/hipaa_gcp.json @@ -96,6 +96,14 @@ "iam_role_sa_enforce_separation_of_duties", "iam_sa_no_user_managed_keys", "iam_sa_user_managed_key_unused" + ], + "ConfigRequirements": [ + { + "Check": "iam_sa_user_managed_key_unused", + "ConfigKey": "max_unused_account_days", + "Operator": "lte", + "Value": 180 + } ] }, { diff --git a/prowler/compliance/gcp/mitre_attack_gcp.json b/prowler/compliance/gcp/mitre_attack_gcp.json index 22133caca80..274667f4cc4 100644 --- a/prowler/compliance/gcp/mitre_attack_gcp.json +++ b/prowler/compliance/gcp/mitre_attack_gcp.json @@ -290,6 +290,20 @@ "Value": "Significant", "Comment": "This control is able to mitigate against abuse of compromised valid accounts by restricting access from those accounts to resources contained within the VPC perimeter the account belongs to. Resources and services contained in other VPC networks also cannot be accessed by user accounts that are not within the VPC network perimeter." } + ], + "ConfigRequirements": [ + { + "Check": "iam_sa_user_managed_key_unused", + "ConfigKey": "max_unused_account_days", + "Operator": "lte", + "Value": 180 + }, + { + "Check": "iam_service_account_unused", + "ConfigKey": "max_unused_account_days", + "Operator": "lte", + "Value": 180 + } ] }, { diff --git a/prowler/compliance/gcp/nis2_gcp.json b/prowler/compliance/gcp/nis2_gcp.json index 14d5477dc0d..7d72bc931e9 100644 --- a/prowler/compliance/gcp/nis2_gcp.json +++ b/prowler/compliance/gcp/nis2_gcp.json @@ -704,6 +704,20 @@ "SubSection": "6.2 Secure development life cycle", "Service": "generic" } + ], + "ConfigRequirements": [ + { + "Check": "iam_sa_user_managed_key_unused", + "ConfigKey": "max_unused_account_days", + "Operator": "lte", + "Value": 180 + }, + { + "Check": "iam_service_account_unused", + "ConfigKey": "max_unused_account_days", + "Operator": "lte", + "Value": 180 + } ] }, { diff --git a/prowler/compliance/gcp/rbi_cyber_security_framework_gcp.json b/prowler/compliance/gcp/rbi_cyber_security_framework_gcp.json index e86aee6e632..7482ada1bcc 100644 --- a/prowler/compliance/gcp/rbi_cyber_security_framework_gcp.json +++ b/prowler/compliance/gcp/rbi_cyber_security_framework_gcp.json @@ -172,6 +172,14 @@ "cloudstorage_bucket_soft_delete_enabled", "cloudstorage_bucket_lifecycle_management_enabled", "compute_snapshot_not_outdated" + ], + "ConfigRequirements": [ + { + "Check": "compute_snapshot_not_outdated", + "ConfigKey": "max_snapshot_age_days", + "Operator": "lte", + "Value": 90 + } ] } ] diff --git a/prowler/compliance/gcp/secnumcloud_3.2_gcp.json b/prowler/compliance/gcp/secnumcloud_3.2_gcp.json index e709545da43..b5d9cd65d83 100644 --- a/prowler/compliance/gcp/secnumcloud_3.2_gcp.json +++ b/prowler/compliance/gcp/secnumcloud_3.2_gcp.json @@ -292,6 +292,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": 180 + }, + { + "Check": "iam_service_account_unused", + "ConfigKey": "max_unused_account_days", + "Operator": "lte", + "Value": 180 + } ] }, { @@ -751,6 +765,14 @@ "cloudsql_instance_postgres_log_statement_flag", "cloudsql_instance_postgres_log_min_error_statement_flag", "cloudsql_instance_postgres_log_min_messages_flag" + ], + "ConfigRequirements": [ + { + "Check": "cloudstorage_bucket_sufficient_retention_period", + "ConfigKey": "storage_min_retention_days", + "Operator": "gte", + "Value": 90 + } ] }, { diff --git a/prowler/compliance/gcp/soc2_gcp.json b/prowler/compliance/gcp/soc2_gcp.json index 8450967e298..1ae24c004ef 100644 --- a/prowler/compliance/gcp/soc2_gcp.json +++ b/prowler/compliance/gcp/soc2_gcp.json @@ -27,6 +27,14 @@ "iam_sa_no_administrative_privileges", "iam_sa_no_user_managed_keys", "iam_sa_user_managed_key_unused" + ], + "ConfigRequirements": [ + { + "Check": "iam_sa_user_managed_key_unused", + "ConfigKey": "max_unused_account_days", + "Operator": "lte", + "Value": 180 + } ] }, { diff --git a/prowler/compliance/github/cis_1.0_github.json b/prowler/compliance/github/cis_1.0_github.json index 2a60df6bcd3..733fca9b5bd 100644 --- a/prowler/compliance/github/cis_1.0_github.json +++ b/prowler/compliance/github/cis_1.0_github.json @@ -182,6 +182,14 @@ "References": "", "DefaultValue": "By default, newly opened Git branches would never be removed, regardless of activity or inactivity." } + ], + "ConfigRequirements": [ + { + "Check": "repository_inactive_not_archived", + "ConfigKey": "inactive_not_archived_days_threshold", + "Operator": "lte", + "Value": 180 + } ] }, { diff --git a/prowler/compliance/kubernetes/cis_1.10_kubernetes.json b/prowler/compliance/kubernetes/cis_1.10_kubernetes.json index 6fea9c2291c..ce6fe883002 100644 --- a/prowler/compliance/kubernetes/cis_1.10_kubernetes.json +++ b/prowler/compliance/kubernetes/cis_1.10_kubernetes.json @@ -866,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 + } ] }, { @@ -889,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 + } ] }, { diff --git a/prowler/compliance/kubernetes/cis_1.11_kubernetes.json b/prowler/compliance/kubernetes/cis_1.11_kubernetes.json index b14cc1f9499..89b0a6d8a3f 100644 --- a/prowler/compliance/kubernetes/cis_1.11_kubernetes.json +++ b/prowler/compliance/kubernetes/cis_1.11_kubernetes.json @@ -866,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 + } ] }, { @@ -889,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 + } ] }, { diff --git a/prowler/compliance/kubernetes/cis_1.12_kubernetes.json b/prowler/compliance/kubernetes/cis_1.12_kubernetes.json index 292b1085ce2..494eb09597f 100644 --- a/prowler/compliance/kubernetes/cis_1.12_kubernetes.json +++ b/prowler/compliance/kubernetes/cis_1.12_kubernetes.json @@ -866,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 + } ] }, { @@ -889,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 + } ] }, { diff --git a/prowler/compliance/kubernetes/cis_1.8_kubernetes.json b/prowler/compliance/kubernetes/cis_1.8_kubernetes.json index f3762757aaa..ebabce6a984 100644 --- a/prowler/compliance/kubernetes/cis_1.8_kubernetes.json +++ b/prowler/compliance/kubernetes/cis_1.8_kubernetes.json @@ -889,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 + } ] }, { @@ -912,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 + } ] }, { diff --git a/prowler/compliance/kubernetes/iso27001_2022_kubernetes.json b/prowler/compliance/kubernetes/iso27001_2022_kubernetes.json index 73f1b1266b3..e6b02b4425a 100644 --- a/prowler/compliance/kubernetes/iso27001_2022_kubernetes.json +++ b/prowler/compliance/kubernetes/iso27001_2022_kubernetes.json @@ -1109,6 +1109,26 @@ "apiserver_audit_log_maxbackup_set", "apiserver_audit_log_maxsize_set", "apiserver_audit_log_path_set" + ], + "ConfigRequirements": [ + { + "Check": "apiserver_audit_log_maxage_set", + "ConfigKey": "audit_log_maxage", + "Operator": "gte", + "Value": 30 + }, + { + "Check": "apiserver_audit_log_maxbackup_set", + "ConfigKey": "audit_log_maxbackup", + "Operator": "gte", + "Value": 10 + }, + { + "Check": "apiserver_audit_log_maxsize_set", + "ConfigKey": "audit_log_maxsize", + "Operator": "gte", + "Value": 100 + } ] }, { diff --git a/prowler/compliance/kubernetes/prowler_threatscore_kubernetes.json b/prowler/compliance/kubernetes/prowler_threatscore_kubernetes.json index b8b6a606656..50b2bd49358 100644 --- a/prowler/compliance/kubernetes/prowler_threatscore_kubernetes.json +++ b/prowler/compliance/kubernetes/prowler_threatscore_kubernetes.json @@ -1235,6 +1235,14 @@ "LevelOfRisk": 3, "Weight": 10 } + ], + "ConfigRequirements": [ + { + "Check": "apiserver_audit_log_maxbackup_set", + "ConfigKey": "audit_log_maxbackup", + "Operator": "gte", + "Value": 10 + } ] }, { @@ -1253,6 +1261,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..be495f6dcfd 100644 --- a/prowler/compliance/m365/cis_4.0_m365.json +++ b/prowler/compliance/m365/cis_4.0_m365.json @@ -1812,6 +1812,14 @@ "References": "https://learn.microsoft.com/en-us/exchange/clients-and-mobile-in-exchange-online/mailtips/mailtips:https://learn.microsoft.com/en-us/powershell/module/exchange/set-organizationconfig?view=exchange-ps", "DefaultValue": "MailTipsAllTipsEnabled: TrueMailTipsExternalRecipientsTipsEnabled: FalseMailTipsGroupMetricsEnabled: TrueMailTipsLargeAudienceThreshold: 25" } + ], + "ConfigRequirements": [ + { + "Check": "exchange_organization_mailtips_enabled", + "ConfigKey": "recommended_mailtips_large_audience_threshold", + "Operator": "lte", + "Value": 25 + } ] }, { diff --git a/prowler/compliance/m365/cis_6.0_m365.json b/prowler/compliance/m365/cis_6.0_m365.json index d0dcfb2e6d5..8e03865e15f 100644 --- a/prowler/compliance/m365/cis_6.0_m365.json +++ b/prowler/compliance/m365/cis_6.0_m365.json @@ -2110,6 +2110,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..9098f7a017b 100644 --- a/prowler/compliance/m365/prowler_threatscore_m365.json +++ b/prowler/compliance/m365/prowler_threatscore_m365.json @@ -529,6 +529,14 @@ "LevelOfRisk": 2, "Weight": 8 } + ], + "ConfigRequirements": [ + { + "Check": "exchange_organization_mailtips_enabled", + "ConfigKey": "recommended_mailtips_large_audience_threshold", + "Operator": "lte", + "Value": 25 + } ] }, { diff --git a/prowler/compliance/okta/okta_idaas_stig_v1r2_okta.json b/prowler/compliance/okta/okta_idaas_stig_v1r2_okta.json index bac3d9132e6..0d1090eb57e 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 + } ] }, { diff --git a/tests/lib/check/compliance_config_requirements_coverage_baseline.json b/tests/lib/check/compliance_config_requirements_coverage_baseline.json new file mode 100644 index 00000000000..4c6afd1c418 --- /dev/null +++ b/tests/lib/check/compliance_config_requirements_coverage_baseline.json @@ -0,0 +1,331 @@ +[ + "asd_essential_eight_aws.json::awslambda_function_using_supported_runtimes", + "asd_essential_eight_aws.json::ecs_service_fargate_latest_platform_version", + "asd_essential_eight_aws.json::eks_cluster_uses_a_supported_version", + "asd_essential_eight_aws.json::rds_instance_backup_enabled", + "aws_account_security_onboarding_aws.json::ecr_repositories_scan_vulnerabilities_in_latest_image", + "aws_account_security_onboarding_aws.json::organizations_scp_check_deny_regions", + "aws_ai_security_framework_aws.json::awslambda_function_using_supported_runtimes", + "aws_ai_security_framework_aws.json::eks_cluster_uses_a_supported_version", + "aws_ai_security_framework_aws.json::eks_control_plane_logging_all_types_enabled", + "aws_ai_security_framework_aws.json::organizations_delegated_administrators", + "aws_ai_security_framework_aws.json::organizations_scp_check_deny_regions", + "aws_ai_security_framework_aws.json::s3_bucket_cross_account_access", + "aws_ai_security_framework_aws.json::secretsmanager_has_restrictive_resource_policy", + "aws_ai_security_framework_aws.json::vpc_endpoint_connections_trust_boundaries", + "aws_ai_security_framework_aws.json::vpc_endpoint_services_allowed_principals_trust_boundaries", + "aws_foundational_security_best_practices_aws.json::awslambda_function_using_supported_runtimes", + "aws_foundational_security_best_practices_aws.json::awslambda_function_vpc_multi_az", + "aws_foundational_security_best_practices_aws.json::codebuild_project_no_secrets_in_variables", + "aws_foundational_security_best_practices_aws.json::ec2_securitygroup_allow_ingress_from_internet_to_high_risk_tcp_ports", + "aws_foundational_security_best_practices_aws.json::eks_cluster_uses_a_supported_version", + "aws_foundational_security_best_practices_aws.json::eks_control_plane_logging_all_types_enabled", + "aws_foundational_security_best_practices_aws.json::elb_is_in_multiple_az", + "aws_foundational_security_best_practices_aws.json::elbv2_is_in_multiple_az", + "aws_foundational_security_best_practices_aws.json::opensearch_service_domains_not_publicly_accessible", + "aws_foundational_security_best_practices_aws.json::rds_instance_backup_enabled", + "aws_foundational_security_best_practices_aws.json::s3_bucket_cross_account_access", + "aws_foundational_security_best_practices_aws.json::ssm_documents_set_as_public", + "aws_foundational_technical_review_aws.json::rds_instance_backup_enabled", + "aws_foundational_technical_review_aws.json::vpc_endpoint_connections_trust_boundaries", + "aws_foundational_technical_review_aws.json::vpc_endpoint_services_allowed_principals_trust_boundaries", + "aws_well_architected_framework_reliability_pillar_aws.json::rds_instance_backup_enabled", + "aws_well_architected_framework_security_pillar_aws.json::apigateway_domain_name_pqc_tls_enabled", + "aws_well_architected_framework_security_pillar_aws.json::cloudfront_distributions_pqc_tls_enabled", + "aws_well_architected_framework_security_pillar_aws.json::ecr_repositories_scan_vulnerabilities_in_latest_image", + "aws_well_architected_framework_security_pillar_aws.json::eks_control_plane_logging_all_types_enabled", + "aws_well_architected_framework_security_pillar_aws.json::opensearch_service_domains_not_publicly_accessible", + "aws_well_architected_framework_security_pillar_aws.json::ssm_documents_set_as_public", + "aws_well_architected_framework_security_pillar_aws.json::transfer_server_pqc_ssh_kex_enabled", + "aws_well_architected_framework_security_pillar_aws.json::vpc_endpoint_connections_trust_boundaries", + "aws_well_architected_framework_security_pillar_aws.json::vpc_endpoint_services_allowed_principals_trust_boundaries", + "c5_aws.json::codebuild_project_no_secrets_in_variables", + "c5_aws.json::dynamodb_table_cross_account_access", + "c5_aws.json::ec2_securitygroup_allow_ingress_from_internet_to_any_port", + "c5_aws.json::ec2_securitygroup_allow_ingress_from_internet_to_high_risk_tcp_ports", + "c5_aws.json::ecr_repositories_scan_vulnerabilities_in_latest_image", + "c5_aws.json::eks_control_plane_logging_all_types_enabled", + "c5_aws.json::eventbridge_bus_cross_account_access", + "c5_aws.json::eventbridge_schema_registry_cross_account_access", + "c5_aws.json::opensearch_service_domains_not_publicly_accessible", + "c5_aws.json::organizations_delegated_administrators", + "c5_aws.json::organizations_scp_check_deny_regions", + "c5_aws.json::rds_instance_backup_enabled", + "c5_aws.json::s3_bucket_cross_account_access", + "c5_aws.json::ssm_documents_set_as_public", + "c5_aws.json::trustedadvisor_premium_support_plan_subscribed", + "c5_aws.json::vpc_endpoint_connections_trust_boundaries", + "c5_azure.json::app_ensure_java_version_is_latest", + "c5_azure.json::app_ensure_php_version_is_latest", + "c5_azure.json::app_ensure_python_version_is_latest", + "c5_azure.json::defender_attack_path_notifications_properly_configured", + "ccc_aws.json::apigateway_domain_name_pqc_tls_enabled", + "ccc_aws.json::cloudfront_distributions_pqc_tls_enabled", + "ccc_aws.json::codebuild_project_no_secrets_in_variables", + "ccc_aws.json::codebuild_project_uses_allowed_github_organizations", + "ccc_aws.json::ecr_repositories_scan_vulnerabilities_in_latest_image", + "ccc_aws.json::eventbridge_bus_cross_account_access", + "ccc_aws.json::eventbridge_schema_registry_cross_account_access", + "ccc_aws.json::organizations_scp_check_deny_regions", + "ccc_aws.json::rds_instance_backup_enabled", + "ccc_aws.json::s3_bucket_cross_account_access", + "ccc_aws.json::transfer_server_pqc_ssh_kex_enabled", + "ccc_aws.json::vpc_endpoint_connections_trust_boundaries", + "ccc_aws.json::vpc_endpoint_services_allowed_principals_trust_boundaries", + "cis_1.10_kubernetes.json::apiserver_strong_ciphers_only", + "cis_1.10_kubernetes.json::kubelet_strong_ciphers_only", + "cis_1.11_kubernetes.json::apiserver_strong_ciphers_only", + "cis_1.11_kubernetes.json::kubelet_strong_ciphers_only", + "cis_1.12_kubernetes.json::apiserver_strong_ciphers_only", + "cis_1.12_kubernetes.json::kubelet_strong_ciphers_only", + "cis_1.8_kubernetes.json::apiserver_strong_ciphers_only", + "cis_1.8_kubernetes.json::kubelet_strong_ciphers_only", + "cis_2.0_azure.json::app_ensure_java_version_is_latest", + "cis_2.0_azure.json::app_ensure_php_version_is_latest", + "cis_2.0_azure.json::app_ensure_python_version_is_latest", + "cis_2.1_azure.json::app_ensure_java_version_is_latest", + "cis_2.1_azure.json::app_ensure_php_version_is_latest", + "cis_2.1_azure.json::app_ensure_python_version_is_latest", + "cis_3.0_azure.json::app_ensure_java_version_is_latest", + "cis_3.0_azure.json::app_ensure_php_version_is_latest", + "cis_3.0_azure.json::app_ensure_python_version_is_latest", + "cis_4.0_azure.json::defender_attack_path_notifications_properly_configured", + "cis_4.0_m365.json::defender_malware_policy_comprehensive_attachments_filter_applied", + "cis_4.0_m365.json::entra_admin_users_sign_in_frequency_enabled", + "cis_4.0_m365.json::exchange_user_mailbox_auditing_enabled", + "cis_4.0_m365.json::teams_external_file_sharing_restricted", + "cis_5.0_azure.json::defender_attack_path_notifications_properly_configured", + "cis_6.0_m365.json::defender_malware_policy_comprehensive_attachments_filter_applied", + "cis_6.0_m365.json::exchange_user_mailbox_auditing_enabled", + "cis_6.0_m365.json::teams_external_file_sharing_restricted", + "cisa_aws.json::rds_instance_backup_enabled", + "cisa_aws.json::vpc_endpoint_connections_trust_boundaries", + "csa_ccm_4.0.json::app_ensure_java_version_is_latest", + "csa_ccm_4.0.json::app_ensure_php_version_is_latest", + "csa_ccm_4.0.json::app_ensure_python_version_is_latest", + "csa_ccm_4.0.json::awslambda_function_vpc_multi_az", + "csa_ccm_4.0.json::codebuild_project_no_secrets_in_variables", + "csa_ccm_4.0.json::compute_instance_group_multiple_zones", + "csa_ccm_4.0.json::defender_attack_path_notifications_properly_configured", + "csa_ccm_4.0.json::ec2_securitygroup_allow_ingress_from_internet_to_any_port", + "csa_ccm_4.0.json::ec2_securitygroup_allow_ingress_from_internet_to_high_risk_tcp_ports", + "csa_ccm_4.0.json::ecr_repositories_scan_vulnerabilities_in_latest_image", + "csa_ccm_4.0.json::eks_control_plane_logging_all_types_enabled", + "csa_ccm_4.0.json::elb_is_in_multiple_az", + "csa_ccm_4.0.json::elbv2_is_in_multiple_az", + "csa_ccm_4.0.json::rds_instance_backup_enabled", + "csa_ccm_4.0.json::transfer_server_pqc_ssh_kex_enabled", + "dora_2022_2554.json::app_ensure_java_version_is_latest", + "dora_2022_2554.json::app_ensure_php_version_is_latest", + "dora_2022_2554.json::app_ensure_python_version_is_latest", + "dora_2022_2554.json::compute_instance_group_multiple_zones", + "dora_2022_2554.json::defender_attack_path_notifications_properly_configured", + "dora_2022_2554.json::dynamodb_table_cross_account_access", + "dora_2022_2554.json::ec2_securitygroup_allow_ingress_from_internet_to_any_port", + "dora_2022_2554.json::ec2_securitygroup_allow_ingress_from_internet_to_high_risk_tcp_ports", + "dora_2022_2554.json::elb_is_in_multiple_az", + "dora_2022_2554.json::elbv2_is_in_multiple_az", + "dora_2022_2554.json::eventbridge_bus_cross_account_access", + "dora_2022_2554.json::eventbridge_schema_registry_cross_account_access", + "dora_2022_2554.json::organizations_delegated_administrators", + "dora_2022_2554.json::organizations_scp_check_deny_regions", + "dora_2022_2554.json::rds_instance_backup_enabled", + "dora_2022_2554.json::s3_bucket_cross_account_access", + "dora_2022_2554.json::secretsmanager_has_restrictive_resource_policy", + "dora_2022_2554.json::vm_desired_sku_size", + "dora_2022_2554.json::vpc_endpoint_connections_trust_boundaries", + "dora_2022_2554.json::vpc_endpoint_services_allowed_principals_trust_boundaries", + "ens_rd2022_aws.json::apigateway_domain_name_pqc_tls_enabled", + "ens_rd2022_aws.json::cloudfront_distributions_pqc_tls_enabled", + "ens_rd2022_aws.json::organizations_scp_check_deny_regions", + "ens_rd2022_aws.json::transfer_server_pqc_ssh_kex_enabled", + "fedramp_20x_ksi_low_aws.json::awslambda_function_using_supported_runtimes", + "fedramp_20x_ksi_low_aws.json::ec2_securitygroup_allow_ingress_from_internet_to_any_port", + "fedramp_20x_ksi_low_aws.json::ecr_repositories_scan_vulnerabilities_in_latest_image", + "fedramp_20x_ksi_low_aws.json::eks_cluster_uses_a_supported_version", + "fedramp_20x_ksi_low_aws.json::organizations_delegated_administrators", + "fedramp_20x_ksi_low_aws.json::organizations_scp_check_deny_regions", + "fedramp_20x_ksi_low_aws.json::rds_instance_backup_enabled", + "fedramp_20x_ksi_low_aws.json::trustedadvisor_premium_support_plan_subscribed", + "fedramp_20x_ksi_low_azure.json::app_ensure_java_version_is_latest", + "fedramp_20x_ksi_low_azure.json::app_ensure_php_version_is_latest", + "fedramp_20x_ksi_low_azure.json::app_ensure_python_version_is_latest", + "fedramp_20x_ksi_low_azure.json::defender_attack_path_notifications_properly_configured", + "fedramp_low_revision_4_aws.json::rds_instance_backup_enabled", + "fedramp_moderate_revision_4_aws.json::apigateway_domain_name_pqc_tls_enabled", + "fedramp_moderate_revision_4_aws.json::cloudfront_distributions_pqc_tls_enabled", + "fedramp_moderate_revision_4_aws.json::rds_instance_backup_enabled", + "fedramp_moderate_revision_4_aws.json::transfer_server_pqc_ssh_kex_enabled", + "ffiec_aws.json::apigateway_domain_name_pqc_tls_enabled", + "ffiec_aws.json::cloudfront_distributions_pqc_tls_enabled", + "ffiec_aws.json::rds_instance_backup_enabled", + "ffiec_aws.json::transfer_server_pqc_ssh_kex_enabled", + "gdpr_aws.json::rds_instance_backup_enabled", + "gxp_21_cfr_part_11_aws.json::apigateway_domain_name_pqc_tls_enabled", + "gxp_21_cfr_part_11_aws.json::cloudfront_distributions_pqc_tls_enabled", + "gxp_21_cfr_part_11_aws.json::rds_instance_backup_enabled", + "gxp_21_cfr_part_11_aws.json::transfer_server_pqc_ssh_kex_enabled", + "gxp_eu_annex_11_aws.json::rds_instance_backup_enabled", + "hipaa_aws.json::rds_instance_backup_enabled", + "hipaa_azure.json::defender_attack_path_notifications_properly_configured", + "iso27001_2013_aws.json::apigateway_domain_name_pqc_tls_enabled", + "iso27001_2013_aws.json::cloudfront_distributions_pqc_tls_enabled", + "iso27001_2013_aws.json::transfer_server_pqc_ssh_kex_enabled", + "iso27001_2022_aws.json::awslambda_function_vpc_multi_az", + "iso27001_2022_aws.json::ec2_securitygroup_allow_ingress_from_internet_to_any_port", + "iso27001_2022_aws.json::ec2_securitygroup_allow_ingress_from_internet_to_high_risk_tcp_ports", + "iso27001_2022_aws.json::eks_control_plane_logging_all_types_enabled", + "iso27001_2022_aws.json::elb_is_in_multiple_az", + "iso27001_2022_aws.json::elbv2_is_in_multiple_az", + "iso27001_2022_aws.json::opensearch_service_domains_not_publicly_accessible", + "iso27001_2022_aws.json::ssm_documents_set_as_public", + "iso27001_2022_aws.json::vpc_endpoint_connections_trust_boundaries", + "iso27001_2022_aws.json::vpc_endpoint_services_allowed_principals_trust_boundaries", + "iso27001_2022_kubernetes.json::apiserver_strong_ciphers_only", + "iso27001_2022_kubernetes.json::kubelet_strong_ciphers_only", + "iso27001_2022_m365.json::defender_malware_policy_comprehensive_attachments_filter_applied", + "iso27001_2022_m365.json::entra_admin_users_sign_in_frequency_enabled", + "iso27001_2022_m365.json::exchange_user_mailbox_auditing_enabled", + "iso27001_2022_m365.json::teams_external_file_sharing_restricted", + "kisa_isms_p_2023_aws.json::apigateway_domain_name_pqc_tls_enabled", + "kisa_isms_p_2023_aws.json::awslambda_function_using_supported_runtimes", + "kisa_isms_p_2023_aws.json::awslambda_function_vpc_multi_az", + "kisa_isms_p_2023_aws.json::cloudfront_distributions_pqc_tls_enabled", + "kisa_isms_p_2023_aws.json::codebuild_project_no_secrets_in_variables", + "kisa_isms_p_2023_aws.json::dynamodb_table_cross_account_access", + "kisa_isms_p_2023_aws.json::ec2_securitygroup_allow_ingress_from_internet_to_any_port", + "kisa_isms_p_2023_aws.json::ec2_securitygroup_allow_ingress_from_internet_to_high_risk_tcp_ports", + "kisa_isms_p_2023_aws.json::ecr_repositories_scan_vulnerabilities_in_latest_image", + "kisa_isms_p_2023_aws.json::ecs_service_fargate_latest_platform_version", + "kisa_isms_p_2023_aws.json::eks_cluster_uses_a_supported_version", + "kisa_isms_p_2023_aws.json::eks_control_plane_logging_all_types_enabled", + "kisa_isms_p_2023_aws.json::elb_is_in_multiple_az", + "kisa_isms_p_2023_aws.json::elbv2_is_in_multiple_az", + "kisa_isms_p_2023_aws.json::eventbridge_bus_cross_account_access", + "kisa_isms_p_2023_aws.json::eventbridge_schema_registry_cross_account_access", + "kisa_isms_p_2023_aws.json::opensearch_service_domains_not_publicly_accessible", + "kisa_isms_p_2023_aws.json::organizations_delegated_administrators", + "kisa_isms_p_2023_aws.json::organizations_scp_check_deny_regions", + "kisa_isms_p_2023_aws.json::rds_instance_backup_enabled", + "kisa_isms_p_2023_aws.json::s3_bucket_cross_account_access", + "kisa_isms_p_2023_aws.json::ssm_documents_set_as_public", + "kisa_isms_p_2023_aws.json::transfer_server_pqc_ssh_kex_enabled", + "kisa_isms_p_2023_aws.json::vpc_endpoint_connections_trust_boundaries", + "kisa_isms_p_2023_aws.json::vpc_endpoint_services_allowed_principals_trust_boundaries", + "kisa_isms_p_2023_korean_aws.json::apigateway_domain_name_pqc_tls_enabled", + "kisa_isms_p_2023_korean_aws.json::awslambda_function_using_supported_runtimes", + "kisa_isms_p_2023_korean_aws.json::awslambda_function_vpc_multi_az", + "kisa_isms_p_2023_korean_aws.json::cloudfront_distributions_pqc_tls_enabled", + "kisa_isms_p_2023_korean_aws.json::codebuild_project_no_secrets_in_variables", + "kisa_isms_p_2023_korean_aws.json::dynamodb_table_cross_account_access", + "kisa_isms_p_2023_korean_aws.json::ec2_securitygroup_allow_ingress_from_internet_to_any_port", + "kisa_isms_p_2023_korean_aws.json::ec2_securitygroup_allow_ingress_from_internet_to_high_risk_tcp_ports", + "kisa_isms_p_2023_korean_aws.json::ecr_repositories_scan_vulnerabilities_in_latest_image", + "kisa_isms_p_2023_korean_aws.json::ecs_service_fargate_latest_platform_version", + "kisa_isms_p_2023_korean_aws.json::eks_cluster_uses_a_supported_version", + "kisa_isms_p_2023_korean_aws.json::eks_control_plane_logging_all_types_enabled", + "kisa_isms_p_2023_korean_aws.json::elb_is_in_multiple_az", + "kisa_isms_p_2023_korean_aws.json::elbv2_is_in_multiple_az", + "kisa_isms_p_2023_korean_aws.json::eventbridge_bus_cross_account_access", + "kisa_isms_p_2023_korean_aws.json::eventbridge_schema_registry_cross_account_access", + "kisa_isms_p_2023_korean_aws.json::opensearch_service_domains_not_publicly_accessible", + "kisa_isms_p_2023_korean_aws.json::organizations_delegated_administrators", + "kisa_isms_p_2023_korean_aws.json::organizations_scp_check_deny_regions", + "kisa_isms_p_2023_korean_aws.json::rds_instance_backup_enabled", + "kisa_isms_p_2023_korean_aws.json::s3_bucket_cross_account_access", + "kisa_isms_p_2023_korean_aws.json::ssm_documents_set_as_public", + "kisa_isms_p_2023_korean_aws.json::transfer_server_pqc_ssh_kex_enabled", + "kisa_isms_p_2023_korean_aws.json::vpc_endpoint_connections_trust_boundaries", + "kisa_isms_p_2023_korean_aws.json::vpc_endpoint_services_allowed_principals_trust_boundaries", + "mitre_attack_aws.json::organizations_delegated_administrators", + "mitre_attack_aws.json::organizations_scp_check_deny_regions", + "mitre_attack_aws.json::rds_instance_backup_enabled", + "mitre_attack_azure.json::app_ensure_java_version_is_latest", + "mitre_attack_azure.json::app_ensure_php_version_is_latest", + "mitre_attack_azure.json::app_ensure_python_version_is_latest", + "nis2_aws.json::codebuild_project_no_secrets_in_variables", + "nis2_aws.json::eks_control_plane_logging_all_types_enabled", + "nis2_aws.json::rds_instance_backup_enabled", + "nis2_aws.json::ssm_documents_set_as_public", + "nis2_aws.json::vpc_endpoint_connections_trust_boundaries", + "nis2_aws.json::vpc_endpoint_services_allowed_principals_trust_boundaries", + "nis2_azure.json::app_ensure_java_version_is_latest", + "nis2_azure.json::app_ensure_php_version_is_latest", + "nis2_azure.json::app_ensure_python_version_is_latest", + "nist_800_171_revision_2_aws.json::apigateway_domain_name_pqc_tls_enabled", + "nist_800_171_revision_2_aws.json::cloudfront_distributions_pqc_tls_enabled", + "nist_800_171_revision_2_aws.json::rds_instance_backup_enabled", + "nist_800_171_revision_2_aws.json::transfer_server_pqc_ssh_kex_enabled", + "nist_800_53_revision_4_aws.json::rds_instance_backup_enabled", + "nist_800_53_revision_5_aws.json::apigateway_domain_name_pqc_tls_enabled", + "nist_800_53_revision_5_aws.json::cloudfront_distributions_pqc_tls_enabled", + "nist_800_53_revision_5_aws.json::rds_instance_backup_enabled", + "nist_800_53_revision_5_aws.json::transfer_server_pqc_ssh_kex_enabled", + "nist_csf_1.1_aws.json::rds_instance_backup_enabled", + "nist_csf_2.0_aws.json::codebuild_project_no_secrets_in_variables", + "nist_csf_2.0_aws.json::codebuild_project_uses_allowed_github_organizations", + "nist_csf_2.0_aws.json::ec2_securitygroup_allow_ingress_from_internet_to_high_risk_tcp_ports", + "nist_csf_2.0_aws.json::elb_is_in_multiple_az", + "nist_csf_2.0_aws.json::eventbridge_bus_cross_account_access", + "nist_csf_2.0_aws.json::organizations_delegated_administrators", + "nist_csf_2.0_aws.json::organizations_scp_check_deny_regions", + "nist_csf_2.0_aws.json::rds_instance_backup_enabled", + "nist_csf_2.0_aws.json::s3_bucket_cross_account_access", + "nist_csf_2.0_aws.json::ssm_documents_set_as_public", + "nist_csf_2.0_aws.json::trustedadvisor_premium_support_plan_subscribed", + "nist_csf_2.0_aws.json::vpc_endpoint_connections_trust_boundaries", + "okta_idaas_stig_v1r2_okta.json::idp_smart_card_dod_approved_ca", + "okta_idaas_stig_v1r2_okta.json::signon_global_session_lifetime_18h", + "okta_idaas_stig_v1r2_okta.json::user_inactivity_automation_35d_enabled", + "pci_3.2.1_aws.json::codebuild_project_no_secrets_in_variables", + "pci_3.2.1_aws.json::eks_cluster_uses_a_supported_version", + "pci_3.2.1_aws.json::opensearch_service_domains_not_publicly_accessible", + "pci_3.2.1_aws.json::rds_instance_backup_enabled", + "pci_4.0_aws.json::codebuild_project_no_secrets_in_variables", + "pci_4.0_aws.json::ec2_securitygroup_allow_ingress_from_internet_to_any_port", + "pci_4.0_aws.json::ecs_service_fargate_latest_platform_version", + "pci_4.0_aws.json::eks_cluster_uses_a_supported_version", + "pci_4.0_aws.json::eks_control_plane_logging_all_types_enabled", + "pci_4.0_aws.json::eventbridge_schema_registry_cross_account_access", + "pci_4.0_aws.json::opensearch_service_domains_not_publicly_accessible", + "pci_4.0_aws.json::rds_instance_backup_enabled", + "pci_4.0_aws.json::s3_bucket_cross_account_access", + "pci_4.0_aws.json::ssm_documents_set_as_public", + "pci_4.0_kubernetes.json::kubelet_strong_ciphers_only", + "prowler_threatscore_aws.json::ec2_securitygroup_allow_ingress_from_internet_to_high_risk_tcp_ports", + "prowler_threatscore_aws.json::opensearch_service_domains_not_publicly_accessible", + "prowler_threatscore_aws.json::ssm_documents_set_as_public", + "prowler_threatscore_kubernetes.json::apiserver_strong_ciphers_only", + "prowler_threatscore_kubernetes.json::kubelet_strong_ciphers_only", + "prowler_threatscore_m365.json::defender_malware_policy_comprehensive_attachments_filter_applied", + "prowler_threatscore_m365.json::entra_admin_users_sign_in_frequency_enabled", + "prowler_threatscore_m365.json::exchange_user_mailbox_auditing_enabled", + "prowler_threatscore_m365.json::teams_external_file_sharing_restricted", + "rbi_cyber_security_framework_aws.json::apigateway_domain_name_pqc_tls_enabled", + "rbi_cyber_security_framework_aws.json::cloudfront_distributions_pqc_tls_enabled", + "rbi_cyber_security_framework_aws.json::rds_instance_backup_enabled", + "rbi_cyber_security_framework_aws.json::ssm_documents_set_as_public", + "rbi_cyber_security_framework_aws.json::transfer_server_pqc_ssh_kex_enabled", + "rbi_cyber_security_framework_azure.json::app_ensure_java_version_is_latest", + "rbi_cyber_security_framework_azure.json::app_ensure_php_version_is_latest", + "rbi_cyber_security_framework_azure.json::app_ensure_python_version_is_latest", + "secnumcloud_3.2_aws.json::apigateway_domain_name_pqc_tls_enabled", + "secnumcloud_3.2_aws.json::cloudfront_distributions_pqc_tls_enabled", + "secnumcloud_3.2_aws.json::codebuild_project_no_secrets_in_variables", + "secnumcloud_3.2_aws.json::ec2_securitygroup_allow_ingress_from_internet_to_any_port", + "secnumcloud_3.2_aws.json::ec2_securitygroup_allow_ingress_from_internet_to_high_risk_tcp_ports", + "secnumcloud_3.2_aws.json::ecr_repositories_scan_vulnerabilities_in_latest_image", + "secnumcloud_3.2_aws.json::eks_control_plane_logging_all_types_enabled", + "secnumcloud_3.2_aws.json::elbv2_is_in_multiple_az", + "secnumcloud_3.2_aws.json::organizations_scp_check_deny_regions", + "secnumcloud_3.2_aws.json::rds_instance_backup_enabled", + "secnumcloud_3.2_aws.json::ssm_documents_set_as_public", + "secnumcloud_3.2_aws.json::transfer_server_pqc_ssh_kex_enabled", + "secnumcloud_3.2_aws.json::vpc_endpoint_connections_trust_boundaries", + "secnumcloud_3.2_aws.json::vpc_endpoint_services_allowed_principals_trust_boundaries", + "secnumcloud_3.2_azure.json::defender_attack_path_notifications_properly_configured", + "secnumcloud_3.2_gcp.json::compute_instance_group_multiple_zones", + "soc2_aws.json::ec2_securitygroup_allow_ingress_from_internet_to_any_port", + "soc2_aws.json::ec2_securitygroup_allow_ingress_from_internet_to_high_risk_tcp_ports", + "soc2_aws.json::rds_instance_backup_enabled" +] diff --git a/tests/lib/check/compliance_config_requirements_coverage_test.py b/tests/lib/check/compliance_config_requirements_coverage_test.py new file mode 100644 index 00000000000..836e93bdfb0 --- /dev/null +++ b/tests/lib/check/compliance_config_requirements_coverage_test.py @@ -0,0 +1,298 @@ +"""Coverage test for ``ConfigRequirements`` across the shipped compliance JSONs. + +Sibling of ``compliance_config_requirements_data_test.py``: that file validates +that the constraints which *exist* are well-formed; this one validates that +constraints are *not missing*. + +Invariant (per-framework): + For every compliance framework, if it maps a configurable check whose + configuration can *relax the verdict* (i.e. a looser value makes the check + PASS when the control is not really satisfied), then that framework must + declare a ``ConfigRequirements`` entry for that check in at least one of its + requirements. Otherwise a user could loosen the config and the framework + would silently report the requirement as compliant. + +Why a baseline: + The invariant is currently violated by a large, pre-existing backlog (the + constraints were originally added mostly to the CIS frameworks). Fixing all + of them at once is impractical and needs per-control judgement on the right + threshold value. So the known gaps are frozen in + ``compliance_config_requirements_coverage_baseline.json`` and this test: + - FAILS on any *new* gap not in the baseline (regression guard), and + - FAILS on any baseline entry that is *no longer* a gap (keeps the + baseline shrinking as gaps are fixed — delete the entry when you add + the constraint). + + Regenerate the baseline after intentionally changing the set of gaps with: + python tests/lib/check/compliance_config_requirements_coverage_test.py --update +""" + +import ast +import glob +import json +import os +import pathlib + +_REPO_ROOT = pathlib.Path(__file__).resolve().parents[3] +_COMPLIANCE_DIR = _REPO_ROOT / "prowler" / "compliance" +_SERVICES_GLOB = str(_REPO_ROOT / "prowler" / "providers" / "*" / "services") +_BASELINE_PATH = ( + pathlib.Path(__file__).with_name( + "compliance_config_requirements_coverage_baseline.json" + ) +) + +# Config keys that a check reads but which do NOT relax its verdict, so a +# requirement mapping such a check does not need a ConfigRequirements: +# - shodan_api_key: only enables the lookup; without it the check produces an +# informational/no finding, it never turns a FAIL into a PASS. +# - detect_secrets_plugins / secrets_ignore_patterns: tune the secret-scanning +# engine, not a security policy threshold. +# Keep this list small and explicit; everything else is treated as verdict-affecting. +_KEYS_NOT_AFFECTING_VERDICT = { + "shodan_api_key", + "detect_secrets_plugins", + "secrets_ignore_patterns", +} + +# Config keys whose Pydantic schema bound already pins the value to the default +# in the direction a user would relax it, so the value CANNOT be loosened — a +# ConfigRequirements would be inert (it could never fire). These are exempt from +# the coverage invariant. The value is the relaxation direction ("gte" = a higher +# value is safer, so loosening means going below; "lte" = the opposite). +# ``test_schema_enforced_keys_are_really_pinned`` proves each entry against the +# live schema + config.yaml default, so a schema/default change can't silently +# leave a real gap hidden here. +_KEYS_SCHEMA_ENFORCED = { + "vm_backup_min_daily_retention_days": "gte", # schema ge=7 == default 7 + "days_to_expire_threshold": "gte", # schema ge=7 == default 7 +} + +_EXEMPT_KEYS = _KEYS_NOT_AFFECTING_VERDICT | set(_KEYS_SCHEMA_ENFORCED) + + +def _refers_audit_config(base: ast.AST) -> bool: + """True when ``base`` is an expression referring to a provider audit_config.""" + if isinstance(base, ast.Attribute) and base.attr == "audit_config": + return True + if isinstance(base, ast.Name) and base.id == "audit_config": + return True + # getattr(, "audit_config", ) + if ( + isinstance(base, ast.Call) + and isinstance(base.func, ast.Name) + and base.func.id == "getattr" + and len(base.args) >= 2 + and isinstance(base.args[1], ast.Constant) + and base.args[1].value == "audit_config" + ): + return True + return False + + +def _config_keys_in_file(path: str) -> set: + """Return the set of config keys a check entrypoint reads from audit_config.""" + keys: set = set() + try: + tree = ast.parse(open(path, encoding="utf-8").read()) + except (SyntaxError, OSError): + return keys + + class _Visitor(ast.NodeVisitor): + def visit_Call(self, node): # audit_config.get("key", ...) + f = node.func + if ( + isinstance(f, ast.Attribute) + and f.attr == "get" + and _refers_audit_config(f.value) + and node.args + and isinstance(node.args[0], ast.Constant) + and isinstance(node.args[0].value, str) + ): + keys.add(node.args[0].value) + self.generic_visit(node) + + def visit_Subscript(self, node): # audit_config["key"] + if _refers_audit_config(node.value) and isinstance( + node.slice, ast.Constant + ): + if isinstance(node.slice.value, str): + keys.add(node.slice.value) + self.generic_visit(node) + + _Visitor().visit(tree) + return keys + + +def _check_config_keys() -> dict: + """Map every check name to the set of audit_config keys it reads.""" + result: dict = {} + for services_dir in glob.glob(_SERVICES_GLOB): + for path in glob.glob(os.path.join(services_dir, "**", "*.py"), recursive=True): + name = os.path.basename(path)[:-3] + # Only the check entrypoint file (named like its folder) is a check. + if name != os.path.basename(os.path.dirname(path)): + continue + keys = _config_keys_in_file(path) + if keys: + result.setdefault(name, set()).update(keys) + return result + + +def _requirements(data): + return data.get("Requirements") or data.get("requirements") or [] + + +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 compute_current_gaps() -> set: + """Return the set of ``::`` pairs missing a constraint. + + A pair is a gap when the framework maps a configurable, verdict-affecting + check but declares no ConfigRequirements for it in any requirement. + """ + check_keys = _check_config_keys() + gaps = set() + for path in sorted( + glob.glob(str(_COMPLIANCE_DIR / "**" / "*.json"), recursive=True) + ): + with open(path, encoding="utf-8") as f: + data = json.load(f) + fname = pathlib.Path(path).name + reqs = _requirements(data) + + mapped_configurable = set() + for req in reqs: + for chk in _req_checks(req): + keys = check_keys.get(chk) + if keys and (keys - _EXEMPT_KEYS): + mapped_configurable.add(chk) + + constrained = set() + for req in reqs: + for c in _req_constraints(req): + constrained.add(c.get("Check")) + + for chk in mapped_configurable - constrained: + gaps.add(f"{fname}::{chk}") + return gaps + + +def _load_baseline() -> set: + if not _BASELINE_PATH.exists(): + return set() + with open(_BASELINE_PATH, encoding="utf-8") as f: + return set(json.load(f)) + + +def test_no_new_config_requirement_gaps(): + """No configurable, verdict-affecting check may be mapped without a + ConfigRequirements unless it is an accepted, pre-existing gap.""" + current = compute_current_gaps() + baseline = _load_baseline() + new_gaps = sorted(current - baseline) + assert not new_gaps, ( + "These frameworks map a configurable check whose config relaxes the " + "verdict but declare no ConfigRequirements for it. Add the constraint, " + "or (only if the config truly cannot relax the verdict) add its key to " + f"_KEYS_NOT_AFFECTING_VERDICT:\n " + "\n ".join(new_gaps) + ) + + +def test_baseline_has_no_stale_entries(): + """Every baseline entry must still be a real gap; fix one → remove it here.""" + current = compute_current_gaps() + baseline = _load_baseline() + resolved = sorted(baseline - current) + assert not resolved, ( + "These baseline entries are no longer gaps (a ConfigRequirements now " + "exists, or the check/mapping was removed). Delete them from " + f"{_BASELINE_PATH.name}:\n " + "\n ".join(resolved) + ) + + +def _schema_bounds(): + """Map every config key to its (minimum, maximum) across all provider schemas.""" + from prowler.config.schema.registry import SCHEMAS + + bounds = {} + for model in SCHEMAS.values(): + if model is None: + continue + props = model.model_json_schema().get("properties", {}) + for name, prop in props.items(): + mn = mx = None + for cand in [prop, *prop.get("anyOf", [])]: + if "minimum" in cand: + mn = cand["minimum"] + if "maximum" in cand: + mx = cand["maximum"] + if name not in bounds: + bounds[name] = (mn, mx) + return bounds + + +def _config_defaults(): + """Map every config key to its default value from config.yaml (first section wins).""" + import yaml + + cfg_path = _REPO_ROOT / "prowler" / "config" / "config.yaml" + with open(cfg_path, encoding="utf-8") as f: + cfg = yaml.safe_load(f) + defaults = {} + for section in cfg.values(): + if isinstance(section, dict): + for k, v in section.items(): + defaults.setdefault(k, v) + return defaults + + +def test_schema_enforced_keys_are_really_pinned(): + """Each _KEYS_SCHEMA_ENFORCED entry must genuinely be non-relaxable: the + config.yaml default must equal the schema bound in the relaxation direction + (gte -> minimum, lte -> maximum). This stops the exemption from hiding a real + gap if a schema or default ever changes.""" + bounds = _schema_bounds() + defaults = _config_defaults() + problems = [] + for key, direction in _KEYS_SCHEMA_ENFORCED.items(): + default = defaults.get(key) + mn, mx = bounds.get(key, (None, None)) + pin = mn if direction == "gte" else mx + if default is None or pin is None or default != pin: + problems.append( + f"{key} (dir={direction}): default={default!r} schema_min={mn!r} " + f"schema_max={mx!r} — not pinned, exemption unjustified" + ) + assert not problems, ( + "These keys are exempted as schema-enforced but the schema no longer " + "pins them to the default (they CAN now be relaxed → real gap). Add a " + "ConfigRequirements and remove them from _KEYS_SCHEMA_ENFORCED:\n " + + "\n ".join(problems) + ) + + +if __name__ == "__main__": + import sys + + if "--update" in sys.argv: + gaps = sorted(compute_current_gaps()) + with open(_BASELINE_PATH, "w", encoding="utf-8") as f: + json.dump(gaps, f, indent=2) + f.write("\n") + print(f"Wrote {len(gaps)} baseline entries to {_BASELINE_PATH}") + else: + print(f"Current gaps: {len(compute_current_gaps())}") diff --git a/tests/lib/outputs/compliance/iso27001/iso27001_aws_numeric_config_requirements_test.py b/tests/lib/outputs/compliance/iso27001/iso27001_aws_numeric_config_requirements_test.py new file mode 100644 index 00000000000..376587872c8 --- /dev/null +++ b/tests/lib/outputs/compliance/iso27001/iso27001_aws_numeric_config_requirements_test.py @@ -0,0 +1,73 @@ +"""Integration coverage for a numeric (lte) ConfigRequirements in a CSV output. + +ISO 27001 AWS A.8.10 maps ``ec2_instance_older_than_specific_days`` with a +``max_ec2_instance_age_in_days lte 180`` constraint (the config.yaml default, +applied as a security floor by the sdk-config-compliance coverage work): if the +user loosens the threshold above 180 the check can PASS while the requirement is +not really satisfied, so the requirement row must be FAIL with [CONFIG NOT VALID]. +Mirrors cis_azure_config_requirements_test.py but for a numeric threshold. +""" + +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.iso27001.iso27001_aws import AWSISO27001 + +_REPO_ROOT = pathlib.Path(__file__).resolve().parents[5] +_FRAMEWORK = _REPO_ROOT / "prowler" / "compliance" / "aws" / "iso27001_2022_aws.json" +_REQUIREMENT_ID = "A.8.10" +_CHECK = "ec2_instance_older_than_specific_days" +_KEY = "max_ec2_instance_age_in_days" + + +def _load(): + return Compliance(**json.load(open(_FRAMEWORK))) + + +def _finding(check_id, status): + 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:ec2:us-east-1:123456789012:instance/i-0", + resource_name="i-0", + 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 + mock_gp.return_value.type = "aws" + out = AWSISO27001(findings=findings, compliance=_load(), file_path=None) + return [r for r in out._data if r.Requirements_Id == _REQUIREMENT_ID] + + +class Test_ISO27001_AWS_Numeric_Constraint: + def test_loosened_threshold_forces_fail(self): + # 365 > 180 -> the applied config is looser than the requirement needs. + rows = _rows_for({_KEY: 365}) + assert rows, f"expected a row for requirement {_REQUIREMENT_ID}" + assert all(r.Status == "FAIL" for r in rows) + assert all("CONFIG NOT VALID" in r.StatusExtended for r in rows) + + def test_default_threshold_keeps_pass(self): + # 180 == the floor -> satisfies lte 180, the PASS stands. + rows = _rows_for({_KEY: 180}) + assert rows + assert all(r.Status == "PASS" for r in rows) + + def test_unset_config_keeps_pass(self): + # Key not set -> default assumed adequate, no override. + rows = _rows_for({}) + assert rows + assert all(r.Status == "PASS" for r in rows) From 4518e614ca37f6658adea552817e35f645428344 Mon Sep 17 00:00:00 2001 From: pedrooot Date: Tue, 23 Jun 2026 16:31:52 +0200 Subject: [PATCH 10/18] chore: fix tests --- tests/lib/check/compliance_config_requirements_coverage_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/lib/check/compliance_config_requirements_coverage_test.py b/tests/lib/check/compliance_config_requirements_coverage_test.py index 836e93bdfb0..050ecbc17f4 100644 --- a/tests/lib/check/compliance_config_requirements_coverage_test.py +++ b/tests/lib/check/compliance_config_requirements_coverage_test.py @@ -208,7 +208,7 @@ def test_no_new_config_requirement_gaps(): "These frameworks map a configurable check whose config relaxes the " "verdict but declare no ConfigRequirements for it. Add the constraint, " "or (only if the config truly cannot relax the verdict) add its key to " - f"_KEYS_NOT_AFFECTING_VERDICT:\n " + "\n ".join(new_gaps) + "_KEYS_NOT_AFFECTING_VERDICT:\n " + "\n ".join(new_gaps) ) From 11ba679dd8795b006274f28920b09860a60954c1 Mon Sep 17 00:00:00 2001 From: pedrooot Date: Wed, 24 Jun 2026 10:21:20 +0200 Subject: [PATCH 11/18] chore(compliance): drop invented ConfigRequirements, keep control-specific only --- .../prowler_threatscore_alibabacloud.json | 8 - .../secnumcloud_3.2_alibabacloud.json | 32 -- .../aws/asd_essential_eight_aws.json | 28 -- .../aws/aws_ai_security_framework_aws.json | 64 ---- ...ndational_security_best_practices_aws.json | 74 +++- ...aws_foundational_technical_review_aws.json | 14 - ...itected_framework_security_pillar_aws.json | 34 -- prowler/compliance/aws/c5_aws.json | 120 ------- prowler/compliance/aws/ccc_aws.json | 44 +-- prowler/compliance/aws/cisa_aws.json | 20 -- prowler/compliance/aws/ens_rd2022_aws.json | 42 --- .../aws/fedramp_20x_ksi_low_aws.json | 46 --- .../aws/fedramp_low_revision_4_aws.json | 38 -- .../aws/fedramp_moderate_revision_4_aws.json | 8 - prowler/compliance/aws/ffiec_aws.json | 30 -- prowler/compliance/aws/gdpr_aws.json | 12 - .../aws/gxp_21_cfr_part_11_aws.json | 30 -- prowler/compliance/aws/hipaa_aws.json | 28 -- prowler/compliance/aws/iso27001_2022_aws.json | 76 ---- .../compliance/aws/kisa_isms_p_2023_aws.json | 106 ------ .../aws/kisa_isms_p_2023_korean_aws.json | 106 ------ prowler/compliance/aws/mitre_attack_aws.json | 30 -- prowler/compliance/aws/nis2_aws.json | 124 ------- .../aws/nist_800_171_revision_2_aws.json | 46 --- .../aws/nist_800_53_revision_4_aws.json | 16 - .../aws/nist_800_53_revision_5_aws.json | 48 --- prowler/compliance/aws/nist_csf_1.1_aws.json | 40 --- prowler/compliance/aws/nist_csf_2.0_aws.json | 74 ---- prowler/compliance/aws/pci_3.2.1_aws.json | 50 --- prowler/compliance/aws/pci_4.0_aws.json | 40 --- .../aws/prowler_threatscore_aws.json | 8 - .../aws/rbi_cyber_security_framework_aws.json | 6 - .../compliance/aws/secnumcloud_3.2_aws.json | 74 ---- prowler/compliance/aws/soc2_aws.json | 48 --- prowler/compliance/azure/c5_azure.json | 8 - prowler/compliance/azure/cis_4.0_azure.json | 12 + prowler/compliance/azure/cis_5.0_azure.json | 12 + prowler/compliance/csa_ccm_4.0.json | 125 +------ prowler/compliance/dora_2022_2554.json | 121 +------ prowler/compliance/gcp/c5_gcp.json | 16 - .../gcp/fedramp_20x_ksi_low_gcp.json | 14 - prowler/compliance/gcp/hipaa_gcp.json | 8 - prowler/compliance/gcp/mitre_attack_gcp.json | 14 - prowler/compliance/gcp/nis2_gcp.json | 14 - .../gcp/rbi_cyber_security_framework_gcp.json | 8 - .../compliance/gcp/secnumcloud_3.2_gcp.json | 22 -- prowler/compliance/gcp/soc2_gcp.json | 8 - prowler/compliance/github/cis_1.0_github.json | 8 - .../kubernetes/cis_1.10_kubernetes.json | 29 ++ .../kubernetes/cis_1.11_kubernetes.json | 29 ++ .../kubernetes/cis_1.12_kubernetes.json | 29 ++ .../kubernetes/cis_1.8_kubernetes.json | 29 ++ .../kubernetes/iso27001_2022_kubernetes.json | 20 -- .../prowler_threatscore_kubernetes.json | 17 + prowler/compliance/m365/cis_4.0_m365.json | 78 ++++- prowler/compliance/m365/cis_6.0_m365.json | 70 ++++ .../m365/prowler_threatscore_m365.json | 78 ++++- .../okta/okta_idaas_stig_v1r2_okta.json | 16 + ...config_requirements_coverage_baseline.json | 331 ------------------ ...iance_config_requirements_coverage_test.py | 298 ---------------- ...01_aws_numeric_config_requirements_test.py | 73 ---- 61 files changed, 458 insertions(+), 2593 deletions(-) delete mode 100644 tests/lib/check/compliance_config_requirements_coverage_baseline.json delete mode 100644 tests/lib/check/compliance_config_requirements_coverage_test.py delete mode 100644 tests/lib/outputs/compliance/iso27001/iso27001_aws_numeric_config_requirements_test.py diff --git a/prowler/compliance/alibabacloud/prowler_threatscore_alibabacloud.json b/prowler/compliance/alibabacloud/prowler_threatscore_alibabacloud.json index 382b9fac8d9..0bfd47df7e9 100644 --- a/prowler/compliance/alibabacloud/prowler_threatscore_alibabacloud.json +++ b/prowler/compliance/alibabacloud/prowler_threatscore_alibabacloud.json @@ -891,14 +891,6 @@ "LevelOfRisk": 2, "Weight": 8 } - ], - "ConfigRequirements": [ - { - "Check": "sls_logstore_retention_period", - "ConfigKey": "min_log_retention_days", - "Operator": "gte", - "Value": 365 - } ] }, { diff --git a/prowler/compliance/alibabacloud/secnumcloud_3.2_alibabacloud.json b/prowler/compliance/alibabacloud/secnumcloud_3.2_alibabacloud.json index f5a40f7c7dc..e71dc8b8698 100644 --- a/prowler/compliance/alibabacloud/secnumcloud_3.2_alibabacloud.json +++ b/prowler/compliance/alibabacloud/secnumcloud_3.2_alibabacloud.json @@ -290,14 +290,6 @@ ], "Checks": [ "ram_user_console_access_unused" - ], - "ConfigRequirements": [ - { - "Check": "ram_user_console_access_unused", - "ConfigKey": "max_console_access_days", - "Operator": "lte", - "Value": 45 - } ] }, { @@ -668,14 +660,6 @@ "Checks": [ "actiontrail_multi_region_enabled", "sls_logstore_retention_period" - ], - "ConfigRequirements": [ - { - "Check": "sls_logstore_retention_period", - "ConfigKey": "min_log_retention_days", - "Operator": "gte", - "Value": 365 - } ] }, { @@ -744,14 +728,6 @@ "rds_instance_sql_audit_enabled", "rds_instance_sql_audit_retention", "sls_logstore_retention_period" - ], - "ConfigRequirements": [ - { - "Check": "rds_instance_sql_audit_retention", - "ConfigKey": "min_rds_audit_retention_days", - "Operator": "gte", - "Value": 180 - } ] }, { @@ -1040,14 +1016,6 @@ "Checks": [ "securitycenter_vulnerability_scan_enabled", "cs_kubernetes_cluster_check_weekly" - ], - "ConfigRequirements": [ - { - "Check": "cs_kubernetes_cluster_check_weekly", - "ConfigKey": "max_cluster_check_days", - "Operator": "lte", - "Value": 7 - } ] }, { diff --git a/prowler/compliance/aws/asd_essential_eight_aws.json b/prowler/compliance/aws/asd_essential_eight_aws.json index 0b430189836..dd39c442681 100644 --- a/prowler/compliance/aws/asd_essential_eight_aws.json +++ b/prowler/compliance/aws/asd_essential_eight_aws.json @@ -479,14 +479,6 @@ "AdditionalInformation": "ASD Essential Eight ML1 - Patch operating systems - clause 8.", "References": "https://www.cyber.gov.au/resources-business-and-government/essential-cyber-security/essential-eight/essential-eight-maturity-model" } - ], - "ConfigRequirements": [ - { - "Check": "ec2_instance_older_than_specific_days", - "ConfigKey": "max_ec2_instance_age_in_days", - "Operator": "lte", - "Value": 180 - } ] }, { @@ -1163,26 +1155,6 @@ "AdditionalInformation": "ASD Essential Eight ML1 - Regular backups - clause 1.", "References": "https://www.cyber.gov.au/resources-business-and-government/essential-cyber-security/essential-eight/essential-eight-maturity-model" } - ], - "ConfigRequirements": [ - { - "Check": "documentdb_cluster_backup_enabled", - "ConfigKey": "minimum_backup_retention_period", - "Operator": "gte", - "Value": 7 - }, - { - "Check": "elasticache_redis_cluster_backup_enabled", - "ConfigKey": "minimum_snapshot_retention_period", - "Operator": "gte", - "Value": 7 - }, - { - "Check": "neptune_cluster_backup_enabled", - "ConfigKey": "minimum_backup_retention_period", - "Operator": "gte", - "Value": 7 - } ] }, { diff --git a/prowler/compliance/aws/aws_ai_security_framework_aws.json b/prowler/compliance/aws/aws_ai_security_framework_aws.json index 01399c52c8a..9b87f7464d4 100644 --- a/prowler/compliance/aws/aws_ai_security_framework_aws.json +++ b/prowler/compliance/aws/aws_ai_security_framework_aws.json @@ -263,20 +263,6 @@ "iam_user_two_active_access_key", "iam_user_console_access_unused", "bedrock_api_key_no_long_term_credentials" - ], - "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 - } ] }, { @@ -315,20 +301,6 @@ "iam_role_access_not_stale_to_bedrock", "iam_user_access_not_stale_to_bedrock", "iam_role_cross_account_readonlyaccess_policy" - ], - "ConfigRequirements": [ - { - "Check": "iam_role_access_not_stale_to_bedrock", - "ConfigKey": "max_unused_bedrock_access_days", - "Operator": "lte", - "Value": 60 - }, - { - "Check": "iam_user_access_not_stale_to_bedrock", - "ConfigKey": "max_unused_bedrock_access_days", - "Operator": "lte", - "Value": 60 - } ] }, { @@ -460,20 +432,6 @@ "secretsmanager_has_restrictive_resource_policy", "secretsmanager_not_publicly_accessible", "secretsmanager_secret_unused" - ], - "ConfigRequirements": [ - { - "Check": "secretsmanager_secret_rotated_periodically", - "ConfigKey": "max_days_secret_unrotated", - "Operator": "lte", - "Value": 90 - }, - { - "Check": "secretsmanager_secret_unused", - "ConfigKey": "max_days_secret_unused", - "Operator": "lte", - "Value": 90 - } ] }, { @@ -633,14 +591,6 @@ ], "Checks": [ "cloudtrail_threat_detection_llm_jacking" - ], - "ConfigRequirements": [ - { - "Check": "cloudtrail_threat_detection_llm_jacking", - "ConfigKey": "threat_detection_llm_jacking_threshold", - "Operator": "lte", - "Value": 0.4 - } ] }, { @@ -950,20 +900,6 @@ "cloudtrail_threat_detection_llm_jacking", "cloudtrail_threat_detection_privilege_escalation", "cloudtrail_threat_detection_enumeration" - ], - "ConfigRequirements": [ - { - "Check": "cloudtrail_threat_detection_enumeration", - "ConfigKey": "threat_detection_enumeration_threshold", - "Operator": "lte", - "Value": 0.3 - }, - { - "Check": "cloudtrail_threat_detection_privilege_escalation", - "ConfigKey": "threat_detection_privilege_escalation_threshold", - "Operator": "lte", - "Value": 0.2 - } ] }, { 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 419dac59ee8..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 + } ] }, { @@ -1146,14 +1154,6 @@ "SectionDescription": "This section contains recommendations for configuring EC2 resources.", "Service": "EC2" } - ], - "ConfigRequirements": [ - { - "Check": "ec2_instance_older_than_specific_days", - "ConfigKey": "max_ec2_instance_age_in_days", - "Operator": "lte", - "Value": 180 - } ] }, { @@ -1994,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 + } ] }, { @@ -2028,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 + } ] }, { @@ -2242,14 +2258,6 @@ "SectionDescription": "This section contains recommendations for configuring ElastiCache resources.", "Service": "ElastiCache" } - ], - "ConfigRequirements": [ - { - "Check": "elasticache_redis_cluster_backup_enabled", - "ConfigKey": "minimum_snapshot_retention_period", - "Operator": "gte", - "Value": 7 - } ] }, { @@ -2870,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" + ] + } ] }, { diff --git a/prowler/compliance/aws/aws_foundational_technical_review_aws.json b/prowler/compliance/aws/aws_foundational_technical_review_aws.json index 9e2486799cf..9d8e3cfdc81 100644 --- a/prowler/compliance/aws/aws_foundational_technical_review_aws.json +++ b/prowler/compliance/aws/aws_foundational_technical_review_aws.json @@ -183,12 +183,6 @@ "ConfigKey": "mute_non_default_regions", "Operator": "eq", "Value": false - }, - { - "Check": "iam_user_accesskey_unused", - "ConfigKey": "max_unused_access_keys_days", - "Operator": "lte", - "Value": 45 } ] }, @@ -396,14 +390,6 @@ "ec2_securitygroup_default_restrict_traffic", "ec2_securitygroup_not_used", "ec2_securitygroup_with_many_ingress_egress_rules" - ], - "ConfigRequirements": [ - { - "Check": "ec2_securitygroup_with_many_ingress_egress_rules", - "ConfigKey": "max_security_group_rules", - "Operator": "lte", - "Value": 50 - } ] }, { 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 393552c592f..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 @@ -444,32 +444,6 @@ "ecr_repositories_lifecycle_policy_enabled", "elbv2_listeners_underneath", "iam_password_policy_expires_passwords_within_90_days_or_less" - ], - "ConfigRequirements": [ - { - "Check": "appstream_fleet_maximum_session_duration", - "ConfigKey": "max_session_duration_seconds", - "Operator": "lte", - "Value": 36000 - }, - { - "Check": "appstream_fleet_session_disconnect_timeout", - "ConfigKey": "max_disconnect_timeout_in_seconds", - "Operator": "lte", - "Value": 300 - }, - { - "Check": "appstream_fleet_session_idle_disconnect_timeout", - "ConfigKey": "max_idle_disconnect_timeout_in_seconds", - "Operator": "lte", - "Value": 600 - }, - { - "Check": "cloudwatch_log_group_retention_policy_specific_days_enabled", - "ConfigKey": "log_group_retention_days", - "Operator": "gte", - "Value": 365 - } ] }, { @@ -804,14 +778,6 @@ "shield_advanced_protection_in_global_accelerators", "shield_advanced_protection_in_internet_facing_load_balancers", "shield_advanced_protection_in_route53_hosted_zones" - ], - "ConfigRequirements": [ - { - "Check": "ec2_securitygroup_with_many_ingress_egress_rules", - "ConfigKey": "max_security_group_rules", - "Operator": "lte", - "Value": 50 - } ] }, { diff --git a/prowler/compliance/aws/c5_aws.json b/prowler/compliance/aws/c5_aws.json index 9964da55109..269a5ce3082 100644 --- a/prowler/compliance/aws/c5_aws.json +++ b/prowler/compliance/aws/c5_aws.json @@ -428,14 +428,6 @@ "cloudtrail_threat_detection_enumeration", "ec2_securitygroup_allow_ingress_from_internet_to_high_risk_tcp_ports", "wellarchitected_workload_no_high_or_medium_risks" - ], - "ConfigRequirements": [ - { - "Check": "cloudtrail_threat_detection_enumeration", - "ConfigKey": "threat_detection_enumeration_threshold", - "Operator": "lte", - "Value": 0.3 - } ] }, { @@ -590,14 +582,6 @@ ], "Checks": [ "secretsmanager_secret_rotated_periodically" - ], - "ConfigRequirements": [ - { - "Check": "secretsmanager_secret_rotated_periodically", - "ConfigKey": "max_days_secret_unrotated", - "Operator": "lte", - "Value": 90 - } ] }, { @@ -615,14 +599,6 @@ "Checks": [ "guardduty_no_high_severity_findings", "cloudtrail_threat_detection_privilege_escalation" - ], - "ConfigRequirements": [ - { - "Check": "cloudtrail_threat_detection_privilege_escalation", - "ConfigKey": "threat_detection_privilege_escalation_threshold", - "Operator": "lte", - "Value": 0.2 - } ] }, { @@ -1566,14 +1542,6 @@ "waf_global_webacl_logging_enabled", "wafv2_webacl_logging_enabled", "wafv2_webacl_rule_logging_enabled" - ], - "ConfigRequirements": [ - { - "Check": "cloudwatch_log_group_retention_policy_specific_days_enabled", - "ConfigKey": "log_group_retention_days", - "Operator": "gte", - "Value": 365 - } ] }, { @@ -1787,14 +1755,6 @@ "rds_cluster_deletion_protection", "rds_instance_deletion_protection", "s3_bucket_no_mfa_delete" - ], - "ConfigRequirements": [ - { - "Check": "kinesis_stream_data_retention_period", - "ConfigKey": "min_kinesis_stream_retention_hours", - "Operator": "gte", - "Value": 168 - } ] }, { @@ -3032,14 +2992,6 @@ "iam_user_mfa_enabled_console_access", "iam_user_console_access_unused", "iam_user_no_setup_initial_access_key" - ], - "ConfigRequirements": [ - { - "Check": "iam_user_console_access_unused", - "ConfigKey": "max_console_access_days", - "Operator": "lte", - "Value": 45 - } ] }, { @@ -3110,26 +3062,6 @@ "rds_cluster_protected_by_backup_plan", "rds_instance_backup_enabled", "rds_instance_protected_by_backup_plan" - ], - "ConfigRequirements": [ - { - "Check": "documentdb_cluster_backup_enabled", - "ConfigKey": "minimum_backup_retention_period", - "Operator": "gte", - "Value": 7 - }, - { - "Check": "elasticache_redis_cluster_backup_enabled", - "ConfigKey": "minimum_snapshot_retention_period", - "Operator": "gte", - "Value": 7 - }, - { - "Check": "neptune_cluster_backup_enabled", - "ConfigKey": "minimum_backup_retention_period", - "Operator": "gte", - "Value": 7 - } ] }, { @@ -5448,38 +5380,6 @@ "iam_user_accesskey_unused", "iam_user_console_access_unused", "secretsmanager_secret_unused" - ], - "ConfigRequirements": [ - { - "Check": "iam_role_access_not_stale_to_bedrock", - "ConfigKey": "max_unused_bedrock_access_days", - "Operator": "lte", - "Value": 60 - }, - { - "Check": "iam_user_access_not_stale_to_bedrock", - "ConfigKey": "max_unused_bedrock_access_days", - "Operator": "lte", - "Value": 60 - }, - { - "Check": "iam_user_access_not_stale_to_sagemaker", - "ConfigKey": "max_unused_sagemaker_access_days", - "Operator": "lte", - "Value": 90 - }, - { - "Check": "iam_user_accesskey_unused", - "ConfigKey": "max_unused_access_keys_days", - "Operator": "lte", - "Value": 45 - }, - { - "Check": "secretsmanager_secret_unused", - "ConfigKey": "max_days_secret_unused", - "Operator": "lte", - "Value": 90 - } ] }, { @@ -10588,26 +10488,6 @@ "appstream_fleet_session_idle_disconnect_timeout", "appstream_fleet_session_disconnect_timeout", "appstream_fleet_maximum_session_duration" - ], - "ConfigRequirements": [ - { - "Check": "appstream_fleet_maximum_session_duration", - "ConfigKey": "max_session_duration_seconds", - "Operator": "lte", - "Value": 36000 - }, - { - "Check": "appstream_fleet_session_disconnect_timeout", - "ConfigKey": "max_disconnect_timeout_in_seconds", - "Operator": "lte", - "Value": 300 - }, - { - "Check": "appstream_fleet_session_idle_disconnect_timeout", - "ConfigKey": "max_idle_disconnect_timeout_in_seconds", - "Operator": "lte", - "Value": 600 - } ] }, { diff --git a/prowler/compliance/aws/ccc_aws.json b/prowler/compliance/aws/ccc_aws.json index a8ffefa0190..7935424193d 100644 --- a/prowler/compliance/aws/ccc_aws.json +++ b/prowler/compliance/aws/ccc_aws.json @@ -1049,20 +1049,6 @@ "rds_instance_backup_enabled", "neptune_cluster_backup_enabled", "documentdb_cluster_backup_enabled" - ], - "ConfigRequirements": [ - { - "Check": "documentdb_cluster_backup_enabled", - "ConfigKey": "minimum_backup_retention_period", - "Operator": "gte", - "Value": 7 - }, - { - "Check": "neptune_cluster_backup_enabled", - "ConfigKey": "minimum_backup_retention_period", - "Operator": "gte", - "Value": 7 - } ] }, { @@ -1850,12 +1836,6 @@ "ConfigKey": "mute_non_default_regions", "Operator": "eq", "Value": false - }, - { - "Check": "cloudtrail_threat_detection_enumeration", - "ConfigKey": "threat_detection_enumeration_threshold", - "Operator": "lte", - "Value": 0.3 } ] }, @@ -2641,14 +2621,6 @@ ], "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 - } ] }, { @@ -4385,6 +4357,14 @@ ], "Checks": [ "acm_certificates_expiration_check" + ], + "ConfigRequirements": [ + { + "Check": "acm_certificates_expiration_check", + "ConfigKey": "days_to_expire_threshold", + "Operator": "gte", + "Value": 30 + } ] }, { @@ -4818,14 +4798,6 @@ "Checks": [ "secretsmanager_automatic_rotation_enabled", "secretsmanager_secret_rotated_periodically" - ], - "ConfigRequirements": [ - { - "Check": "secretsmanager_secret_rotated_periodically", - "ConfigKey": "max_days_secret_unrotated", - "Operator": "lte", - "Value": 90 - } ] }, { diff --git a/prowler/compliance/aws/cisa_aws.json b/prowler/compliance/aws/cisa_aws.json index ebf7c09d37b..27b06a1ea48 100644 --- a/prowler/compliance/aws/cisa_aws.json +++ b/prowler/compliance/aws/cisa_aws.json @@ -21,14 +21,6 @@ "ec2_instance_older_than_specific_days", "ssm_managed_compliant_patching", "ec2_elastic_ip_unassigned" - ], - "ConfigRequirements": [ - { - "Check": "ec2_instance_older_than_specific_days", - "ConfigKey": "max_ec2_instance_age_in_days", - "Operator": "lte", - "Value": 180 - } ] }, { @@ -157,18 +149,6 @@ "ConfigKey": "mute_non_default_regions", "Operator": "eq", "Value": false - }, - { - "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 } ] }, diff --git a/prowler/compliance/aws/ens_rd2022_aws.json b/prowler/compliance/aws/ens_rd2022_aws.json index 9345ac925fe..144437ce527 100644 --- a/prowler/compliance/aws/ens_rd2022_aws.json +++ b/prowler/compliance/aws/ens_rd2022_aws.json @@ -348,20 +348,6 @@ "iam_user_accesskey_unused", "iam_user_console_access_unused", "iam_rotate_access_key_90_days" - ], - "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 - } ] }, { @@ -561,26 +547,6 @@ "iam_user_access_not_stale_to_sagemaker", "iam_user_accesskey_unused", "iam_user_console_access_unused" - ], - "ConfigRequirements": [ - { - "Check": "iam_role_access_not_stale_to_bedrock", - "ConfigKey": "max_unused_bedrock_access_days", - "Operator": "lte", - "Value": 60 - }, - { - "Check": "iam_user_access_not_stale_to_bedrock", - "ConfigKey": "max_unused_bedrock_access_days", - "Operator": "lte", - "Value": 60 - }, - { - "Check": "iam_user_access_not_stale_to_sagemaker", - "ConfigKey": "max_unused_sagemaker_access_days", - "Operator": "lte", - "Value": 90 - } ] }, { @@ -1408,14 +1374,6 @@ ], "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 - } ] }, { diff --git a/prowler/compliance/aws/fedramp_20x_ksi_low_aws.json b/prowler/compliance/aws/fedramp_20x_ksi_low_aws.json index 089fce6d37f..15763ef48ed 100644 --- a/prowler/compliance/aws/fedramp_20x_ksi_low_aws.json +++ b/prowler/compliance/aws/fedramp_20x_ksi_low_aws.json @@ -44,12 +44,6 @@ "ConfigKey": "mute_non_default_regions", "Operator": "eq", "Value": false - }, - { - "Check": "ec2_instance_older_than_specific_days", - "ConfigKey": "max_ec2_instance_age_in_days", - "Operator": "lte", - "Value": 180 } ] }, @@ -131,38 +125,6 @@ "iam_user_two_active_access_key", "organizations_scp_check_deny_regions", "organizations_opt_out_ai_services_policy" - ], - "ConfigRequirements": [ - { - "Check": "iam_role_access_not_stale_to_bedrock", - "ConfigKey": "max_unused_bedrock_access_days", - "Operator": "lte", - "Value": 60 - }, - { - "Check": "iam_user_access_not_stale_to_bedrock", - "ConfigKey": "max_unused_bedrock_access_days", - "Operator": "lte", - "Value": 60 - }, - { - "Check": "iam_user_access_not_stale_to_sagemaker", - "ConfigKey": "max_unused_sagemaker_access_days", - "Operator": "lte", - "Value": 90 - }, - { - "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 - } ] }, { @@ -241,14 +203,6 @@ "s3_bucket_server_access_logging_enabled", "vpc_flow_logs_enabled", "wafv2_webacl_logging_enabled" - ], - "ConfigRequirements": [ - { - "Check": "cloudwatch_log_group_retention_policy_specific_days_enabled", - "ConfigKey": "log_group_retention_days", - "Operator": "gte", - "Value": 365 - } ] }, { diff --git a/prowler/compliance/aws/fedramp_low_revision_4_aws.json b/prowler/compliance/aws/fedramp_low_revision_4_aws.json index d1b5925f0a6..059de696754 100644 --- a/prowler/compliance/aws/fedramp_low_revision_4_aws.json +++ b/prowler/compliance/aws/fedramp_low_revision_4_aws.json @@ -59,36 +59,6 @@ "ConfigKey": "mute_non_default_regions", "Operator": "eq", "Value": false - }, - { - "Check": "iam_role_access_not_stale_to_bedrock", - "ConfigKey": "max_unused_bedrock_access_days", - "Operator": "lte", - "Value": 60 - }, - { - "Check": "iam_user_access_not_stale_to_bedrock", - "ConfigKey": "max_unused_bedrock_access_days", - "Operator": "lte", - "Value": 60 - }, - { - "Check": "iam_user_access_not_stale_to_sagemaker", - "ConfigKey": "max_unused_sagemaker_access_days", - "Operator": "lte", - "Value": 90 - }, - { - "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 } ] }, @@ -313,14 +283,6 @@ "ec2_networkacl_allow_ingress_any_port", "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_22", "ec2_networkacl_allow_ingress_any_port" - ], - "ConfigRequirements": [ - { - "Check": "ec2_instance_older_than_specific_days", - "ConfigKey": "max_ec2_instance_age_in_days", - "Operator": "lte", - "Value": 180 - } ] }, { diff --git a/prowler/compliance/aws/fedramp_moderate_revision_4_aws.json b/prowler/compliance/aws/fedramp_moderate_revision_4_aws.json index e34d190907c..eaa3ea25dce 100644 --- a/prowler/compliance/aws/fedramp_moderate_revision_4_aws.json +++ b/prowler/compliance/aws/fedramp_moderate_revision_4_aws.json @@ -805,14 +805,6 @@ "ec2_networkacl_allow_ingress_any_port", "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_22", "ec2_networkacl_allow_ingress_any_port" - ], - "ConfigRequirements": [ - { - "Check": "ec2_instance_older_than_specific_days", - "ConfigKey": "max_ec2_instance_age_in_days", - "Operator": "lte", - "Value": 180 - } ] }, { diff --git a/prowler/compliance/aws/ffiec_aws.json b/prowler/compliance/aws/ffiec_aws.json index 4246d2b2fc4..8a50b799259 100644 --- a/prowler/compliance/aws/ffiec_aws.json +++ b/prowler/compliance/aws/ffiec_aws.json @@ -21,14 +21,6 @@ "ec2_instance_managed_by_ssm", "ec2_instance_older_than_specific_days", "ec2_elastic_ip_unassigned" - ], - "ConfigRequirements": [ - { - "Check": "ec2_instance_older_than_specific_days", - "ConfigKey": "max_ec2_instance_age_in_days", - "Operator": "lte", - "Value": 180 - } ] }, { @@ -133,14 +125,6 @@ "redshift_cluster_audit_logging", "s3_bucket_server_access_logging_enabled", "vpc_flow_logs_enabled" - ], - "ConfigRequirements": [ - { - "Check": "cloudwatch_log_group_retention_policy_specific_days_enabled", - "ConfigKey": "log_group_retention_days", - "Operator": "gte", - "Value": 365 - } ] }, { @@ -744,20 +728,6 @@ "iam_user_mfa_enabled_console_access", "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 - } ] }, { diff --git a/prowler/compliance/aws/gdpr_aws.json b/prowler/compliance/aws/gdpr_aws.json index ddc0b2c7abb..a97a11e3dcd 100644 --- a/prowler/compliance/aws/gdpr_aws.json +++ b/prowler/compliance/aws/gdpr_aws.json @@ -66,18 +66,6 @@ "ConfigKey": "mute_non_default_regions", "Operator": "eq", "Value": false - }, - { - "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 } ] }, 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 a9cca175afc..871af9e726e 100644 --- a/prowler/compliance/aws/gxp_21_cfr_part_11_aws.json +++ b/prowler/compliance/aws/gxp_21_cfr_part_11_aws.json @@ -30,14 +30,6 @@ "s3_bucket_object_versioning", "ssm_managed_compliant_patching", "ssm_managed_compliant_patching" - ], - "ConfigRequirements": [ - { - "Check": "ec2_instance_older_than_specific_days", - "ConfigKey": "max_ec2_instance_age_in_days", - "Operator": "lte", - "Value": 180 - } ] }, { @@ -66,14 +58,6 @@ "s3_bucket_object_versioning", "sagemaker_notebook_instance_without_direct_internet_access_configured", "sagemaker_notebook_instance_encryption_enabled" - ], - "ConfigRequirements": [ - { - "Check": "cloudwatch_log_group_retention_policy_specific_days_enabled", - "ConfigKey": "log_group_retention_days", - "Operator": "gte", - "Value": 365 - } ] }, { @@ -126,20 +110,6 @@ "ec2_networkacl_allow_ingress_any_port", "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_22", "ec2_networkacl_allow_ingress_any_port" - ], - "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 - } ] }, { diff --git a/prowler/compliance/aws/hipaa_aws.json b/prowler/compliance/aws/hipaa_aws.json index 5aa5f531e89..9eb243e6ccf 100644 --- a/prowler/compliance/aws/hipaa_aws.json +++ b/prowler/compliance/aws/hipaa_aws.json @@ -86,14 +86,6 @@ "ec2_networkacl_allow_ingress_any_port", "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_22", "ec2_networkacl_allow_ingress_any_port" - ], - "ConfigRequirements": [ - { - "Check": "ec2_instance_older_than_specific_days", - "ConfigKey": "max_ec2_instance_age_in_days", - "Operator": "lte", - "Value": 180 - } ] }, { @@ -231,20 +223,6 @@ "iam_no_root_access_key", "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 - } ] }, { @@ -782,12 +760,6 @@ "ConfigKey": "mute_non_default_regions", "Operator": "eq", "Value": false - }, - { - "Check": "cloudwatch_log_group_retention_policy_specific_days_enabled", - "ConfigKey": "log_group_retention_days", - "Operator": "gte", - "Value": 365 } ] }, diff --git a/prowler/compliance/aws/iso27001_2022_aws.json b/prowler/compliance/aws/iso27001_2022_aws.json index fa7aee996a6..563b856317a 100644 --- a/prowler/compliance/aws/iso27001_2022_aws.json +++ b/prowler/compliance/aws/iso27001_2022_aws.json @@ -123,26 +123,6 @@ "cloudtrail_threat_detection_enumeration", "cloudtrail_threat_detection_llm_jacking", "cloudtrail_threat_detection_privilege_escalation" - ], - "ConfigRequirements": [ - { - "Check": "cloudtrail_threat_detection_enumeration", - "ConfigKey": "threat_detection_enumeration_threshold", - "Operator": "lte", - "Value": 0.3 - }, - { - "Check": "cloudtrail_threat_detection_llm_jacking", - "ConfigKey": "threat_detection_llm_jacking_threshold", - "Operator": "lte", - "Value": 0.4 - }, - { - "Check": "cloudtrail_threat_detection_privilege_escalation", - "ConfigKey": "threat_detection_privilege_escalation_threshold", - "Operator": "lte", - "Value": 0.2 - } ] }, { @@ -289,38 +269,6 @@ "iam_password_policy_uppercase", "iam_user_mfa_enabled_console_access", "iam_rotate_access_key_90_days" - ], - "ConfigRequirements": [ - { - "Check": "iam_role_access_not_stale_to_bedrock", - "ConfigKey": "max_unused_bedrock_access_days", - "Operator": "lte", - "Value": 60 - }, - { - "Check": "iam_user_access_not_stale_to_bedrock", - "ConfigKey": "max_unused_bedrock_access_days", - "Operator": "lte", - "Value": 60 - }, - { - "Check": "iam_user_access_not_stale_to_sagemaker", - "ConfigKey": "max_unused_sagemaker_access_days", - "Operator": "lte", - "Value": 90 - }, - { - "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 - } ] }, { @@ -1269,14 +1217,6 @@ "s3_bucket_lifecycle_enabled", "s3_bucket_object_versioning", "ec2_instance_older_than_specific_days" - ], - "ConfigRequirements": [ - { - "Check": "ec2_instance_older_than_specific_days", - "ConfigKey": "max_ec2_instance_age_in_days", - "Operator": "lte", - "Value": 180 - } ] }, { @@ -1506,14 +1446,6 @@ "waf_global_webacl_logging_enabled", "wafv2_webacl_logging_enabled", "wafv2_webacl_rule_logging_enabled" - ], - "ConfigRequirements": [ - { - "Check": "cloudwatch_log_group_retention_policy_specific_days_enabled", - "ConfigKey": "log_group_retention_days", - "Operator": "gte", - "Value": 365 - } ] }, { @@ -1697,14 +1629,6 @@ "ec2_securitygroup_not_used", "ec2_securitygroup_with_many_ingress_egress_rules", "ec2_transitgateway_auto_accept_vpc_attachments" - ], - "ConfigRequirements": [ - { - "Check": "ec2_securitygroup_with_many_ingress_egress_rules", - "ConfigKey": "max_security_group_rules", - "Operator": "lte", - "Value": 50 - } ] }, { diff --git a/prowler/compliance/aws/kisa_isms_p_2023_aws.json b/prowler/compliance/aws/kisa_isms_p_2023_aws.json index de4c0d44c79..7b0446ac3f0 100644 --- a/prowler/compliance/aws/kisa_isms_p_2023_aws.json +++ b/prowler/compliance/aws/kisa_isms_p_2023_aws.json @@ -1343,32 +1343,6 @@ "Case 2: In the login process for information systems and personal information processing systems, detailed messages are displayed about whether the ID exists or the password is incorrect, and there is no limit on login failure attempts." ] } - ], - "ConfigRequirements": [ - { - "Check": "appstream_fleet_maximum_session_duration", - "ConfigKey": "max_session_duration_seconds", - "Operator": "lte", - "Value": 36000 - }, - { - "Check": "appstream_fleet_session_disconnect_timeout", - "ConfigKey": "max_disconnect_timeout_in_seconds", - "Operator": "lte", - "Value": 300 - }, - { - "Check": "appstream_fleet_session_idle_disconnect_timeout", - "ConfigKey": "max_idle_disconnect_timeout_in_seconds", - "Operator": "lte", - "Value": 600 - }, - { - "Check": "iam_user_console_access_unused", - "ConfigKey": "max_console_access_days", - "Operator": "lte", - "Value": 45 - } ] }, { @@ -2226,26 +2200,6 @@ "Case 3: The encryption key applied in the development system is the same as the one applied in the production system, making it easy to decrypt actual data through the development system." ] } - ], - "ConfigRequirements": [ - { - "Check": "iam_user_accesskey_unused", - "ConfigKey": "max_unused_access_keys_days", - "Operator": "lte", - "Value": 45 - }, - { - "Check": "secretsmanager_secret_rotated_periodically", - "ConfigKey": "max_days_secret_unrotated", - "Operator": "lte", - "Value": 90 - }, - { - "Check": "secretsmanager_secret_unused", - "ConfigKey": "max_days_secret_unused", - "Operator": "lte", - "Value": 90 - } ] }, { @@ -2551,14 +2505,6 @@ "Case 4: Inconsistencies exist between fault handling procedures and fault type-specific response methods, or there is a lack of rationale for estimating response times, making swift, accurate, and systematic responses difficult." ] } - ], - "ConfigRequirements": [ - { - "Check": "ec2_instance_older_than_specific_days", - "ConfigKey": "max_ec2_instance_age_in_days", - "Operator": "lte", - "Value": 180 - } ] }, { @@ -2617,26 +2563,6 @@ "Case 4: Although higher-level or internal guidelines stipulate that recovery tests for backup media should be conducted periodically, recovery tests have not been performed for an extended period." ] } - ], - "ConfigRequirements": [ - { - "Check": "documentdb_cluster_backup_enabled", - "ConfigKey": "minimum_backup_retention_period", - "Operator": "gte", - "Value": 7 - }, - { - "Check": "elasticache_redis_cluster_backup_enabled", - "ConfigKey": "minimum_snapshot_retention_period", - "Operator": "gte", - "Value": 7 - }, - { - "Check": "neptune_cluster_backup_enabled", - "ConfigKey": "minimum_backup_retention_period", - "Operator": "gte", - "Value": 7 - } ] }, { @@ -2733,32 +2659,6 @@ "Case 6: A personal information processing system handling personal information of 100,000 data subjects is only retaining access logs for one year." ] } - ], - "ConfigRequirements": [ - { - "Check": "cloudtrail_threat_detection_enumeration", - "ConfigKey": "threat_detection_enumeration_threshold", - "Operator": "lte", - "Value": 0.3 - }, - { - "Check": "cloudtrail_threat_detection_llm_jacking", - "ConfigKey": "threat_detection_llm_jacking_threshold", - "Operator": "lte", - "Value": 0.4 - }, - { - "Check": "cloudtrail_threat_detection_privilege_escalation", - "ConfigKey": "threat_detection_privilege_escalation_threshold", - "Operator": "lte", - "Value": 0.2 - }, - { - "Check": "cloudwatch_log_group_retention_policy_specific_days_enabled", - "ConfigKey": "log_group_retention_days", - "Operator": "gte", - "Value": 365 - } ] }, { @@ -3507,12 +3407,6 @@ "RSA-1024", "P-192" ] - }, - { - "Check": "ec2_securitygroup_with_many_ingress_egress_rules", - "ConfigKey": "max_security_group_rules", - "Operator": "lte", - "Value": 50 } ], "Attributes": [ 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 d6aea0ba17e..40b338ce41f 100644 --- a/prowler/compliance/aws/kisa_isms_p_2023_korean_aws.json +++ b/prowler/compliance/aws/kisa_isms_p_2023_korean_aws.json @@ -1343,32 +1343,6 @@ "사례 2 : 정보시스템 및 개인정보처리시스템 로그인 실패 시 해당 ID가 존재하지 않거나 비밀번호가 틀림을 자세히 표시해 주고 있으며, 로그인 실패횟수에 대한 제한이 없는 경우" ] } - ], - "ConfigRequirements": [ - { - "Check": "appstream_fleet_maximum_session_duration", - "ConfigKey": "max_session_duration_seconds", - "Operator": "lte", - "Value": 36000 - }, - { - "Check": "appstream_fleet_session_disconnect_timeout", - "ConfigKey": "max_disconnect_timeout_in_seconds", - "Operator": "lte", - "Value": 300 - }, - { - "Check": "appstream_fleet_session_idle_disconnect_timeout", - "ConfigKey": "max_idle_disconnect_timeout_in_seconds", - "Operator": "lte", - "Value": 600 - }, - { - "Check": "iam_user_console_access_unused", - "ConfigKey": "max_console_access_days", - "Operator": "lte", - "Value": 45 - } ] }, { @@ -2228,26 +2202,6 @@ "사례 3 : 개발시스템에 적용되어 있는 암호키와 운영시스템에 적용된 암호키가 동일하여, 암호화된 실데이터가 개발시스템을 통해 쉽게 복호화가 가능한 경우" ] } - ], - "ConfigRequirements": [ - { - "Check": "iam_user_accesskey_unused", - "ConfigKey": "max_unused_access_keys_days", - "Operator": "lte", - "Value": 45 - }, - { - "Check": "secretsmanager_secret_rotated_periodically", - "ConfigKey": "max_days_secret_unrotated", - "Operator": "lte", - "Value": 90 - }, - { - "Check": "secretsmanager_secret_unused", - "ConfigKey": "max_days_secret_unused", - "Operator": "lte", - "Value": 90 - } ] }, { @@ -2555,14 +2509,6 @@ "사례 4 : 장애처리절차와 장애유형별 조치방법 간 일관성이 없거나 예상소요시간 산정에 대한 근거가 부족하여 신속·정확하고 체계적인 대응이 어려운 경우" ] } - ], - "ConfigRequirements": [ - { - "Check": "ec2_instance_older_than_specific_days", - "ConfigKey": "max_ec2_instance_age_in_days", - "Operator": "lte", - "Value": 180 - } ] }, { @@ -2621,26 +2567,6 @@ "사례 4 : 상위 지침 또는 내부 지침에는 주기적으로 백업매체에 대한 복구 테스트를 수행하도록 정하고 있으나 복구테스트를 장기간 실시하지 않은 경우" ] } - ], - "ConfigRequirements": [ - { - "Check": "documentdb_cluster_backup_enabled", - "ConfigKey": "minimum_backup_retention_period", - "Operator": "gte", - "Value": 7 - }, - { - "Check": "elasticache_redis_cluster_backup_enabled", - "ConfigKey": "minimum_snapshot_retention_period", - "Operator": "gte", - "Value": 7 - }, - { - "Check": "neptune_cluster_backup_enabled", - "ConfigKey": "minimum_backup_retention_period", - "Operator": "gte", - "Value": 7 - } ] }, { @@ -2736,32 +2662,6 @@ "사례 6 : 개인정보처리자가 정보주체 10만 명의 개인정보를 처리하는 개인정보처리시스템의 개인정보취급자 접속기록을 1년간만 보관하고 있는 경우" ] } - ], - "ConfigRequirements": [ - { - "Check": "cloudtrail_threat_detection_enumeration", - "ConfigKey": "threat_detection_enumeration_threshold", - "Operator": "lte", - "Value": 0.3 - }, - { - "Check": "cloudtrail_threat_detection_llm_jacking", - "ConfigKey": "threat_detection_llm_jacking_threshold", - "Operator": "lte", - "Value": 0.4 - }, - { - "Check": "cloudtrail_threat_detection_privilege_escalation", - "ConfigKey": "threat_detection_privilege_escalation_threshold", - "Operator": "lte", - "Value": 0.2 - }, - { - "Check": "cloudwatch_log_group_retention_policy_specific_days_enabled", - "ConfigKey": "log_group_retention_days", - "Operator": "gte", - "Value": 365 - } ] }, { @@ -3510,12 +3410,6 @@ "RSA-1024", "P-192" ] - }, - { - "Check": "ec2_securitygroup_with_many_ingress_egress_rules", - "ConfigKey": "max_security_group_rules", - "Operator": "lte", - "Value": 50 } ], "Attributes": [ diff --git a/prowler/compliance/aws/mitre_attack_aws.json b/prowler/compliance/aws/mitre_attack_aws.json index e17e3fba9f9..3ac8cf04323 100644 --- a/prowler/compliance/aws/mitre_attack_aws.json +++ b/prowler/compliance/aws/mitre_attack_aws.json @@ -244,36 +244,6 @@ "ConfigKey": "mute_non_default_regions", "Operator": "eq", "Value": false - }, - { - "Check": "iam_role_access_not_stale_to_bedrock", - "ConfigKey": "max_unused_bedrock_access_days", - "Operator": "lte", - "Value": 60 - }, - { - "Check": "iam_user_access_not_stale_to_bedrock", - "ConfigKey": "max_unused_bedrock_access_days", - "Operator": "lte", - "Value": 60 - }, - { - "Check": "iam_user_access_not_stale_to_sagemaker", - "ConfigKey": "max_unused_sagemaker_access_days", - "Operator": "lte", - "Value": 90 - }, - { - "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": [ diff --git a/prowler/compliance/aws/nis2_aws.json b/prowler/compliance/aws/nis2_aws.json index 31e577ab893..3a4d567d256 100644 --- a/prowler/compliance/aws/nis2_aws.json +++ b/prowler/compliance/aws/nis2_aws.json @@ -50,14 +50,6 @@ "SubSection": "1.1 Policy on the security of network and information systems", "Service": "generic" } - ], - "ConfigRequirements": [ - { - "Check": "secretsmanager_secret_rotated_periodically", - "ConfigKey": "max_days_secret_unrotated", - "Operator": "lte", - "Value": 90 - } ] }, { @@ -74,20 +66,6 @@ "SubSection": "1.1 Policy on the security of network and information systems", "Service": "generic" } - ], - "ConfigRequirements": [ - { - "Check": "cloudwatch_log_group_retention_policy_specific_days_enabled", - "ConfigKey": "log_group_retention_days", - "Operator": "gte", - "Value": 365 - }, - { - "Check": "kinesis_stream_data_retention_period", - "ConfigKey": "min_kinesis_stream_retention_hours", - "Operator": "gte", - "Value": 168 - } ] }, { @@ -748,26 +726,6 @@ "SubSection": "3.4 Event assessment and classification", "Service": "cloudtrail" } - ], - "ConfigRequirements": [ - { - "Check": "cloudtrail_threat_detection_enumeration", - "ConfigKey": "threat_detection_enumeration_threshold", - "Operator": "lte", - "Value": 0.3 - }, - { - "Check": "cloudtrail_threat_detection_llm_jacking", - "ConfigKey": "threat_detection_llm_jacking_threshold", - "Operator": "lte", - "Value": 0.4 - }, - { - "Check": "cloudtrail_threat_detection_privilege_escalation", - "ConfigKey": "threat_detection_privilege_escalation_threshold", - "Operator": "lte", - "Value": 0.2 - } ] }, { @@ -898,26 +856,6 @@ "SubSection": "3.6 Post-incident reviews", "Service": "generic" } - ], - "ConfigRequirements": [ - { - "Check": "documentdb_cluster_backup_enabled", - "ConfigKey": "minimum_backup_retention_period", - "Operator": "gte", - "Value": 7 - }, - { - "Check": "elasticache_redis_cluster_backup_enabled", - "ConfigKey": "minimum_snapshot_retention_period", - "Operator": "gte", - "Value": 7 - }, - { - "Check": "neptune_cluster_backup_enabled", - "ConfigKey": "minimum_backup_retention_period", - "Operator": "gte", - "Value": 7 - } ] }, { @@ -1657,14 +1595,6 @@ "SubSection": "9.2 Cryptography", "Service": "secretsmanager" } - ], - "ConfigRequirements": [ - { - "Check": "secretsmanager_secret_unused", - "ConfigKey": "max_days_secret_unused", - "Operator": "lte", - "Value": 90 - } ] }, { @@ -1946,14 +1876,6 @@ "SubSection": "11.3 Privileged accounts and system administration accounts", "Service": "iam" } - ], - "ConfigRequirements": [ - { - "Check": "iam_user_console_access_unused", - "ConfigKey": "max_console_access_days", - "Operator": "lte", - "Value": 45 - } ] }, { @@ -2055,32 +1977,6 @@ "SubSection": "11.5 Identification", "Service": "iam" } - ], - "ConfigRequirements": [ - { - "Check": "iam_role_access_not_stale_to_bedrock", - "ConfigKey": "max_unused_bedrock_access_days", - "Operator": "lte", - "Value": 60 - }, - { - "Check": "iam_user_access_not_stale_to_bedrock", - "ConfigKey": "max_unused_bedrock_access_days", - "Operator": "lte", - "Value": 60 - }, - { - "Check": "iam_user_access_not_stale_to_sagemaker", - "ConfigKey": "max_unused_sagemaker_access_days", - "Operator": "lte", - "Value": 90 - }, - { - "Check": "iam_user_accesskey_unused", - "ConfigKey": "max_unused_access_keys_days", - "Operator": "lte", - "Value": 45 - } ] }, { @@ -2166,26 +2062,6 @@ "SubSection": "11.6 Authentication", "Service": "appstream" } - ], - "ConfigRequirements": [ - { - "Check": "appstream_fleet_maximum_session_duration", - "ConfigKey": "max_session_duration_seconds", - "Operator": "lte", - "Value": 36000 - }, - { - "Check": "appstream_fleet_session_disconnect_timeout", - "ConfigKey": "max_disconnect_timeout_in_seconds", - "Operator": "lte", - "Value": 300 - }, - { - "Check": "appstream_fleet_session_idle_disconnect_timeout", - "ConfigKey": "max_idle_disconnect_timeout_in_seconds", - "Operator": "lte", - "Value": 600 - } ] }, { 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 5bb5c604c53..921bd33a537 100644 --- a/prowler/compliance/aws/nist_800_171_revision_2_aws.json +++ b/prowler/compliance/aws/nist_800_171_revision_2_aws.json @@ -48,38 +48,6 @@ "ec2_networkacl_allow_ingress_any_port", "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_22", "ec2_networkacl_allow_ingress_any_port" - ], - "ConfigRequirements": [ - { - "Check": "iam_role_access_not_stale_to_bedrock", - "ConfigKey": "max_unused_bedrock_access_days", - "Operator": "lte", - "Value": 60 - }, - { - "Check": "iam_user_access_not_stale_to_bedrock", - "ConfigKey": "max_unused_bedrock_access_days", - "Operator": "lte", - "Value": 60 - }, - { - "Check": "iam_user_access_not_stale_to_sagemaker", - "ConfigKey": "max_unused_sagemaker_access_days", - "Operator": "lte", - "Value": 90 - }, - { - "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 - } ] }, { @@ -380,12 +348,6 @@ "ConfigKey": "mute_non_default_regions", "Operator": "eq", "Value": false - }, - { - "Check": "cloudwatch_log_group_retention_policy_specific_days_enabled", - "ConfigKey": "log_group_retention_days", - "Operator": "gte", - "Value": 365 } ] }, @@ -545,14 +507,6 @@ "ssm_managed_compliant_patching", "ec2_elastic_ip_unassigned", "ec2_networkacl_allow_ingress_any_port" - ], - "ConfigRequirements": [ - { - "Check": "ec2_instance_older_than_specific_days", - "ConfigKey": "max_ec2_instance_age_in_days", - "Operator": "lte", - "Value": 180 - } ] }, { 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 7caa64fc8b0..8bd36a3910e 100644 --- a/prowler/compliance/aws/nist_800_53_revision_4_aws.json +++ b/prowler/compliance/aws/nist_800_53_revision_4_aws.json @@ -615,14 +615,6 @@ ], "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 - } ] }, { @@ -706,14 +698,6 @@ "elbv2_deletion_protection", "ssm_managed_compliant_patching", "ec2_networkacl_allow_ingress_any_port" - ], - "ConfigRequirements": [ - { - "Check": "ec2_instance_older_than_specific_days", - "ConfigKey": "max_ec2_instance_age_in_days", - "Operator": "lte", - "Value": 180 - } ] }, { 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 69da4dab7d8..e0ef9362292 100644 --- a/prowler/compliance/aws/nist_800_53_revision_5_aws.json +++ b/prowler/compliance/aws/nist_800_53_revision_5_aws.json @@ -35,38 +35,6 @@ "iam_user_accesskey_unused", "iam_user_console_access_unused", "secretsmanager_automatic_rotation_enabled" - ], - "ConfigRequirements": [ - { - "Check": "iam_role_access_not_stale_to_bedrock", - "ConfigKey": "max_unused_bedrock_access_days", - "Operator": "lte", - "Value": 60 - }, - { - "Check": "iam_user_access_not_stale_to_bedrock", - "ConfigKey": "max_unused_bedrock_access_days", - "Operator": "lte", - "Value": 60 - }, - { - "Check": "iam_user_access_not_stale_to_sagemaker", - "ConfigKey": "max_unused_sagemaker_access_days", - "Operator": "lte", - "Value": 90 - }, - { - "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 - } ] }, { @@ -1406,14 +1374,6 @@ ], "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 - } ] }, { @@ -2755,14 +2715,6 @@ "elbv2_deletion_protection", "ssm_managed_compliant_patching", "ec2_networkacl_allow_ingress_any_port" - ], - "ConfigRequirements": [ - { - "Check": "ec2_instance_older_than_specific_days", - "ConfigKey": "max_ec2_instance_age_in_days", - "Operator": "lte", - "Value": 180 - } ] }, { diff --git a/prowler/compliance/aws/nist_csf_1.1_aws.json b/prowler/compliance/aws/nist_csf_1.1_aws.json index a9cd04f0ac6..9921efce568 100644 --- a/prowler/compliance/aws/nist_csf_1.1_aws.json +++ b/prowler/compliance/aws/nist_csf_1.1_aws.json @@ -817,38 +817,6 @@ "iam_user_accesskey_unused", "iam_user_console_access_unused", "secretsmanager_automatic_rotation_enabled" - ], - "ConfigRequirements": [ - { - "Check": "iam_role_access_not_stale_to_bedrock", - "ConfigKey": "max_unused_bedrock_access_days", - "Operator": "lte", - "Value": 60 - }, - { - "Check": "iam_user_access_not_stale_to_bedrock", - "ConfigKey": "max_unused_bedrock_access_days", - "Operator": "lte", - "Value": 60 - }, - { - "Check": "iam_user_access_not_stale_to_sagemaker", - "ConfigKey": "max_unused_sagemaker_access_days", - "Operator": "lte", - "Value": 90 - }, - { - "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 - } ] }, { @@ -1146,14 +1114,6 @@ "elbv2_deletion_protection", "ssm_managed_compliant_patching", "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_22" - ], - "ConfigRequirements": [ - { - "Check": "ec2_instance_older_than_specific_days", - "ConfigKey": "max_ec2_instance_age_in_days", - "Operator": "lte", - "Value": 180 - } ] }, { diff --git a/prowler/compliance/aws/nist_csf_2.0_aws.json b/prowler/compliance/aws/nist_csf_2.0_aws.json index df9ae9f280b..c06eeec11d3 100644 --- a/prowler/compliance/aws/nist_csf_2.0_aws.json +++ b/prowler/compliance/aws/nist_csf_2.0_aws.json @@ -405,24 +405,6 @@ "ConfigKey": "mute_non_default_regions", "Operator": "eq", "Value": false - }, - { - "Check": "cloudtrail_threat_detection_enumeration", - "ConfigKey": "threat_detection_enumeration_threshold", - "Operator": "lte", - "Value": 0.3 - }, - { - "Check": "cloudtrail_threat_detection_llm_jacking", - "ConfigKey": "threat_detection_llm_jacking_threshold", - "Operator": "lte", - "Value": 0.4 - }, - { - "Check": "cloudtrail_threat_detection_privilege_escalation", - "ConfigKey": "threat_detection_privilege_escalation_threshold", - "Operator": "lte", - "Value": 0.2 } ] }, @@ -858,36 +840,6 @@ "ConfigKey": "mute_non_default_regions", "Operator": "eq", "Value": false - }, - { - "Check": "iam_role_access_not_stale_to_bedrock", - "ConfigKey": "max_unused_bedrock_access_days", - "Operator": "lte", - "Value": 60 - }, - { - "Check": "iam_user_access_not_stale_to_bedrock", - "ConfigKey": "max_unused_bedrock_access_days", - "Operator": "lte", - "Value": 60 - }, - { - "Check": "iam_user_access_not_stale_to_sagemaker", - "ConfigKey": "max_unused_sagemaker_access_days", - "Operator": "lte", - "Value": 90 - }, - { - "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 } ] }, @@ -1367,26 +1319,6 @@ "rds_cluster_protected_by_backup_plan", "rds_instance_backup_enabled", "rds_instance_protected_by_backup_plan" - ], - "ConfigRequirements": [ - { - "Check": "documentdb_cluster_backup_enabled", - "ConfigKey": "minimum_backup_retention_period", - "Operator": "gte", - "Value": 7 - }, - { - "Check": "elasticache_redis_cluster_backup_enabled", - "ConfigKey": "minimum_snapshot_retention_period", - "Operator": "gte", - "Value": 7 - }, - { - "Check": "neptune_cluster_backup_enabled", - "ConfigKey": "minimum_backup_retention_period", - "Operator": "gte", - "Value": 7 - } ] }, { @@ -1422,12 +1354,6 @@ "ConfigKey": "mute_non_default_regions", "Operator": "eq", "Value": false - }, - { - "Check": "secretsmanager_secret_rotated_periodically", - "ConfigKey": "max_days_secret_unrotated", - "Operator": "lte", - "Value": 90 } ] }, diff --git a/prowler/compliance/aws/pci_3.2.1_aws.json b/prowler/compliance/aws/pci_3.2.1_aws.json index 6f3fd6b2bb4..ca8e968bf9f 100644 --- a/prowler/compliance/aws/pci_3.2.1_aws.json +++ b/prowler/compliance/aws/pci_3.2.1_aws.json @@ -689,14 +689,6 @@ "Section": "Requirement 3: Protect stored cardholder data", "SubSection": "Requirement 3: Protect stored cardholder data" } - ], - "ConfigRequirements": [ - { - "Check": "elasticache_redis_cluster_backup_enabled", - "ConfigKey": "minimum_snapshot_retention_period", - "Operator": "gte", - "Value": 7 - } ] }, { @@ -1563,20 +1555,6 @@ "Section": "Requirement 8: Identify and authenticate access to system components", "SubSection": "Requirement 8: Identify and authenticate access to system components" } - ], - "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 - } ] }, { @@ -1612,26 +1590,6 @@ "Section": "Requirement 8: Identify and authenticate access to system components", "SubSection": "8.1 Define and implement policies and procedures to ensure proper user identification management for non-consumer users and administrators" } - ], - "ConfigRequirements": [ - { - "Check": "iam_role_access_not_stale_to_bedrock", - "ConfigKey": "max_unused_bedrock_access_days", - "Operator": "lte", - "Value": 60 - }, - { - "Check": "iam_user_access_not_stale_to_bedrock", - "ConfigKey": "max_unused_bedrock_access_days", - "Operator": "lte", - "Value": 60 - }, - { - "Check": "iam_user_access_not_stale_to_sagemaker", - "ConfigKey": "max_unused_sagemaker_access_days", - "Operator": "lte", - "Value": 90 - } ] }, { @@ -2160,14 +2118,6 @@ "Section": "Requirement 10: Track and monitor all access to network resources and cardholder data", "SubSection": "Requirement 10: Track and monitor all access to network resources and cardholder data" } - ], - "ConfigRequirements": [ - { - "Check": "cloudwatch_log_group_retention_policy_specific_days_enabled", - "ConfigKey": "log_group_retention_days", - "Operator": "gte", - "Value": 365 - } ] }, { diff --git a/prowler/compliance/aws/pci_4.0_aws.json b/prowler/compliance/aws/pci_4.0_aws.json index 4404386be1e..e21b5435568 100644 --- a/prowler/compliance/aws/pci_4.0_aws.json +++ b/prowler/compliance/aws/pci_4.0_aws.json @@ -9000,14 +9000,6 @@ "Section": "10.3.3: Audit logs are protected from destruction and unauthorized modifications. ", "Service": "elasticache" } - ], - "ConfigRequirements": [ - { - "Check": "elasticache_redis_cluster_backup_enabled", - "ConfigKey": "minimum_snapshot_retention_period", - "Operator": "gte", - "Value": 7 - } ] }, { @@ -9036,14 +9028,6 @@ "Section": "10.3.3: Audit logs are protected from destruction and unauthorized modifications. ", "Service": "neptune" } - ], - "ConfigRequirements": [ - { - "Check": "neptune_cluster_backup_enabled", - "ConfigKey": "minimum_backup_retention_period", - "Operator": "gte", - "Value": 7 - } ] }, { @@ -9626,14 +9610,6 @@ "Section": "10.5.1: Audit log history is retained and available for analysis. ", "Service": "documentdb" } - ], - "ConfigRequirements": [ - { - "Check": "documentdb_cluster_backup_enabled", - "ConfigKey": "minimum_backup_retention_period", - "Operator": "gte", - "Value": 7 - } ] }, { @@ -17053,14 +17029,6 @@ "Section": "7.2.4: Access to system components and data is appropriately defined and assigned. ", "Service": "iam" } - ], - "ConfigRequirements": [ - { - "Check": "iam_user_accesskey_unused", - "ConfigKey": "max_unused_access_keys_days", - "Operator": "lte", - "Value": 45 - } ] }, { @@ -17075,14 +17043,6 @@ "Section": "7.2.4: Access to system components and data is appropriately defined and assigned. ", "Service": "secretsmanager" } - ], - "ConfigRequirements": [ - { - "Check": "secretsmanager_secret_unused", - "ConfigKey": "max_days_secret_unused", - "Operator": "lte", - "Value": 90 - } ] }, { diff --git a/prowler/compliance/aws/prowler_threatscore_aws.json b/prowler/compliance/aws/prowler_threatscore_aws.json index 25c2dfae16b..c8c093907a4 100644 --- a/prowler/compliance/aws/prowler_threatscore_aws.json +++ b/prowler/compliance/aws/prowler_threatscore_aws.json @@ -1555,14 +1555,6 @@ "LevelOfRisk": 2, "Weight": 8 } - ], - "ConfigRequirements": [ - { - "Check": "cloudwatch_log_group_retention_policy_specific_days_enabled", - "ConfigKey": "log_group_retention_days", - "Operator": "gte", - "Value": 365 - } ] }, { diff --git a/prowler/compliance/aws/rbi_cyber_security_framework_aws.json b/prowler/compliance/aws/rbi_cyber_security_framework_aws.json index af8aca27033..5de1f5ca8a2 100644 --- a/prowler/compliance/aws/rbi_cyber_security_framework_aws.json +++ b/prowler/compliance/aws/rbi_cyber_security_framework_aws.json @@ -192,12 +192,6 @@ "ConfigKey": "mute_non_default_regions", "Operator": "eq", "Value": false - }, - { - "Check": "cloudwatch_log_group_retention_policy_specific_days_enabled", - "ConfigKey": "log_group_retention_days", - "Operator": "gte", - "Value": 365 } ] }, diff --git a/prowler/compliance/aws/secnumcloud_3.2_aws.json b/prowler/compliance/aws/secnumcloud_3.2_aws.json index edb21502c5c..701f931b05c 100644 --- a/prowler/compliance/aws/secnumcloud_3.2_aws.json +++ b/prowler/compliance/aws/secnumcloud_3.2_aws.json @@ -307,38 +307,6 @@ "iam_user_accesskey_unused", "iam_user_console_access_unused", "iam_no_expired_server_certificates_stored" - ], - "ConfigRequirements": [ - { - "Check": "iam_role_access_not_stale_to_bedrock", - "ConfigKey": "max_unused_bedrock_access_days", - "Operator": "lte", - "Value": 60 - }, - { - "Check": "iam_user_access_not_stale_to_bedrock", - "ConfigKey": "max_unused_bedrock_access_days", - "Operator": "lte", - "Value": 60 - }, - { - "Check": "iam_user_access_not_stale_to_sagemaker", - "ConfigKey": "max_unused_sagemaker_access_days", - "Operator": "lte", - "Value": 90 - }, - { - "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 - } ] }, { @@ -594,20 +562,6 @@ "codebuild_project_no_secrets_in_variables", "ssm_document_secrets", "cloudwatch_log_group_no_secrets_in_logs" - ], - "ConfigRequirements": [ - { - "Check": "secretsmanager_secret_rotated_periodically", - "ConfigKey": "max_days_secret_unrotated", - "Operator": "lte", - "Value": 90 - }, - { - "Check": "secretsmanager_secret_unused", - "ConfigKey": "max_days_secret_unused", - "Operator": "lte", - "Value": 90 - } ] }, { @@ -895,26 +849,6 @@ "documentdb_cluster_backup_enabled", "elasticache_redis_cluster_backup_enabled", "redshift_cluster_automated_snapshot" - ], - "ConfigRequirements": [ - { - "Check": "documentdb_cluster_backup_enabled", - "ConfigKey": "minimum_backup_retention_period", - "Operator": "gte", - "Value": 7 - }, - { - "Check": "elasticache_redis_cluster_backup_enabled", - "ConfigKey": "minimum_snapshot_retention_period", - "Operator": "gte", - "Value": 7 - }, - { - "Check": "neptune_cluster_backup_enabled", - "ConfigKey": "minimum_backup_retention_period", - "Operator": "gte", - "Value": 7 - } ] }, { @@ -951,14 +885,6 @@ "codebuild_project_logging_enabled", "ecs_task_definitions_logging_enabled", "glue_etl_jobs_logging_enabled" - ], - "ConfigRequirements": [ - { - "Check": "cloudwatch_log_group_retention_policy_specific_days_enabled", - "ConfigKey": "log_group_retention_days", - "Operator": "gte", - "Value": 365 - } ] }, { diff --git a/prowler/compliance/aws/soc2_aws.json b/prowler/compliance/aws/soc2_aws.json index 291e849796c..c0041a9daeb 100644 --- a/prowler/compliance/aws/soc2_aws.json +++ b/prowler/compliance/aws/soc2_aws.json @@ -24,20 +24,6 @@ "iam_inline_policy_no_administrative_privileges", "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 - } ] }, { @@ -150,26 +136,6 @@ "cloudtrail_threat_detection_privilege_escalation", "cloudtrail_threat_detection_enumeration", "cloudtrail_threat_detection_llm_jacking" - ], - "ConfigRequirements": [ - { - "Check": "cloudtrail_threat_detection_enumeration", - "ConfigKey": "threat_detection_enumeration_threshold", - "Operator": "lte", - "Value": 0.3 - }, - { - "Check": "cloudtrail_threat_detection_llm_jacking", - "ConfigKey": "threat_detection_llm_jacking_threshold", - "Operator": "lte", - "Value": 0.4 - }, - { - "Check": "cloudtrail_threat_detection_privilege_escalation", - "ConfigKey": "threat_detection_privilege_escalation_threshold", - "Operator": "lte", - "Value": 0.2 - } ] }, { @@ -494,12 +460,6 @@ "ConfigKey": "mute_non_default_regions", "Operator": "eq", "Value": false - }, - { - "Check": "cloudwatch_log_group_retention_policy_specific_days_enabled", - "ConfigKey": "log_group_retention_days", - "Operator": "gte", - "Value": 365 } ] }, @@ -720,14 +680,6 @@ "s3_bucket_object_versioning", "cloudwatch_log_group_retention_policy_specific_days_enabled", "kinesis_stream_data_retention_period" - ], - "ConfigRequirements": [ - { - "Check": "kinesis_stream_data_retention_period", - "ConfigKey": "min_kinesis_stream_retention_hours", - "Operator": "gte", - "Value": 168 - } ] }, { diff --git a/prowler/compliance/azure/c5_azure.json b/prowler/compliance/azure/c5_azure.json index a19a89e6e5a..6fff7a36915 100644 --- a/prowler/compliance/azure/c5_azure.json +++ b/prowler/compliance/azure/c5_azure.json @@ -6845,14 +6845,6 @@ ], "Checks": [ "apim_threat_detection_llm_jacking" - ], - "ConfigRequirements": [ - { - "Check": "apim_threat_detection_llm_jacking", - "ConfigKey": "apim_threat_detection_llm_jacking_threshold", - "Operator": "lte", - "Value": 0.1 - } ] }, { diff --git a/prowler/compliance/azure/cis_4.0_azure.json b/prowler/compliance/azure/cis_4.0_azure.json index 2e3a311aca9..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" + ] + } ] }, { diff --git a/prowler/compliance/azure/cis_5.0_azure.json b/prowler/compliance/azure/cis_5.0_azure.json index dce5f299fea..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" + ] + } ] }, { diff --git a/prowler/compliance/csa_ccm_4.0.json b/prowler/compliance/csa_ccm_4.0.json index 46a6797bef2..1cb7428e517 100644 --- a/prowler/compliance/csa_ccm_4.0.json +++ b/prowler/compliance/csa_ccm_4.0.json @@ -914,30 +914,7 @@ "oraclecloud": [ "objectstorage_bucket_versioning_enabled" ] - }, - "config_requirements": [ - { - "Check": "documentdb_cluster_backup_enabled", - "ConfigKey": "minimum_backup_retention_period", - "Operator": "gte", - "Value": 7, - "Provider": "aws" - }, - { - "Check": "elasticache_redis_cluster_backup_enabled", - "ConfigKey": "minimum_snapshot_retention_period", - "Operator": "gte", - "Value": 7, - "Provider": "aws" - }, - { - "Check": "neptune_cluster_backup_enabled", - "ConfigKey": "minimum_backup_retention_period", - "Operator": "gte", - "Value": 7, - "Provider": "aws" - } - ] + } }, { "id": "BCR-09", @@ -2233,16 +2210,7 @@ "identity_user_customer_secret_keys_rotated_90_days", "identity_user_db_passwords_rotated_90_days" ] - }, - "config_requirements": [ - { - "Check": "secretsmanager_secret_rotated_periodically", - "ConfigKey": "max_days_secret_unrotated", - "Operator": "lte", - "Value": 90, - "Provider": "aws" - } - ] + } }, { "id": "CEK-14", @@ -3284,44 +3252,7 @@ "oraclecloud": [ "audit_log_retention_period_365_days" ] - }, - "config_requirements": [ - { - "Check": "cloudstorage_bucket_sufficient_retention_period", - "ConfigKey": "storage_min_retention_days", - "Operator": "gte", - "Value": 90, - "Provider": "gcp" - }, - { - "Check": "cloudwatch_log_group_retention_policy_specific_days_enabled", - "ConfigKey": "log_group_retention_days", - "Operator": "gte", - "Value": 365, - "Provider": "aws" - }, - { - "Check": "kinesis_stream_data_retention_period", - "ConfigKey": "min_kinesis_stream_retention_hours", - "Operator": "gte", - "Value": 168, - "Provider": "aws" - }, - { - "Check": "rds_instance_sql_audit_retention", - "ConfigKey": "min_rds_audit_retention_days", - "Operator": "gte", - "Value": 180, - "Provider": "alibabacloud" - }, - { - "Check": "sls_logstore_retention_period", - "ConfigKey": "min_log_retention_days", - "Operator": "gte", - "Value": 365, - "Provider": "alibabacloud" - } - ] + } }, { "id": "DSP-17", @@ -3928,44 +3859,7 @@ "identity_user_customer_secret_keys_rotated_90_days", "identity_user_valid_email_address" ] - }, - "config_requirements": [ - { - "Check": "iam_sa_user_managed_key_unused", - "ConfigKey": "max_unused_account_days", - "Operator": "lte", - "Value": 180, - "Provider": "gcp" - }, - { - "Check": "iam_service_account_unused", - "ConfigKey": "max_unused_account_days", - "Operator": "lte", - "Value": 180, - "Provider": "gcp" - }, - { - "Check": "iam_user_accesskey_unused", - "ConfigKey": "max_unused_access_keys_days", - "Operator": "lte", - "Value": 45, - "Provider": "aws" - }, - { - "Check": "iam_user_console_access_unused", - "ConfigKey": "max_console_access_days", - "Operator": "lte", - "Value": 45, - "Provider": "aws" - }, - { - "Check": "ram_user_console_access_unused", - "ConfigKey": "max_console_access_days", - "Operator": "lte", - "Value": 45, - "Provider": "alibabacloud" - } - ] + } }, { "id": "IAM-04", @@ -4491,16 +4385,7 @@ "identity_user_customer_secret_keys_rotated_90_days", "identity_user_db_passwords_rotated_90_days" ] - }, - "config_requirements": [ - { - "Check": "secretsmanager_secret_unused", - "ConfigKey": "max_days_secret_unused", - "Operator": "lte", - "Value": 90, - "Provider": "aws" - } - ] + } }, { "id": "IAM-09", diff --git a/prowler/compliance/dora_2022_2554.json b/prowler/compliance/dora_2022_2554.json index faa006275cb..3b828059da0 100644 --- a/prowler/compliance/dora_2022_2554.json +++ b/prowler/compliance/dora_2022_2554.json @@ -161,23 +161,7 @@ "ram_policy_attached_only_to_group_or_roles", "ram_policy_no_administrative_privileges" ] - }, - "config_requirements": [ - { - "Check": "iam_service_account_unused", - "ConfigKey": "max_unused_account_days", - "Operator": "lte", - "Value": 180, - "Provider": "gcp" - }, - { - "Check": "ram_user_console_access_unused", - "ConfigKey": "max_console_access_days", - "Operator": "lte", - "Value": 45, - "Provider": "alibabacloud" - } - ] + } }, { "id": "DORA-Art6", @@ -429,20 +413,6 @@ "ConfigKey": "mute_non_default_regions", "Operator": "eq", "Value": false - }, - { - "Check": "iam_sa_user_managed_key_unused", - "ConfigKey": "max_unused_account_days", - "Operator": "lte", - "Value": 180, - "Provider": "gcp" - }, - { - "Check": "secretsmanager_secret_unused", - "ConfigKey": "max_days_secret_unused", - "Operator": "lte", - "Value": 90, - "Provider": "aws" } ] }, @@ -695,34 +665,6 @@ "ConfigKey": "mute_non_default_regions", "Operator": "eq", "Value": false - }, - { - "Check": "apim_threat_detection_llm_jacking", - "ConfigKey": "apim_threat_detection_llm_jacking_threshold", - "Operator": "lte", - "Value": 0.1, - "Provider": "azure" - }, - { - "Check": "cloudtrail_threat_detection_enumeration", - "ConfigKey": "threat_detection_enumeration_threshold", - "Operator": "lte", - "Value": 0.3, - "Provider": "aws" - }, - { - "Check": "cloudtrail_threat_detection_llm_jacking", - "ConfigKey": "threat_detection_llm_jacking_threshold", - "Operator": "lte", - "Value": 0.4, - "Provider": "aws" - }, - { - "Check": "cloudtrail_threat_detection_privilege_escalation", - "ConfigKey": "threat_detection_privilege_escalation_threshold", - "Operator": "lte", - "Value": 0.2, - "Provider": "aws" } ] }, @@ -772,23 +714,7 @@ "cs_kubernetes_cluster_check_recent", "cs_kubernetes_cluster_check_weekly" ] - }, - "config_requirements": [ - { - "Check": "cs_kubernetes_cluster_check_recent", - "ConfigKey": "max_cluster_check_days", - "Operator": "lte", - "Value": 7, - "Provider": "alibabacloud" - }, - { - "Check": "cs_kubernetes_cluster_check_weekly", - "ConfigKey": "max_cluster_check_days", - "Operator": "lte", - "Value": 7, - "Provider": "alibabacloud" - } - ] + } }, { "id": "DORA-Art12", @@ -852,23 +778,7 @@ "compute_snapshot_not_outdated", "compute_instance_suspended_without_persistent_disks" ] - }, - "config_requirements": [ - { - "Check": "cloudstorage_bucket_sufficient_retention_period", - "ConfigKey": "storage_min_retention_days", - "Operator": "gte", - "Value": 90, - "Provider": "gcp" - }, - { - "Check": "compute_snapshot_not_outdated", - "ConfigKey": "max_snapshot_age_days", - "Operator": "lte", - "Value": 90, - "Provider": "gcp" - } - ] + } }, { "id": "DORA-Art13", @@ -1043,30 +953,7 @@ "rds_instance_postgresql_log_disconnections_enabled", "rds_instance_postgresql_log_duration_enabled" ] - }, - "config_requirements": [ - { - "Check": "cloudwatch_log_group_retention_policy_specific_days_enabled", - "ConfigKey": "log_group_retention_days", - "Operator": "gte", - "Value": 365, - "Provider": "aws" - }, - { - "Check": "rds_instance_sql_audit_retention", - "ConfigKey": "min_rds_audit_retention_days", - "Operator": "gte", - "Value": 180, - "Provider": "alibabacloud" - }, - { - "Check": "sls_logstore_retention_period", - "ConfigKey": "min_log_retention_days", - "Operator": "gte", - "Value": 365, - "Provider": "alibabacloud" - } - ] + } }, { "id": "DORA-Art18", diff --git a/prowler/compliance/gcp/c5_gcp.json b/prowler/compliance/gcp/c5_gcp.json index 0d2f43ed01f..e9d8b15b176 100644 --- a/prowler/compliance/gcp/c5_gcp.json +++ b/prowler/compliance/gcp/c5_gcp.json @@ -80,14 +80,6 @@ "iam_role_kms_enforce_separation_of_duties", "iam_service_account_unused", "iam_sa_no_user_managed_keys" - ], - "ConfigRequirements": [ - { - "Check": "iam_service_account_unused", - "ConfigKey": "max_unused_account_days", - "Operator": "lte", - "Value": 180 - } ] }, { @@ -2725,14 +2717,6 @@ "iam_sa_user_managed_key_unused", "iam_service_account_unused", "iam_sa_no_administrative_privileges" - ], - "ConfigRequirements": [ - { - "Check": "iam_sa_user_managed_key_unused", - "ConfigKey": "max_unused_account_days", - "Operator": "lte", - "Value": 180 - } ] }, { diff --git a/prowler/compliance/gcp/fedramp_20x_ksi_low_gcp.json b/prowler/compliance/gcp/fedramp_20x_ksi_low_gcp.json index a7df7403655..5638b0c51e9 100644 --- a/prowler/compliance/gcp/fedramp_20x_ksi_low_gcp.json +++ b/prowler/compliance/gcp/fedramp_20x_ksi_low_gcp.json @@ -87,20 +87,6 @@ "iam_sa_user_managed_key_rotate_90_days", "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": 180 - }, - { - "Check": "iam_service_account_unused", - "ConfigKey": "max_unused_account_days", - "Operator": "lte", - "Value": 180 - } ] }, { diff --git a/prowler/compliance/gcp/hipaa_gcp.json b/prowler/compliance/gcp/hipaa_gcp.json index 9794eae1c22..37d9838e591 100644 --- a/prowler/compliance/gcp/hipaa_gcp.json +++ b/prowler/compliance/gcp/hipaa_gcp.json @@ -96,14 +96,6 @@ "iam_role_sa_enforce_separation_of_duties", "iam_sa_no_user_managed_keys", "iam_sa_user_managed_key_unused" - ], - "ConfigRequirements": [ - { - "Check": "iam_sa_user_managed_key_unused", - "ConfigKey": "max_unused_account_days", - "Operator": "lte", - "Value": 180 - } ] }, { diff --git a/prowler/compliance/gcp/mitre_attack_gcp.json b/prowler/compliance/gcp/mitre_attack_gcp.json index 274667f4cc4..22133caca80 100644 --- a/prowler/compliance/gcp/mitre_attack_gcp.json +++ b/prowler/compliance/gcp/mitre_attack_gcp.json @@ -290,20 +290,6 @@ "Value": "Significant", "Comment": "This control is able to mitigate against abuse of compromised valid accounts by restricting access from those accounts to resources contained within the VPC perimeter the account belongs to. Resources and services contained in other VPC networks also cannot be accessed by user accounts that are not within the VPC network perimeter." } - ], - "ConfigRequirements": [ - { - "Check": "iam_sa_user_managed_key_unused", - "ConfigKey": "max_unused_account_days", - "Operator": "lte", - "Value": 180 - }, - { - "Check": "iam_service_account_unused", - "ConfigKey": "max_unused_account_days", - "Operator": "lte", - "Value": 180 - } ] }, { diff --git a/prowler/compliance/gcp/nis2_gcp.json b/prowler/compliance/gcp/nis2_gcp.json index 7d72bc931e9..14d5477dc0d 100644 --- a/prowler/compliance/gcp/nis2_gcp.json +++ b/prowler/compliance/gcp/nis2_gcp.json @@ -704,20 +704,6 @@ "SubSection": "6.2 Secure development life cycle", "Service": "generic" } - ], - "ConfigRequirements": [ - { - "Check": "iam_sa_user_managed_key_unused", - "ConfigKey": "max_unused_account_days", - "Operator": "lte", - "Value": 180 - }, - { - "Check": "iam_service_account_unused", - "ConfigKey": "max_unused_account_days", - "Operator": "lte", - "Value": 180 - } ] }, { diff --git a/prowler/compliance/gcp/rbi_cyber_security_framework_gcp.json b/prowler/compliance/gcp/rbi_cyber_security_framework_gcp.json index 7482ada1bcc..e86aee6e632 100644 --- a/prowler/compliance/gcp/rbi_cyber_security_framework_gcp.json +++ b/prowler/compliance/gcp/rbi_cyber_security_framework_gcp.json @@ -172,14 +172,6 @@ "cloudstorage_bucket_soft_delete_enabled", "cloudstorage_bucket_lifecycle_management_enabled", "compute_snapshot_not_outdated" - ], - "ConfigRequirements": [ - { - "Check": "compute_snapshot_not_outdated", - "ConfigKey": "max_snapshot_age_days", - "Operator": "lte", - "Value": 90 - } ] } ] diff --git a/prowler/compliance/gcp/secnumcloud_3.2_gcp.json b/prowler/compliance/gcp/secnumcloud_3.2_gcp.json index b5d9cd65d83..e709545da43 100644 --- a/prowler/compliance/gcp/secnumcloud_3.2_gcp.json +++ b/prowler/compliance/gcp/secnumcloud_3.2_gcp.json @@ -292,20 +292,6 @@ "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": 180 - }, - { - "Check": "iam_service_account_unused", - "ConfigKey": "max_unused_account_days", - "Operator": "lte", - "Value": 180 - } ] }, { @@ -765,14 +751,6 @@ "cloudsql_instance_postgres_log_statement_flag", "cloudsql_instance_postgres_log_min_error_statement_flag", "cloudsql_instance_postgres_log_min_messages_flag" - ], - "ConfigRequirements": [ - { - "Check": "cloudstorage_bucket_sufficient_retention_period", - "ConfigKey": "storage_min_retention_days", - "Operator": "gte", - "Value": 90 - } ] }, { diff --git a/prowler/compliance/gcp/soc2_gcp.json b/prowler/compliance/gcp/soc2_gcp.json index 1ae24c004ef..8450967e298 100644 --- a/prowler/compliance/gcp/soc2_gcp.json +++ b/prowler/compliance/gcp/soc2_gcp.json @@ -27,14 +27,6 @@ "iam_sa_no_administrative_privileges", "iam_sa_no_user_managed_keys", "iam_sa_user_managed_key_unused" - ], - "ConfigRequirements": [ - { - "Check": "iam_sa_user_managed_key_unused", - "ConfigKey": "max_unused_account_days", - "Operator": "lte", - "Value": 180 - } ] }, { diff --git a/prowler/compliance/github/cis_1.0_github.json b/prowler/compliance/github/cis_1.0_github.json index 733fca9b5bd..2a60df6bcd3 100644 --- a/prowler/compliance/github/cis_1.0_github.json +++ b/prowler/compliance/github/cis_1.0_github.json @@ -182,14 +182,6 @@ "References": "", "DefaultValue": "By default, newly opened Git branches would never be removed, regardless of activity or inactivity." } - ], - "ConfigRequirements": [ - { - "Check": "repository_inactive_not_archived", - "ConfigKey": "inactive_not_archived_days_threshold", - "Operator": "lte", - "Value": 180 - } ] }, { diff --git a/prowler/compliance/kubernetes/cis_1.10_kubernetes.json b/prowler/compliance/kubernetes/cis_1.10_kubernetes.json index ce6fe883002..ed0e90d88f7 100644 --- a/prowler/compliance/kubernetes/cis_1.10_kubernetes.json +++ b/prowler/compliance/kubernetes/cis_1.10_kubernetes.json @@ -1133,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" + ] + } ] }, { @@ -2093,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 89b0a6d8a3f..25d725683fb 100644 --- a/prowler/compliance/kubernetes/cis_1.11_kubernetes.json +++ b/prowler/compliance/kubernetes/cis_1.11_kubernetes.json @@ -1133,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" + ] + } ] }, { @@ -2136,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 494eb09597f..ded2d6944a8 100644 --- a/prowler/compliance/kubernetes/cis_1.12_kubernetes.json +++ b/prowler/compliance/kubernetes/cis_1.12_kubernetes.json @@ -1133,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" + ] + } ] }, { @@ -2114,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 ebabce6a984..a09d753f582 100644 --- a/prowler/compliance/kubernetes/cis_1.8_kubernetes.json +++ b/prowler/compliance/kubernetes/cis_1.8_kubernetes.json @@ -1156,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" + ] + } ] }, { @@ -2116,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/iso27001_2022_kubernetes.json b/prowler/compliance/kubernetes/iso27001_2022_kubernetes.json index e6b02b4425a..73f1b1266b3 100644 --- a/prowler/compliance/kubernetes/iso27001_2022_kubernetes.json +++ b/prowler/compliance/kubernetes/iso27001_2022_kubernetes.json @@ -1109,26 +1109,6 @@ "apiserver_audit_log_maxbackup_set", "apiserver_audit_log_maxsize_set", "apiserver_audit_log_path_set" - ], - "ConfigRequirements": [ - { - "Check": "apiserver_audit_log_maxage_set", - "ConfigKey": "audit_log_maxage", - "Operator": "gte", - "Value": 30 - }, - { - "Check": "apiserver_audit_log_maxbackup_set", - "ConfigKey": "audit_log_maxbackup", - "Operator": "gte", - "Value": 10 - }, - { - "Check": "apiserver_audit_log_maxsize_set", - "ConfigKey": "audit_log_maxsize", - "Operator": "gte", - "Value": 100 - } ] }, { diff --git a/prowler/compliance/kubernetes/prowler_threatscore_kubernetes.json b/prowler/compliance/kubernetes/prowler_threatscore_kubernetes.json index 50b2bd49358..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" + ] + } ] }, { diff --git a/prowler/compliance/m365/cis_4.0_m365.json b/prowler/compliance/m365/cis_4.0_m365.json index be495f6dcfd..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 + } ] }, { @@ -1812,14 +1882,6 @@ "References": "https://learn.microsoft.com/en-us/exchange/clients-and-mobile-in-exchange-online/mailtips/mailtips:https://learn.microsoft.com/en-us/powershell/module/exchange/set-organizationconfig?view=exchange-ps", "DefaultValue": "MailTipsAllTipsEnabled: TrueMailTipsExternalRecipientsTipsEnabled: FalseMailTipsGroupMetricsEnabled: TrueMailTipsLargeAudienceThreshold: 25" } - ], - "ConfigRequirements": [ - { - "Check": "exchange_organization_mailtips_enabled", - "ConfigKey": "recommended_mailtips_large_audience_threshold", - "Operator": "lte", - "Value": 25 - } ] }, { diff --git a/prowler/compliance/m365/cis_6.0_m365.json b/prowler/compliance/m365/cis_6.0_m365.json index 8e03865e15f..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 + } ] }, { diff --git a/prowler/compliance/m365/prowler_threatscore_m365.json b/prowler/compliance/m365/prowler_threatscore_m365.json index 9098f7a017b..f5a6cd1cd81 100644 --- a/prowler/compliance/m365/prowler_threatscore_m365.json +++ b/prowler/compliance/m365/prowler_threatscore_m365.json @@ -529,14 +529,6 @@ "LevelOfRisk": 2, "Weight": 8 } - ], - "ConfigRequirements": [ - { - "Check": "exchange_organization_mailtips_enabled", - "ConfigKey": "recommended_mailtips_large_audience_threshold", - "Operator": "lte", - "Value": 25 - } ] }, { @@ -827,6 +819,14 @@ "LevelOfRisk": 4, "Weight": 100 } + ], + "ConfigRequirements": [ + { + "Check": "entra_admin_users_sign_in_frequency_enabled", + "ConfigKey": "sign_in_frequency", + "Operator": "lte", + "Value": 4 + } ] }, { @@ -972,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 0d1090eb57e..66747cdba73 100644 --- a/prowler/compliance/okta/okta_idaas_stig_v1r2_okta.json +++ b/prowler/compliance/okta/okta_idaas_stig_v1r2_okta.json @@ -85,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 + } ] }, { @@ -411,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/tests/lib/check/compliance_config_requirements_coverage_baseline.json b/tests/lib/check/compliance_config_requirements_coverage_baseline.json deleted file mode 100644 index 4c6afd1c418..00000000000 --- a/tests/lib/check/compliance_config_requirements_coverage_baseline.json +++ /dev/null @@ -1,331 +0,0 @@ -[ - "asd_essential_eight_aws.json::awslambda_function_using_supported_runtimes", - "asd_essential_eight_aws.json::ecs_service_fargate_latest_platform_version", - "asd_essential_eight_aws.json::eks_cluster_uses_a_supported_version", - "asd_essential_eight_aws.json::rds_instance_backup_enabled", - "aws_account_security_onboarding_aws.json::ecr_repositories_scan_vulnerabilities_in_latest_image", - "aws_account_security_onboarding_aws.json::organizations_scp_check_deny_regions", - "aws_ai_security_framework_aws.json::awslambda_function_using_supported_runtimes", - "aws_ai_security_framework_aws.json::eks_cluster_uses_a_supported_version", - "aws_ai_security_framework_aws.json::eks_control_plane_logging_all_types_enabled", - "aws_ai_security_framework_aws.json::organizations_delegated_administrators", - "aws_ai_security_framework_aws.json::organizations_scp_check_deny_regions", - "aws_ai_security_framework_aws.json::s3_bucket_cross_account_access", - "aws_ai_security_framework_aws.json::secretsmanager_has_restrictive_resource_policy", - "aws_ai_security_framework_aws.json::vpc_endpoint_connections_trust_boundaries", - "aws_ai_security_framework_aws.json::vpc_endpoint_services_allowed_principals_trust_boundaries", - "aws_foundational_security_best_practices_aws.json::awslambda_function_using_supported_runtimes", - "aws_foundational_security_best_practices_aws.json::awslambda_function_vpc_multi_az", - "aws_foundational_security_best_practices_aws.json::codebuild_project_no_secrets_in_variables", - "aws_foundational_security_best_practices_aws.json::ec2_securitygroup_allow_ingress_from_internet_to_high_risk_tcp_ports", - "aws_foundational_security_best_practices_aws.json::eks_cluster_uses_a_supported_version", - "aws_foundational_security_best_practices_aws.json::eks_control_plane_logging_all_types_enabled", - "aws_foundational_security_best_practices_aws.json::elb_is_in_multiple_az", - "aws_foundational_security_best_practices_aws.json::elbv2_is_in_multiple_az", - "aws_foundational_security_best_practices_aws.json::opensearch_service_domains_not_publicly_accessible", - "aws_foundational_security_best_practices_aws.json::rds_instance_backup_enabled", - "aws_foundational_security_best_practices_aws.json::s3_bucket_cross_account_access", - "aws_foundational_security_best_practices_aws.json::ssm_documents_set_as_public", - "aws_foundational_technical_review_aws.json::rds_instance_backup_enabled", - "aws_foundational_technical_review_aws.json::vpc_endpoint_connections_trust_boundaries", - "aws_foundational_technical_review_aws.json::vpc_endpoint_services_allowed_principals_trust_boundaries", - "aws_well_architected_framework_reliability_pillar_aws.json::rds_instance_backup_enabled", - "aws_well_architected_framework_security_pillar_aws.json::apigateway_domain_name_pqc_tls_enabled", - "aws_well_architected_framework_security_pillar_aws.json::cloudfront_distributions_pqc_tls_enabled", - "aws_well_architected_framework_security_pillar_aws.json::ecr_repositories_scan_vulnerabilities_in_latest_image", - "aws_well_architected_framework_security_pillar_aws.json::eks_control_plane_logging_all_types_enabled", - "aws_well_architected_framework_security_pillar_aws.json::opensearch_service_domains_not_publicly_accessible", - "aws_well_architected_framework_security_pillar_aws.json::ssm_documents_set_as_public", - "aws_well_architected_framework_security_pillar_aws.json::transfer_server_pqc_ssh_kex_enabled", - "aws_well_architected_framework_security_pillar_aws.json::vpc_endpoint_connections_trust_boundaries", - "aws_well_architected_framework_security_pillar_aws.json::vpc_endpoint_services_allowed_principals_trust_boundaries", - "c5_aws.json::codebuild_project_no_secrets_in_variables", - "c5_aws.json::dynamodb_table_cross_account_access", - "c5_aws.json::ec2_securitygroup_allow_ingress_from_internet_to_any_port", - "c5_aws.json::ec2_securitygroup_allow_ingress_from_internet_to_high_risk_tcp_ports", - "c5_aws.json::ecr_repositories_scan_vulnerabilities_in_latest_image", - "c5_aws.json::eks_control_plane_logging_all_types_enabled", - "c5_aws.json::eventbridge_bus_cross_account_access", - "c5_aws.json::eventbridge_schema_registry_cross_account_access", - "c5_aws.json::opensearch_service_domains_not_publicly_accessible", - "c5_aws.json::organizations_delegated_administrators", - "c5_aws.json::organizations_scp_check_deny_regions", - "c5_aws.json::rds_instance_backup_enabled", - "c5_aws.json::s3_bucket_cross_account_access", - "c5_aws.json::ssm_documents_set_as_public", - "c5_aws.json::trustedadvisor_premium_support_plan_subscribed", - "c5_aws.json::vpc_endpoint_connections_trust_boundaries", - "c5_azure.json::app_ensure_java_version_is_latest", - "c5_azure.json::app_ensure_php_version_is_latest", - "c5_azure.json::app_ensure_python_version_is_latest", - "c5_azure.json::defender_attack_path_notifications_properly_configured", - "ccc_aws.json::apigateway_domain_name_pqc_tls_enabled", - "ccc_aws.json::cloudfront_distributions_pqc_tls_enabled", - "ccc_aws.json::codebuild_project_no_secrets_in_variables", - "ccc_aws.json::codebuild_project_uses_allowed_github_organizations", - "ccc_aws.json::ecr_repositories_scan_vulnerabilities_in_latest_image", - "ccc_aws.json::eventbridge_bus_cross_account_access", - "ccc_aws.json::eventbridge_schema_registry_cross_account_access", - "ccc_aws.json::organizations_scp_check_deny_regions", - "ccc_aws.json::rds_instance_backup_enabled", - "ccc_aws.json::s3_bucket_cross_account_access", - "ccc_aws.json::transfer_server_pqc_ssh_kex_enabled", - "ccc_aws.json::vpc_endpoint_connections_trust_boundaries", - "ccc_aws.json::vpc_endpoint_services_allowed_principals_trust_boundaries", - "cis_1.10_kubernetes.json::apiserver_strong_ciphers_only", - "cis_1.10_kubernetes.json::kubelet_strong_ciphers_only", - "cis_1.11_kubernetes.json::apiserver_strong_ciphers_only", - "cis_1.11_kubernetes.json::kubelet_strong_ciphers_only", - "cis_1.12_kubernetes.json::apiserver_strong_ciphers_only", - "cis_1.12_kubernetes.json::kubelet_strong_ciphers_only", - "cis_1.8_kubernetes.json::apiserver_strong_ciphers_only", - "cis_1.8_kubernetes.json::kubelet_strong_ciphers_only", - "cis_2.0_azure.json::app_ensure_java_version_is_latest", - "cis_2.0_azure.json::app_ensure_php_version_is_latest", - "cis_2.0_azure.json::app_ensure_python_version_is_latest", - "cis_2.1_azure.json::app_ensure_java_version_is_latest", - "cis_2.1_azure.json::app_ensure_php_version_is_latest", - "cis_2.1_azure.json::app_ensure_python_version_is_latest", - "cis_3.0_azure.json::app_ensure_java_version_is_latest", - "cis_3.0_azure.json::app_ensure_php_version_is_latest", - "cis_3.0_azure.json::app_ensure_python_version_is_latest", - "cis_4.0_azure.json::defender_attack_path_notifications_properly_configured", - "cis_4.0_m365.json::defender_malware_policy_comprehensive_attachments_filter_applied", - "cis_4.0_m365.json::entra_admin_users_sign_in_frequency_enabled", - "cis_4.0_m365.json::exchange_user_mailbox_auditing_enabled", - "cis_4.0_m365.json::teams_external_file_sharing_restricted", - "cis_5.0_azure.json::defender_attack_path_notifications_properly_configured", - "cis_6.0_m365.json::defender_malware_policy_comprehensive_attachments_filter_applied", - "cis_6.0_m365.json::exchange_user_mailbox_auditing_enabled", - "cis_6.0_m365.json::teams_external_file_sharing_restricted", - "cisa_aws.json::rds_instance_backup_enabled", - "cisa_aws.json::vpc_endpoint_connections_trust_boundaries", - "csa_ccm_4.0.json::app_ensure_java_version_is_latest", - "csa_ccm_4.0.json::app_ensure_php_version_is_latest", - "csa_ccm_4.0.json::app_ensure_python_version_is_latest", - "csa_ccm_4.0.json::awslambda_function_vpc_multi_az", - "csa_ccm_4.0.json::codebuild_project_no_secrets_in_variables", - "csa_ccm_4.0.json::compute_instance_group_multiple_zones", - "csa_ccm_4.0.json::defender_attack_path_notifications_properly_configured", - "csa_ccm_4.0.json::ec2_securitygroup_allow_ingress_from_internet_to_any_port", - "csa_ccm_4.0.json::ec2_securitygroup_allow_ingress_from_internet_to_high_risk_tcp_ports", - "csa_ccm_4.0.json::ecr_repositories_scan_vulnerabilities_in_latest_image", - "csa_ccm_4.0.json::eks_control_plane_logging_all_types_enabled", - "csa_ccm_4.0.json::elb_is_in_multiple_az", - "csa_ccm_4.0.json::elbv2_is_in_multiple_az", - "csa_ccm_4.0.json::rds_instance_backup_enabled", - "csa_ccm_4.0.json::transfer_server_pqc_ssh_kex_enabled", - "dora_2022_2554.json::app_ensure_java_version_is_latest", - "dora_2022_2554.json::app_ensure_php_version_is_latest", - "dora_2022_2554.json::app_ensure_python_version_is_latest", - "dora_2022_2554.json::compute_instance_group_multiple_zones", - "dora_2022_2554.json::defender_attack_path_notifications_properly_configured", - "dora_2022_2554.json::dynamodb_table_cross_account_access", - "dora_2022_2554.json::ec2_securitygroup_allow_ingress_from_internet_to_any_port", - "dora_2022_2554.json::ec2_securitygroup_allow_ingress_from_internet_to_high_risk_tcp_ports", - "dora_2022_2554.json::elb_is_in_multiple_az", - "dora_2022_2554.json::elbv2_is_in_multiple_az", - "dora_2022_2554.json::eventbridge_bus_cross_account_access", - "dora_2022_2554.json::eventbridge_schema_registry_cross_account_access", - "dora_2022_2554.json::organizations_delegated_administrators", - "dora_2022_2554.json::organizations_scp_check_deny_regions", - "dora_2022_2554.json::rds_instance_backup_enabled", - "dora_2022_2554.json::s3_bucket_cross_account_access", - "dora_2022_2554.json::secretsmanager_has_restrictive_resource_policy", - "dora_2022_2554.json::vm_desired_sku_size", - "dora_2022_2554.json::vpc_endpoint_connections_trust_boundaries", - "dora_2022_2554.json::vpc_endpoint_services_allowed_principals_trust_boundaries", - "ens_rd2022_aws.json::apigateway_domain_name_pqc_tls_enabled", - "ens_rd2022_aws.json::cloudfront_distributions_pqc_tls_enabled", - "ens_rd2022_aws.json::organizations_scp_check_deny_regions", - "ens_rd2022_aws.json::transfer_server_pqc_ssh_kex_enabled", - "fedramp_20x_ksi_low_aws.json::awslambda_function_using_supported_runtimes", - "fedramp_20x_ksi_low_aws.json::ec2_securitygroup_allow_ingress_from_internet_to_any_port", - "fedramp_20x_ksi_low_aws.json::ecr_repositories_scan_vulnerabilities_in_latest_image", - "fedramp_20x_ksi_low_aws.json::eks_cluster_uses_a_supported_version", - "fedramp_20x_ksi_low_aws.json::organizations_delegated_administrators", - "fedramp_20x_ksi_low_aws.json::organizations_scp_check_deny_regions", - "fedramp_20x_ksi_low_aws.json::rds_instance_backup_enabled", - "fedramp_20x_ksi_low_aws.json::trustedadvisor_premium_support_plan_subscribed", - "fedramp_20x_ksi_low_azure.json::app_ensure_java_version_is_latest", - "fedramp_20x_ksi_low_azure.json::app_ensure_php_version_is_latest", - "fedramp_20x_ksi_low_azure.json::app_ensure_python_version_is_latest", - "fedramp_20x_ksi_low_azure.json::defender_attack_path_notifications_properly_configured", - "fedramp_low_revision_4_aws.json::rds_instance_backup_enabled", - "fedramp_moderate_revision_4_aws.json::apigateway_domain_name_pqc_tls_enabled", - "fedramp_moderate_revision_4_aws.json::cloudfront_distributions_pqc_tls_enabled", - "fedramp_moderate_revision_4_aws.json::rds_instance_backup_enabled", - "fedramp_moderate_revision_4_aws.json::transfer_server_pqc_ssh_kex_enabled", - "ffiec_aws.json::apigateway_domain_name_pqc_tls_enabled", - "ffiec_aws.json::cloudfront_distributions_pqc_tls_enabled", - "ffiec_aws.json::rds_instance_backup_enabled", - "ffiec_aws.json::transfer_server_pqc_ssh_kex_enabled", - "gdpr_aws.json::rds_instance_backup_enabled", - "gxp_21_cfr_part_11_aws.json::apigateway_domain_name_pqc_tls_enabled", - "gxp_21_cfr_part_11_aws.json::cloudfront_distributions_pqc_tls_enabled", - "gxp_21_cfr_part_11_aws.json::rds_instance_backup_enabled", - "gxp_21_cfr_part_11_aws.json::transfer_server_pqc_ssh_kex_enabled", - "gxp_eu_annex_11_aws.json::rds_instance_backup_enabled", - "hipaa_aws.json::rds_instance_backup_enabled", - "hipaa_azure.json::defender_attack_path_notifications_properly_configured", - "iso27001_2013_aws.json::apigateway_domain_name_pqc_tls_enabled", - "iso27001_2013_aws.json::cloudfront_distributions_pqc_tls_enabled", - "iso27001_2013_aws.json::transfer_server_pqc_ssh_kex_enabled", - "iso27001_2022_aws.json::awslambda_function_vpc_multi_az", - "iso27001_2022_aws.json::ec2_securitygroup_allow_ingress_from_internet_to_any_port", - "iso27001_2022_aws.json::ec2_securitygroup_allow_ingress_from_internet_to_high_risk_tcp_ports", - "iso27001_2022_aws.json::eks_control_plane_logging_all_types_enabled", - "iso27001_2022_aws.json::elb_is_in_multiple_az", - "iso27001_2022_aws.json::elbv2_is_in_multiple_az", - "iso27001_2022_aws.json::opensearch_service_domains_not_publicly_accessible", - "iso27001_2022_aws.json::ssm_documents_set_as_public", - "iso27001_2022_aws.json::vpc_endpoint_connections_trust_boundaries", - "iso27001_2022_aws.json::vpc_endpoint_services_allowed_principals_trust_boundaries", - "iso27001_2022_kubernetes.json::apiserver_strong_ciphers_only", - "iso27001_2022_kubernetes.json::kubelet_strong_ciphers_only", - "iso27001_2022_m365.json::defender_malware_policy_comprehensive_attachments_filter_applied", - "iso27001_2022_m365.json::entra_admin_users_sign_in_frequency_enabled", - "iso27001_2022_m365.json::exchange_user_mailbox_auditing_enabled", - "iso27001_2022_m365.json::teams_external_file_sharing_restricted", - "kisa_isms_p_2023_aws.json::apigateway_domain_name_pqc_tls_enabled", - "kisa_isms_p_2023_aws.json::awslambda_function_using_supported_runtimes", - "kisa_isms_p_2023_aws.json::awslambda_function_vpc_multi_az", - "kisa_isms_p_2023_aws.json::cloudfront_distributions_pqc_tls_enabled", - "kisa_isms_p_2023_aws.json::codebuild_project_no_secrets_in_variables", - "kisa_isms_p_2023_aws.json::dynamodb_table_cross_account_access", - "kisa_isms_p_2023_aws.json::ec2_securitygroup_allow_ingress_from_internet_to_any_port", - "kisa_isms_p_2023_aws.json::ec2_securitygroup_allow_ingress_from_internet_to_high_risk_tcp_ports", - "kisa_isms_p_2023_aws.json::ecr_repositories_scan_vulnerabilities_in_latest_image", - "kisa_isms_p_2023_aws.json::ecs_service_fargate_latest_platform_version", - "kisa_isms_p_2023_aws.json::eks_cluster_uses_a_supported_version", - "kisa_isms_p_2023_aws.json::eks_control_plane_logging_all_types_enabled", - "kisa_isms_p_2023_aws.json::elb_is_in_multiple_az", - "kisa_isms_p_2023_aws.json::elbv2_is_in_multiple_az", - "kisa_isms_p_2023_aws.json::eventbridge_bus_cross_account_access", - "kisa_isms_p_2023_aws.json::eventbridge_schema_registry_cross_account_access", - "kisa_isms_p_2023_aws.json::opensearch_service_domains_not_publicly_accessible", - "kisa_isms_p_2023_aws.json::organizations_delegated_administrators", - "kisa_isms_p_2023_aws.json::organizations_scp_check_deny_regions", - "kisa_isms_p_2023_aws.json::rds_instance_backup_enabled", - "kisa_isms_p_2023_aws.json::s3_bucket_cross_account_access", - "kisa_isms_p_2023_aws.json::ssm_documents_set_as_public", - "kisa_isms_p_2023_aws.json::transfer_server_pqc_ssh_kex_enabled", - "kisa_isms_p_2023_aws.json::vpc_endpoint_connections_trust_boundaries", - "kisa_isms_p_2023_aws.json::vpc_endpoint_services_allowed_principals_trust_boundaries", - "kisa_isms_p_2023_korean_aws.json::apigateway_domain_name_pqc_tls_enabled", - "kisa_isms_p_2023_korean_aws.json::awslambda_function_using_supported_runtimes", - "kisa_isms_p_2023_korean_aws.json::awslambda_function_vpc_multi_az", - "kisa_isms_p_2023_korean_aws.json::cloudfront_distributions_pqc_tls_enabled", - "kisa_isms_p_2023_korean_aws.json::codebuild_project_no_secrets_in_variables", - "kisa_isms_p_2023_korean_aws.json::dynamodb_table_cross_account_access", - "kisa_isms_p_2023_korean_aws.json::ec2_securitygroup_allow_ingress_from_internet_to_any_port", - "kisa_isms_p_2023_korean_aws.json::ec2_securitygroup_allow_ingress_from_internet_to_high_risk_tcp_ports", - "kisa_isms_p_2023_korean_aws.json::ecr_repositories_scan_vulnerabilities_in_latest_image", - "kisa_isms_p_2023_korean_aws.json::ecs_service_fargate_latest_platform_version", - "kisa_isms_p_2023_korean_aws.json::eks_cluster_uses_a_supported_version", - "kisa_isms_p_2023_korean_aws.json::eks_control_plane_logging_all_types_enabled", - "kisa_isms_p_2023_korean_aws.json::elb_is_in_multiple_az", - "kisa_isms_p_2023_korean_aws.json::elbv2_is_in_multiple_az", - "kisa_isms_p_2023_korean_aws.json::eventbridge_bus_cross_account_access", - "kisa_isms_p_2023_korean_aws.json::eventbridge_schema_registry_cross_account_access", - "kisa_isms_p_2023_korean_aws.json::opensearch_service_domains_not_publicly_accessible", - "kisa_isms_p_2023_korean_aws.json::organizations_delegated_administrators", - "kisa_isms_p_2023_korean_aws.json::organizations_scp_check_deny_regions", - "kisa_isms_p_2023_korean_aws.json::rds_instance_backup_enabled", - "kisa_isms_p_2023_korean_aws.json::s3_bucket_cross_account_access", - "kisa_isms_p_2023_korean_aws.json::ssm_documents_set_as_public", - "kisa_isms_p_2023_korean_aws.json::transfer_server_pqc_ssh_kex_enabled", - "kisa_isms_p_2023_korean_aws.json::vpc_endpoint_connections_trust_boundaries", - "kisa_isms_p_2023_korean_aws.json::vpc_endpoint_services_allowed_principals_trust_boundaries", - "mitre_attack_aws.json::organizations_delegated_administrators", - "mitre_attack_aws.json::organizations_scp_check_deny_regions", - "mitre_attack_aws.json::rds_instance_backup_enabled", - "mitre_attack_azure.json::app_ensure_java_version_is_latest", - "mitre_attack_azure.json::app_ensure_php_version_is_latest", - "mitre_attack_azure.json::app_ensure_python_version_is_latest", - "nis2_aws.json::codebuild_project_no_secrets_in_variables", - "nis2_aws.json::eks_control_plane_logging_all_types_enabled", - "nis2_aws.json::rds_instance_backup_enabled", - "nis2_aws.json::ssm_documents_set_as_public", - "nis2_aws.json::vpc_endpoint_connections_trust_boundaries", - "nis2_aws.json::vpc_endpoint_services_allowed_principals_trust_boundaries", - "nis2_azure.json::app_ensure_java_version_is_latest", - "nis2_azure.json::app_ensure_php_version_is_latest", - "nis2_azure.json::app_ensure_python_version_is_latest", - "nist_800_171_revision_2_aws.json::apigateway_domain_name_pqc_tls_enabled", - "nist_800_171_revision_2_aws.json::cloudfront_distributions_pqc_tls_enabled", - "nist_800_171_revision_2_aws.json::rds_instance_backup_enabled", - "nist_800_171_revision_2_aws.json::transfer_server_pqc_ssh_kex_enabled", - "nist_800_53_revision_4_aws.json::rds_instance_backup_enabled", - "nist_800_53_revision_5_aws.json::apigateway_domain_name_pqc_tls_enabled", - "nist_800_53_revision_5_aws.json::cloudfront_distributions_pqc_tls_enabled", - "nist_800_53_revision_5_aws.json::rds_instance_backup_enabled", - "nist_800_53_revision_5_aws.json::transfer_server_pqc_ssh_kex_enabled", - "nist_csf_1.1_aws.json::rds_instance_backup_enabled", - "nist_csf_2.0_aws.json::codebuild_project_no_secrets_in_variables", - "nist_csf_2.0_aws.json::codebuild_project_uses_allowed_github_organizations", - "nist_csf_2.0_aws.json::ec2_securitygroup_allow_ingress_from_internet_to_high_risk_tcp_ports", - "nist_csf_2.0_aws.json::elb_is_in_multiple_az", - "nist_csf_2.0_aws.json::eventbridge_bus_cross_account_access", - "nist_csf_2.0_aws.json::organizations_delegated_administrators", - "nist_csf_2.0_aws.json::organizations_scp_check_deny_regions", - "nist_csf_2.0_aws.json::rds_instance_backup_enabled", - "nist_csf_2.0_aws.json::s3_bucket_cross_account_access", - "nist_csf_2.0_aws.json::ssm_documents_set_as_public", - "nist_csf_2.0_aws.json::trustedadvisor_premium_support_plan_subscribed", - "nist_csf_2.0_aws.json::vpc_endpoint_connections_trust_boundaries", - "okta_idaas_stig_v1r2_okta.json::idp_smart_card_dod_approved_ca", - "okta_idaas_stig_v1r2_okta.json::signon_global_session_lifetime_18h", - "okta_idaas_stig_v1r2_okta.json::user_inactivity_automation_35d_enabled", - "pci_3.2.1_aws.json::codebuild_project_no_secrets_in_variables", - "pci_3.2.1_aws.json::eks_cluster_uses_a_supported_version", - "pci_3.2.1_aws.json::opensearch_service_domains_not_publicly_accessible", - "pci_3.2.1_aws.json::rds_instance_backup_enabled", - "pci_4.0_aws.json::codebuild_project_no_secrets_in_variables", - "pci_4.0_aws.json::ec2_securitygroup_allow_ingress_from_internet_to_any_port", - "pci_4.0_aws.json::ecs_service_fargate_latest_platform_version", - "pci_4.0_aws.json::eks_cluster_uses_a_supported_version", - "pci_4.0_aws.json::eks_control_plane_logging_all_types_enabled", - "pci_4.0_aws.json::eventbridge_schema_registry_cross_account_access", - "pci_4.0_aws.json::opensearch_service_domains_not_publicly_accessible", - "pci_4.0_aws.json::rds_instance_backup_enabled", - "pci_4.0_aws.json::s3_bucket_cross_account_access", - "pci_4.0_aws.json::ssm_documents_set_as_public", - "pci_4.0_kubernetes.json::kubelet_strong_ciphers_only", - "prowler_threatscore_aws.json::ec2_securitygroup_allow_ingress_from_internet_to_high_risk_tcp_ports", - "prowler_threatscore_aws.json::opensearch_service_domains_not_publicly_accessible", - "prowler_threatscore_aws.json::ssm_documents_set_as_public", - "prowler_threatscore_kubernetes.json::apiserver_strong_ciphers_only", - "prowler_threatscore_kubernetes.json::kubelet_strong_ciphers_only", - "prowler_threatscore_m365.json::defender_malware_policy_comprehensive_attachments_filter_applied", - "prowler_threatscore_m365.json::entra_admin_users_sign_in_frequency_enabled", - "prowler_threatscore_m365.json::exchange_user_mailbox_auditing_enabled", - "prowler_threatscore_m365.json::teams_external_file_sharing_restricted", - "rbi_cyber_security_framework_aws.json::apigateway_domain_name_pqc_tls_enabled", - "rbi_cyber_security_framework_aws.json::cloudfront_distributions_pqc_tls_enabled", - "rbi_cyber_security_framework_aws.json::rds_instance_backup_enabled", - "rbi_cyber_security_framework_aws.json::ssm_documents_set_as_public", - "rbi_cyber_security_framework_aws.json::transfer_server_pqc_ssh_kex_enabled", - "rbi_cyber_security_framework_azure.json::app_ensure_java_version_is_latest", - "rbi_cyber_security_framework_azure.json::app_ensure_php_version_is_latest", - "rbi_cyber_security_framework_azure.json::app_ensure_python_version_is_latest", - "secnumcloud_3.2_aws.json::apigateway_domain_name_pqc_tls_enabled", - "secnumcloud_3.2_aws.json::cloudfront_distributions_pqc_tls_enabled", - "secnumcloud_3.2_aws.json::codebuild_project_no_secrets_in_variables", - "secnumcloud_3.2_aws.json::ec2_securitygroup_allow_ingress_from_internet_to_any_port", - "secnumcloud_3.2_aws.json::ec2_securitygroup_allow_ingress_from_internet_to_high_risk_tcp_ports", - "secnumcloud_3.2_aws.json::ecr_repositories_scan_vulnerabilities_in_latest_image", - "secnumcloud_3.2_aws.json::eks_control_plane_logging_all_types_enabled", - "secnumcloud_3.2_aws.json::elbv2_is_in_multiple_az", - "secnumcloud_3.2_aws.json::organizations_scp_check_deny_regions", - "secnumcloud_3.2_aws.json::rds_instance_backup_enabled", - "secnumcloud_3.2_aws.json::ssm_documents_set_as_public", - "secnumcloud_3.2_aws.json::transfer_server_pqc_ssh_kex_enabled", - "secnumcloud_3.2_aws.json::vpc_endpoint_connections_trust_boundaries", - "secnumcloud_3.2_aws.json::vpc_endpoint_services_allowed_principals_trust_boundaries", - "secnumcloud_3.2_azure.json::defender_attack_path_notifications_properly_configured", - "secnumcloud_3.2_gcp.json::compute_instance_group_multiple_zones", - "soc2_aws.json::ec2_securitygroup_allow_ingress_from_internet_to_any_port", - "soc2_aws.json::ec2_securitygroup_allow_ingress_from_internet_to_high_risk_tcp_ports", - "soc2_aws.json::rds_instance_backup_enabled" -] diff --git a/tests/lib/check/compliance_config_requirements_coverage_test.py b/tests/lib/check/compliance_config_requirements_coverage_test.py deleted file mode 100644 index 050ecbc17f4..00000000000 --- a/tests/lib/check/compliance_config_requirements_coverage_test.py +++ /dev/null @@ -1,298 +0,0 @@ -"""Coverage test for ``ConfigRequirements`` across the shipped compliance JSONs. - -Sibling of ``compliance_config_requirements_data_test.py``: that file validates -that the constraints which *exist* are well-formed; this one validates that -constraints are *not missing*. - -Invariant (per-framework): - For every compliance framework, if it maps a configurable check whose - configuration can *relax the verdict* (i.e. a looser value makes the check - PASS when the control is not really satisfied), then that framework must - declare a ``ConfigRequirements`` entry for that check in at least one of its - requirements. Otherwise a user could loosen the config and the framework - would silently report the requirement as compliant. - -Why a baseline: - The invariant is currently violated by a large, pre-existing backlog (the - constraints were originally added mostly to the CIS frameworks). Fixing all - of them at once is impractical and needs per-control judgement on the right - threshold value. So the known gaps are frozen in - ``compliance_config_requirements_coverage_baseline.json`` and this test: - - FAILS on any *new* gap not in the baseline (regression guard), and - - FAILS on any baseline entry that is *no longer* a gap (keeps the - baseline shrinking as gaps are fixed — delete the entry when you add - the constraint). - - Regenerate the baseline after intentionally changing the set of gaps with: - python tests/lib/check/compliance_config_requirements_coverage_test.py --update -""" - -import ast -import glob -import json -import os -import pathlib - -_REPO_ROOT = pathlib.Path(__file__).resolve().parents[3] -_COMPLIANCE_DIR = _REPO_ROOT / "prowler" / "compliance" -_SERVICES_GLOB = str(_REPO_ROOT / "prowler" / "providers" / "*" / "services") -_BASELINE_PATH = ( - pathlib.Path(__file__).with_name( - "compliance_config_requirements_coverage_baseline.json" - ) -) - -# Config keys that a check reads but which do NOT relax its verdict, so a -# requirement mapping such a check does not need a ConfigRequirements: -# - shodan_api_key: only enables the lookup; without it the check produces an -# informational/no finding, it never turns a FAIL into a PASS. -# - detect_secrets_plugins / secrets_ignore_patterns: tune the secret-scanning -# engine, not a security policy threshold. -# Keep this list small and explicit; everything else is treated as verdict-affecting. -_KEYS_NOT_AFFECTING_VERDICT = { - "shodan_api_key", - "detect_secrets_plugins", - "secrets_ignore_patterns", -} - -# Config keys whose Pydantic schema bound already pins the value to the default -# in the direction a user would relax it, so the value CANNOT be loosened — a -# ConfigRequirements would be inert (it could never fire). These are exempt from -# the coverage invariant. The value is the relaxation direction ("gte" = a higher -# value is safer, so loosening means going below; "lte" = the opposite). -# ``test_schema_enforced_keys_are_really_pinned`` proves each entry against the -# live schema + config.yaml default, so a schema/default change can't silently -# leave a real gap hidden here. -_KEYS_SCHEMA_ENFORCED = { - "vm_backup_min_daily_retention_days": "gte", # schema ge=7 == default 7 - "days_to_expire_threshold": "gte", # schema ge=7 == default 7 -} - -_EXEMPT_KEYS = _KEYS_NOT_AFFECTING_VERDICT | set(_KEYS_SCHEMA_ENFORCED) - - -def _refers_audit_config(base: ast.AST) -> bool: - """True when ``base`` is an expression referring to a provider audit_config.""" - if isinstance(base, ast.Attribute) and base.attr == "audit_config": - return True - if isinstance(base, ast.Name) and base.id == "audit_config": - return True - # getattr(, "audit_config", ) - if ( - isinstance(base, ast.Call) - and isinstance(base.func, ast.Name) - and base.func.id == "getattr" - and len(base.args) >= 2 - and isinstance(base.args[1], ast.Constant) - and base.args[1].value == "audit_config" - ): - return True - return False - - -def _config_keys_in_file(path: str) -> set: - """Return the set of config keys a check entrypoint reads from audit_config.""" - keys: set = set() - try: - tree = ast.parse(open(path, encoding="utf-8").read()) - except (SyntaxError, OSError): - return keys - - class _Visitor(ast.NodeVisitor): - def visit_Call(self, node): # audit_config.get("key", ...) - f = node.func - if ( - isinstance(f, ast.Attribute) - and f.attr == "get" - and _refers_audit_config(f.value) - and node.args - and isinstance(node.args[0], ast.Constant) - and isinstance(node.args[0].value, str) - ): - keys.add(node.args[0].value) - self.generic_visit(node) - - def visit_Subscript(self, node): # audit_config["key"] - if _refers_audit_config(node.value) and isinstance( - node.slice, ast.Constant - ): - if isinstance(node.slice.value, str): - keys.add(node.slice.value) - self.generic_visit(node) - - _Visitor().visit(tree) - return keys - - -def _check_config_keys() -> dict: - """Map every check name to the set of audit_config keys it reads.""" - result: dict = {} - for services_dir in glob.glob(_SERVICES_GLOB): - for path in glob.glob(os.path.join(services_dir, "**", "*.py"), recursive=True): - name = os.path.basename(path)[:-3] - # Only the check entrypoint file (named like its folder) is a check. - if name != os.path.basename(os.path.dirname(path)): - continue - keys = _config_keys_in_file(path) - if keys: - result.setdefault(name, set()).update(keys) - return result - - -def _requirements(data): - return data.get("Requirements") or data.get("requirements") or [] - - -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 compute_current_gaps() -> set: - """Return the set of ``::`` pairs missing a constraint. - - A pair is a gap when the framework maps a configurable, verdict-affecting - check but declares no ConfigRequirements for it in any requirement. - """ - check_keys = _check_config_keys() - gaps = set() - for path in sorted( - glob.glob(str(_COMPLIANCE_DIR / "**" / "*.json"), recursive=True) - ): - with open(path, encoding="utf-8") as f: - data = json.load(f) - fname = pathlib.Path(path).name - reqs = _requirements(data) - - mapped_configurable = set() - for req in reqs: - for chk in _req_checks(req): - keys = check_keys.get(chk) - if keys and (keys - _EXEMPT_KEYS): - mapped_configurable.add(chk) - - constrained = set() - for req in reqs: - for c in _req_constraints(req): - constrained.add(c.get("Check")) - - for chk in mapped_configurable - constrained: - gaps.add(f"{fname}::{chk}") - return gaps - - -def _load_baseline() -> set: - if not _BASELINE_PATH.exists(): - return set() - with open(_BASELINE_PATH, encoding="utf-8") as f: - return set(json.load(f)) - - -def test_no_new_config_requirement_gaps(): - """No configurable, verdict-affecting check may be mapped without a - ConfigRequirements unless it is an accepted, pre-existing gap.""" - current = compute_current_gaps() - baseline = _load_baseline() - new_gaps = sorted(current - baseline) - assert not new_gaps, ( - "These frameworks map a configurable check whose config relaxes the " - "verdict but declare no ConfigRequirements for it. Add the constraint, " - "or (only if the config truly cannot relax the verdict) add its key to " - "_KEYS_NOT_AFFECTING_VERDICT:\n " + "\n ".join(new_gaps) - ) - - -def test_baseline_has_no_stale_entries(): - """Every baseline entry must still be a real gap; fix one → remove it here.""" - current = compute_current_gaps() - baseline = _load_baseline() - resolved = sorted(baseline - current) - assert not resolved, ( - "These baseline entries are no longer gaps (a ConfigRequirements now " - "exists, or the check/mapping was removed). Delete them from " - f"{_BASELINE_PATH.name}:\n " + "\n ".join(resolved) - ) - - -def _schema_bounds(): - """Map every config key to its (minimum, maximum) across all provider schemas.""" - from prowler.config.schema.registry import SCHEMAS - - bounds = {} - for model in SCHEMAS.values(): - if model is None: - continue - props = model.model_json_schema().get("properties", {}) - for name, prop in props.items(): - mn = mx = None - for cand in [prop, *prop.get("anyOf", [])]: - if "minimum" in cand: - mn = cand["minimum"] - if "maximum" in cand: - mx = cand["maximum"] - if name not in bounds: - bounds[name] = (mn, mx) - return bounds - - -def _config_defaults(): - """Map every config key to its default value from config.yaml (first section wins).""" - import yaml - - cfg_path = _REPO_ROOT / "prowler" / "config" / "config.yaml" - with open(cfg_path, encoding="utf-8") as f: - cfg = yaml.safe_load(f) - defaults = {} - for section in cfg.values(): - if isinstance(section, dict): - for k, v in section.items(): - defaults.setdefault(k, v) - return defaults - - -def test_schema_enforced_keys_are_really_pinned(): - """Each _KEYS_SCHEMA_ENFORCED entry must genuinely be non-relaxable: the - config.yaml default must equal the schema bound in the relaxation direction - (gte -> minimum, lte -> maximum). This stops the exemption from hiding a real - gap if a schema or default ever changes.""" - bounds = _schema_bounds() - defaults = _config_defaults() - problems = [] - for key, direction in _KEYS_SCHEMA_ENFORCED.items(): - default = defaults.get(key) - mn, mx = bounds.get(key, (None, None)) - pin = mn if direction == "gte" else mx - if default is None or pin is None or default != pin: - problems.append( - f"{key} (dir={direction}): default={default!r} schema_min={mn!r} " - f"schema_max={mx!r} — not pinned, exemption unjustified" - ) - assert not problems, ( - "These keys are exempted as schema-enforced but the schema no longer " - "pins them to the default (they CAN now be relaxed → real gap). Add a " - "ConfigRequirements and remove them from _KEYS_SCHEMA_ENFORCED:\n " - + "\n ".join(problems) - ) - - -if __name__ == "__main__": - import sys - - if "--update" in sys.argv: - gaps = sorted(compute_current_gaps()) - with open(_BASELINE_PATH, "w", encoding="utf-8") as f: - json.dump(gaps, f, indent=2) - f.write("\n") - print(f"Wrote {len(gaps)} baseline entries to {_BASELINE_PATH}") - else: - print(f"Current gaps: {len(compute_current_gaps())}") diff --git a/tests/lib/outputs/compliance/iso27001/iso27001_aws_numeric_config_requirements_test.py b/tests/lib/outputs/compliance/iso27001/iso27001_aws_numeric_config_requirements_test.py deleted file mode 100644 index 376587872c8..00000000000 --- a/tests/lib/outputs/compliance/iso27001/iso27001_aws_numeric_config_requirements_test.py +++ /dev/null @@ -1,73 +0,0 @@ -"""Integration coverage for a numeric (lte) ConfigRequirements in a CSV output. - -ISO 27001 AWS A.8.10 maps ``ec2_instance_older_than_specific_days`` with a -``max_ec2_instance_age_in_days lte 180`` constraint (the config.yaml default, -applied as a security floor by the sdk-config-compliance coverage work): if the -user loosens the threshold above 180 the check can PASS while the requirement is -not really satisfied, so the requirement row must be FAIL with [CONFIG NOT VALID]. -Mirrors cis_azure_config_requirements_test.py but for a numeric threshold. -""" - -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.iso27001.iso27001_aws import AWSISO27001 - -_REPO_ROOT = pathlib.Path(__file__).resolve().parents[5] -_FRAMEWORK = _REPO_ROOT / "prowler" / "compliance" / "aws" / "iso27001_2022_aws.json" -_REQUIREMENT_ID = "A.8.10" -_CHECK = "ec2_instance_older_than_specific_days" -_KEY = "max_ec2_instance_age_in_days" - - -def _load(): - return Compliance(**json.load(open(_FRAMEWORK))) - - -def _finding(check_id, status): - 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:ec2:us-east-1:123456789012:instance/i-0", - resource_name="i-0", - 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 - mock_gp.return_value.type = "aws" - out = AWSISO27001(findings=findings, compliance=_load(), file_path=None) - return [r for r in out._data if r.Requirements_Id == _REQUIREMENT_ID] - - -class Test_ISO27001_AWS_Numeric_Constraint: - def test_loosened_threshold_forces_fail(self): - # 365 > 180 -> the applied config is looser than the requirement needs. - rows = _rows_for({_KEY: 365}) - assert rows, f"expected a row for requirement {_REQUIREMENT_ID}" - assert all(r.Status == "FAIL" for r in rows) - assert all("CONFIG NOT VALID" in r.StatusExtended for r in rows) - - def test_default_threshold_keeps_pass(self): - # 180 == the floor -> satisfies lte 180, the PASS stands. - rows = _rows_for({_KEY: 180}) - assert rows - assert all(r.Status == "PASS" for r in rows) - - def test_unset_config_keeps_pass(self): - # Key not set -> default assumed adequate, no override. - rows = _rows_for({}) - assert rows - assert all(r.Status == "PASS" for r in rows) From 5ace54001f3b455764dd36c4ce4ec6c4da1c0697 Mon Sep 17 00:00:00 2001 From: pedrooot Date: Wed, 24 Jun 2026 10:27:37 +0200 Subject: [PATCH 12/18] chore(changelog): point ConfigRequirements entry to #11669 --- prowler/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index 10fc7e234fc..17839235ad3 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -6,7 +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) [(#11667)](https://github.com/prowler-cloud/prowler/pull/11667) +- 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) --- From 8e1ad8c2c84b8f0941b1ceb4ce89156a93ea7a61 Mon Sep 17 00:00:00 2001 From: pedrooot Date: Wed, 24 Jun 2026 11:09:14 +0200 Subject: [PATCH 13/18] feat(compliance): validate ConfigRequirements Value type against Operator --- prowler/lib/check/compliance_models.py | 39 ++++++++++++++++++- ...compliance_config_constraint_model_test.py | 35 ++++++++++++++--- 2 files changed, 67 insertions(+), 7 deletions(-) diff --git a/prowler/lib/check/compliance_models.py b/prowler/lib/check/compliance_models.py index db903262353..d974307ca07 100644 --- a/prowler/lib/check/compliance_models.py +++ b/prowler/lib/check/compliance_models.py @@ -341,6 +341,40 @@ class Compliance_Requirement_ConfigConstraint(BaseModel): # (single-provider frameworks). Provider: Optional[str] = None + @root_validator + # noqa: F841 - since vulture raises unused variable 'cls' + 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 + # TODO: move this to compliance folder class Compliance_Requirement(BaseModel): @@ -740,7 +774,10 @@ class UniversalComplianceRequirement(BaseModel): name: Optional[str] = None attributes: dict = Field(default_factory=dict) checks: dict[str, list[str]] = Field(default_factory=dict) - config_requirements: Optional[list[dict]] = None + # 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 diff --git a/tests/lib/check/compliance_config_constraint_model_test.py b/tests/lib/check/compliance_config_constraint_model_test.py index 164e9e1fa0c..9777c1265e6 100644 --- a/tests/lib/check/compliance_config_constraint_model_test.py +++ b/tests/lib/check/compliance_config_constraint_model_test.py @@ -54,6 +54,29 @@ def test_invalid_operator_rejected(self): 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( @@ -123,14 +146,14 @@ def test_config_requirements_carried_to_universal(self): assert legacy_with == universal_with assert universal_with, "expected at least one requirement with constraints" - # The constraint payload survives as a list of dicts with the same keys - # (``Provider`` is carried through too, ``None`` for single-provider - # frameworks like CIS AWS). + # 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, dict) - assert set(entry) == {"Check", "Provider", "ConfigKey", "Operator", "Value"} - assert entry["Provider"] is None + 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()) From 12c7d554a5d9bd913d79930f892032a7f82751ab Mon Sep 17 00:00:00 2001 From: pedrooot Date: Wed, 24 Jun 2026 11:10:16 +0200 Subject: [PATCH 14/18] chore: fix lint --- .../lib/check/compliance_config_constraint_model_test.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/lib/check/compliance_config_constraint_model_test.py b/tests/lib/check/compliance_config_constraint_model_test.py index 9777c1265e6..1ef262e2737 100644 --- a/tests/lib/check/compliance_config_constraint_model_test.py +++ b/tests/lib/check/compliance_config_constraint_model_test.py @@ -152,7 +152,13 @@ def test_config_requirements_carried_to_universal(self): 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 set(entry.dict()) == { + "Check", + "Provider", + "ConfigKey", + "Operator", + "Value", + } assert entry.Provider is None def test_requirements_without_constraints_are_none_in_universal(self): From 290f1af51fb7d1d1cc2242b4cf0711e4af4d0be7 Mon Sep 17 00:00:00 2001 From: pedrooot Date: Wed, 24 Jun 2026 11:20:36 +0200 Subject: [PATCH 15/18] fix: github-advanced-security --- prowler/lib/check/compliance_models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prowler/lib/check/compliance_models.py b/prowler/lib/check/compliance_models.py index d974307ca07..6c12ac7afbe 100644 --- a/prowler/lib/check/compliance_models.py +++ b/prowler/lib/check/compliance_models.py @@ -342,7 +342,7 @@ class Compliance_Requirement_ConfigConstraint(BaseModel): Provider: Optional[str] = None @root_validator - # noqa: F841 - since vulture raises unused variable 'cls' + @classmethod def validate_value_matches_operator(cls, values): # noqa: F841 """Ensure ``Value``'s type is consistent with ``Operator``. From 09dd0f88f4f579f6d22902c13b52cf3fa9d73816 Mon Sep 17 00:00:00 2001 From: pedrooot Date: Thu, 25 Jun 2026 12:28:13 +0200 Subject: [PATCH 16/18] chore(config): change config violation message --- .../security-compliance-framework.mdx | 11 +-- prowler/lib/check/compliance_config_eval.py | 76 +++++++++++++++++-- prowler/lib/check/compliance_models.py | 2 +- ...compliance_config_constraint_model_test.py | 2 +- .../lib/check/compliance_config_eval_test.py | 29 +++++-- .../cis/cis_aws_config_requirements_test.py | 6 +- .../cis/cis_azure_config_requirements_test.py | 2 +- .../ens/ens_aws_config_requirements_test.py | 4 +- ...csf_compliance_config_requirements_test.py | 6 +- 9 files changed, 107 insertions(+), 31 deletions(-) diff --git a/docs/developer-guide/security-compliance-framework.mdx b/docs/developer-guide/security-compliance-framework.mdx index 9c831551405..75392ed3a79 100644 --- a/docs/developer-guide/security-compliance-framework.mdx +++ b/docs/developer-guide/security-compliance-framework.mdx @@ -541,7 +541,7 @@ All evaluation lives in one shared module, `prowler/lib/check/compliance_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 `status_extended` is prefixed with `[CONFIG NOT VALID]` plus the reason (via `apply_config_status()`). For the table generators, `get_effective_status()` applies the same FAIL roll-up so per-section counts stay consistent. +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. @@ -622,12 +622,13 @@ With a loosened config, the affected requirement's findings report: ```text Status: FAIL -StatusExtended: [CONFIG NOT VALID] config not valid for requirement: - iam_user_accesskey_unused.max_unused_access_keys_days=120 - does not satisfy lte 45. +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 `[CONFIG NOT VALID]` prefix appears identically across the CSV, OCSF, and console-table outputs. +The same `Configuration not valid for this requirement.` message appears identically across the CSV, OCSF, and console-table outputs. ### Authoring guidelines diff --git a/prowler/lib/check/compliance_config_eval.py b/prowler/lib/check/compliance_config_eval.py index b335ba38509..763e1389c2a 100644 --- a/prowler/lib/check/compliance_config_eval.py +++ b/prowler/lib/check/compliance_config_eval.py @@ -17,9 +17,72 @@ from typing import Any, Optional -# Prefix prepended to a finding's ``status_extended`` when its requirement's -# config constraints are not satisfied and the status is forced to FAIL. -CONFIG_NOT_VALID_PREFIX = "[CONFIG NOT VALID]" +# 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: @@ -118,10 +181,7 @@ def evaluate_config_constraints( applied = audit_config[config_key] if not _check_operator(applied, operator, expected): - reason = ( - f"config not valid for requirement: {check}.{config_key}=" - f"{applied!r} does not satisfy {operator} {expected!r}" - ) + reason = _describe_violation(check, config_key, applied, operator, expected) return False, reason return True, "" @@ -286,7 +346,7 @@ def apply_config_status( return status, status_extended return ( "FAIL", - f"{CONFIG_NOT_VALID_PREFIX} {config_status[1]}. {status_extended}", + f"{config_status[1]} {status_extended}".strip(), ) diff --git a/prowler/lib/check/compliance_models.py b/prowler/lib/check/compliance_models.py index 6c12ac7afbe..921a3094495 100644 --- a/prowler/lib/check/compliance_models.py +++ b/prowler/lib/check/compliance_models.py @@ -349,7 +349,7 @@ def validate_value_matches_operator(cls, values): # noqa: F841 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 + spurious config-not-valid FAIL. Validating here turns that into a clear error when the framework is loaded. """ operator = values.get("Operator") diff --git a/tests/lib/check/compliance_config_constraint_model_test.py b/tests/lib/check/compliance_config_constraint_model_test.py index 1ef262e2737..48f67fd504d 100644 --- a/tests/lib/check/compliance_config_constraint_model_test.py +++ b/tests/lib/check/compliance_config_constraint_model_test.py @@ -71,7 +71,7 @@ def test_invalid_operator_rejected(self): ) 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. + # at runtime, forcing a spurious config-not-valid FAIL. with pytest.raises(ValidationError): Compliance_Requirement_ConfigConstraint( Check="c", ConfigKey="k", Operator=operator, Value=value diff --git a/tests/lib/check/compliance_config_eval_test.py b/tests/lib/check/compliance_config_eval_test.py index 9e4775b65eb..4acec9bb4c6 100644 --- a/tests/lib/check/compliance_config_eval_test.py +++ b/tests/lib/check/compliance_config_eval_test.py @@ -50,8 +50,13 @@ def test_lte_violated(self): 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 "120" 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}] @@ -149,7 +154,9 @@ def test_multiple_constraints_first_violation_reported(self): ] is_ok, reason = evaluate_config_constraints(constraints, {"k1": 45, "k2": 90}) assert is_ok is False - assert "b.k2" in reason + # 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: @@ -352,11 +359,14 @@ def test_none_config_status_keeps_finding(self): def test_compliant_keeps_finding(self): assert apply_config_status("PASS", "ext", (True, "")) == ("PASS", "ext") - def test_invalid_config_forces_fail_and_prefixes_reason(self): - status, extended = apply_config_status("PASS", "ext", (False, "bad config")) + 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 "bad config" in extended + assert reason in extended assert "ext" in extended @@ -371,8 +381,13 @@ def test_invalid_config_forces_fail(self): class Test_get_scan_audit_config: def test_returns_empty_without_global_provider(self): - # No global provider set in this unit-test context → safe empty mapping. - assert get_scan_audit_config() == {} + # 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: 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 index 56c5f51674f..28cc3d2e1a4 100644 --- a/tests/lib/outputs/compliance/cis/cis_aws_config_requirements_test.py +++ b/tests/lib/outputs/compliance/cis/cis_aws_config_requirements_test.py @@ -49,14 +49,14 @@ def test_loose_config_forces_requirement_fail(self): 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("CONFIG NOT VALID" in r.StatusExtended 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("CONFIG NOT VALID" not in r.StatusExtended 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")] @@ -80,7 +80,7 @@ def test_region_mute_constraint_forces_fail(self): 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("CONFIG NOT VALID" in r.StatusExtended 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")] 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 index 99c6268c43a..a34402ec77a 100644 --- a/tests/lib/outputs/compliance/cis/cis_azure_config_requirements_test.py +++ b/tests/lib/outputs/compliance/cis/cis_azure_config_requirements_test.py @@ -60,7 +60,7 @@ def test_widened_allowlist_forces_fail(self): ) assert rows, f"expected a row for requirement {_REQUIREMENT_ID}" assert all(r.Status == "FAIL" for r in rows) - assert all("CONFIG NOT VALID" in r.StatusExtended 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( 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 index 336af73df23..b09d9bc0b33 100644 --- a/tests/lib/outputs/compliance/ens/ens_aws_config_requirements_test.py +++ b/tests/lib/outputs/compliance/ens/ens_aws_config_requirements_test.py @@ -51,11 +51,11 @@ def test_region_mute_constraint_forces_fail(self): 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("CONFIG NOT VALID" in r.StatusExtended 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("CONFIG NOT VALID" not in r.StatusExtended for r in rows) + assert all("Configuration not valid" not in r.StatusExtended for r in rows) 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 index 18e706077c0..c846385d888 100644 --- a/tests/lib/outputs/compliance/universal/ocsf_compliance_config_requirements_test.py +++ b/tests/lib/outputs/compliance/universal/ocsf_compliance_config_requirements_test.py @@ -4,7 +4,7 @@ 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 ``[CONFIG NOT VALID]`` prefix. The Check status keeps +the message must carry the ``Configuration not valid`` marker. The Check status keeps the real finding status. """ @@ -173,7 +173,7 @@ class Test_OCSF_Config_Requirements: def test_violating_config_fails_requirement(self, check, constraint, bad, ok): cf = _run(check, constraint, bad) assert cf.compliance.status_id == ComplianceStatusID.Fail - assert "[CONFIG NOT VALID]" in cf.message + assert "Configuration not valid" in cf.message @pytest.mark.parametrize( "check,constraint,bad,ok", @@ -183,7 +183,7 @@ def test_violating_config_fails_requirement(self, check, constraint, bad, ok): def test_valid_config_keeps_pass(self, check, constraint, bad, ok): cf = _run(check, constraint, ok) assert cf.compliance.status_id == ComplianceStatusID.Pass - assert "[CONFIG NOT VALID]" not in cf.message + assert "Configuration not valid" not in cf.message def test_absent_config_assumes_default_ok(self): check, constraint, _bad, _ok = _CASES[0] From c9c563002c2c415c9c43bf18490864feacc07eb6 Mon Sep 17 00:00:00 2001 From: pedrooot Date: Fri, 26 Jun 2026 12:45:39 +0200 Subject: [PATCH 17/18] chore(revision): resolve comments --- prowler/lib/check/compliance_models.py | 155 +++++++++--------- .../compliance/universal/ocsf_compliance.py | 11 +- .../compliance/universal/universal_output.py | 33 +++- .../compliance/universal/universal_table.py | 6 +- .../check/mitre_config_requirements_test.py | 148 +++++++++++++++++ .../ocsf_compliance_status_scoping_test.py | 129 +++++++++++++++ ...iversal_output_config_requirements_test.py | 115 +++++++++++++ 7 files changed, 512 insertions(+), 85 deletions(-) create mode 100644 tests/lib/check/mitre_config_requirements_test.py create mode 100644 tests/lib/outputs/compliance/universal/ocsf_compliance_status_scoping_test.py create mode 100644 tests/lib/outputs/compliance/universal/universal_output_config_requirements_test.py diff --git a/prowler/lib/check/compliance_models.py b/prowler/lib/check/compliance_models.py index 921a3094495..80322a6929e 100644 --- a/prowler/lib/check/compliance_models.py +++ b/prowler/lib/check/compliance_models.py @@ -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,79 +379,6 @@ class STIG_Requirement_Attribute(BaseModel): FixText: Optional[str] = None -# 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 - - # TODO: move this to compliance folder class Compliance_Requirement(BaseModel): """Compliance_Requirement holds the base model for every requirement within a compliance framework""" @@ -971,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, @@ -978,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, diff --git a/prowler/lib/outputs/compliance/universal/ocsf_compliance.py b/prowler/lib/outputs/compliance/universal/ocsf_compliance.py index 8e5b623e491..07c1f67b10d 100644 --- a/prowler/lib/outputs/compliance/universal/ocsf_compliance.py +++ b/prowler/lib/outputs/compliance/universal/ocsf_compliance.py @@ -185,8 +185,10 @@ 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 + framework.requirements, provider_type=self._provider ) for finding in findings: @@ -288,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, @@ -355,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 38c06eb489f..5f4a6cf88b7 100644 --- a/prowler/lib/outputs/compliance/universal/universal_table.py +++ b/prowler/lib/outputs/compliance/universal/universal_table.py @@ -186,7 +186,7 @@ def _render_grouped( effective_status = get_effective_status( finding.status, resolve_requirement_config_status( - req, audit_config, config_status_cache + req, audit_config, config_status_cache, provider_type=provider ), ) for group_key in _get_group_key(req, group_by): @@ -288,7 +288,7 @@ def _render_split( effective_status = get_effective_status( finding.status, resolve_requirement_config_status( - req, audit_config, config_status_cache + req, audit_config, config_status_cache, provider_type=provider ), ) for group_key in _get_group_key(req, group_by): @@ -418,7 +418,7 @@ def _render_scored( effective_status = get_effective_status( finding.status, resolve_requirement_config_status( - req, audit_config, config_status_cache + req, audit_config, config_status_cache, provider_type=provider ), ) for group_key in _get_group_key(req, group_by): 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/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"] From a6fbd3d28bc91fda69b703f732f453f9eaf39c96 Mon Sep 17 00:00:00 2001 From: pedrooot Date: Fri, 26 Jun 2026 13:20:32 +0200 Subject: [PATCH 18/18] chore(revision): resolve comments --- .../okta_idaas_stig/okta_idaas_stig.py | 29 ++- .../okta_idaas_stig/okta_idaas_stig_okta.py | 16 +- .../config_status_dispatch_coverage_test.py | 168 ++++++++++++++++++ .../config_status_renderer_coverage_test.py | 71 ++++++++ .../okta_idaas_stig_okta_test.py | 53 ++++++ .../okta_idaas_stig_table_test.py | 81 ++++++++- 6 files changed, 409 insertions(+), 9 deletions(-) create mode 100644 tests/lib/outputs/compliance/config_status_dispatch_coverage_test.py create mode 100644 tests/lib/outputs/compliance/config_status_renderer_coverage_test.py 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/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/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