From bf8ae351a54f1ec0e91f35866464d3cee9408cf7 Mon Sep 17 00:00:00 2001 From: Antero Silva Date: Thu, 19 Mar 2026 11:37:06 +0000 Subject: [PATCH 1/4] New queries for terraform, Azure GCP IBM and OCI --- .../README.md | 46 +++++++ .../metadata.json | 13 ++ .../query.rego | 44 +++++++ .../test/negative1.tf | 11 ++ .../test/negative2.tf | 11 ++ .../test/positive1.tf | 6 + .../test/positive2.tf | 10 ++ .../test/positive_expected_result.json | 14 ++ .../README.md | 55 ++++++++ .../metadata.json | 13 ++ .../query.rego | 38 ++++++ .../test/negative1.tf | 15 +++ .../test/positive1.tf | 8 ++ .../test/positive2.tf | 13 ++ .../test/positive_expected_result.json | 14 ++ .../README.md | 37 ++++++ .../metadata.json | 13 ++ .../query.rego | 25 ++++ .../test/negative1.tf | 16 +++ .../test/positive1.tf | 11 ++ .../test/positive_expected_result.json | 8 ++ .../README.md | 47 +++++++ .../metadata.json | 13 ++ .../query.rego | 33 +++++ .../test/negative1.tf | 9 ++ .../test/positive1.tf | 8 ++ .../test/positive2.tf | 9 ++ .../test/positive_expected_result.json | 14 ++ .../README.md | 42 ++++++ .../metadata.json | 13 ++ .../query.rego | 17 +++ .../test/negative1.tf | 11 ++ .../test/positive1.tf | 8 ++ .../test/positive_expected_result.json | 8 ++ .../azure_bastion_host_missing/README.md | 50 +++++++ .../azure_bastion_host_missing/metadata.json | 13 ++ .../azure_bastion_host_missing/query.rego | 20 +++ .../test/negative1.tf | 18 +++ .../test/positive1.tf | 6 + .../test/positive_expected_result.json | 8 ++ .../README.md | 36 ++++++ .../metadata.json | 13 ++ .../query.rego | 15 +++ .../test/positive1.tf | 4 + .../test/positive_expected_result.json | 8 ++ .../README.md | 52 ++++++++ .../metadata.json | 13 ++ .../query.rego | 18 +++ .../test/negative1.tf | 8 ++ .../test/positive1.tf | 5 + .../test/positive_expected_result.json | 8 ++ .../README.md | 68 ++++++++++ .../metadata.json | 13 ++ .../query.rego | 38 ++++++ .../test/negative1.tf | 14 ++ .../test/positive1.tf | 5 + .../test/positive2.tf | 6 + .../test/positive_expected_result.json | 14 ++ .../azure_iot_hub_defender_disabled/README.md | 72 +++++++++++ .../metadata.json | 13 ++ .../query.rego | 33 +++++ .../test/negative1.tf | 18 +++ .../test/positive1.tf | 10 ++ .../test/positive_expected_result.json | 8 ++ .../README.md | 64 +++++++++ .../metadata.json | 13 ++ .../query.rego | 17 +++ .../test/negative1.tf | 16 +++ .../test/positive1.tf | 7 + .../test/positive_expected_result.json | 8 ++ .../README.md | 60 +++++++++ .../metadata.json | 13 ++ .../query.rego | 17 +++ .../test/negative1.tf | 13 ++ .../test/positive1.tf | 8 ++ .../test/positive_expected_result.json | 8 ++ .../README.md | 59 +++++++++ .../metadata.json | 13 ++ .../query.rego | 19 +++ .../test/positive1.tf | 8 ++ .../test/positive2.tf | 7 + .../test/positive_expected_result.json | 14 ++ .../README.md | 59 +++++++++ .../metadata.json | 13 ++ .../query.rego | 19 +++ .../test/positive1.tf | 8 ++ .../test/positive2.tf | 7 + .../test/positive_expected_result.json | 14 ++ .../README.md | 65 ++++++++++ .../metadata.json | 13 ++ .../query.rego | 36 ++++++ .../test/negative1.tf | 16 +++ .../test/positive1.tf | 5 + .../test/positive_expected_result.json | 8 ++ .../README.md | 67 ++++++++++ .../metadata.json | 13 ++ .../query.rego | 54 ++++++++ .../test/negative1.tf | 22 ++++ .../test/positive1.tf | 7 + .../test/positive2.tf | 7 + .../test/positive_expected_result.json | 14 ++ .../README.md | 69 ++++++++++ .../metadata.json | 13 ++ .../query.rego | 34 +++++ .../test/negative1.tf | 7 + .../test/negative2.tf | 8 ++ .../test/positive1.tf | 7 + .../test/positive2.tf | 8 ++ .../test/positive_expected_result.json | 14 ++ .../README.md | 61 +++++++++ .../metadata.json | 13 ++ .../query.rego | 17 +++ .../test/negative1.tf | 16 +++ .../test/positive1.tf | 7 + .../test/positive_expected_result.json | 8 ++ .../README.md | 72 +++++++++++ .../metadata.json | 13 ++ .../query.rego | 33 +++++ .../test/negative1.tf | 8 ++ .../test/positive1.tf | 7 + .../test/positive2.tf | 8 ++ .../test/positive_expected_result.json | 14 ++ .../README.md | 79 ++++++++++++ .../metadata.json | 13 ++ .../query.rego | 34 +++++ .../test/negative1.tf | 16 +++ .../test/positive1.tf | 6 + .../test/positive2.tf | 12 ++ .../test/positive_expected_result.json | 14 ++ .../README.md | 56 ++++++++ .../metadata.json | 13 ++ .../query.rego | 32 +++++ .../test/negative1.tf | 13 ++ .../test/positive1.tf | 8 ++ .../test/positive_expected_result.json | 8 ++ .../README.md | 55 ++++++++ .../metadata.json | 13 ++ .../query.rego | 21 +++ .../test/negative1.tf | 7 + .../test/positive1.tf | 7 + .../test/positive_expected_result.json | 8 ++ .../README.md | 72 +++++++++++ .../metadata.json | 13 ++ .../query.rego | 33 +++++ .../test/negative1.tf | 8 ++ .../test/positive1.tf | 7 + .../test/positive2.tf | 8 ++ .../test/positive_expected_result.json | 14 ++ .../README.md | 63 +++++++++ .../metadata.json | 13 ++ .../query.rego | 58 +++++++++ .../test/negative1.tf | 13 ++ .../test/positive1.tf | 7 + .../test/positive2.tf | 13 ++ .../test/positive_expected_result.json | 14 ++ .../README.md | 76 +++++++++++ .../metadata.json | 13 ++ .../query.rego | 51 ++++++++ .../test/negative1.tf | 11 ++ .../test/positive1.tf | 7 + .../test/positive2.tf | 11 ++ .../test/positive3.tf | 11 ++ .../test/positive_expected_result.json | 20 +++ .../README.md | 46 +++++++ .../metadata.json | 13 ++ .../query.rego | 76 +++++++++++ .../test/negative1.tf | 17 +++ .../test/positive1.tf | 7 + .../test/positive2.tf | 6 + .../test/positive3.tf | 10 ++ .../test/positive_expected_result.json | 20 +++ .../README.md | 65 ++++++++++ .../metadata.json | 13 ++ .../query.rego | 33 +++++ .../test/negative1.tf | 5 + .../test/positive1.tf | 4 + .../test/positive2.tf | 5 + .../test/positive_expected_result.json | 14 ++ .../README.md | 62 +++++++++ .../metadata.json | 13 ++ .../query.rego | 34 +++++ .../test/negative1.tf | 16 +++ .../test/positive1.tf | 7 + .../test/positive2.tf | 11 ++ .../test/positive_expected_result.json | 14 ++ .../README.md | 93 +++++++++++++ .../metadata.json | 13 ++ .../query.rego | 74 +++++++++++ .../test/negative1.tf | 17 +++ .../test/negative2.tf | 19 +++ .../test/positive1.tf | 11 ++ .../test/positive2.tf | 16 +++ .../test/positive3.tf | 3 + .../test/positive4.tf | 9 ++ .../test/positive_expected_result.json | 26 ++++ .../README.md | 52 ++++++++ .../metadata.json | 13 ++ .../query.rego | 76 +++++++++++ .../test/negative1.tf | 17 +++ .../test/positive1.tf | 7 + .../test/positive2.tf | 6 + .../test/positive3.tf | 18 +++ .../test/positive_expected_result.json | 20 +++ .../gcp_access_approval_disabled/README.md | 50 +++++++ .../metadata.json | 13 ++ .../gcp_access_approval_disabled/query.rego | 38 ++++++ .../test/negative1.tf | 13 ++ .../test/positive1.tf | 5 + .../test/positive2.tf | 4 + .../test/positive_expected_result.json | 14 ++ .../gcp_api_key_api_targets_missing/README.md | 57 ++++++++ .../metadata.json | 13 ++ .../query.rego | 34 +++++ .../test/negative1.tf | 15 +++ .../test/positive1.tf | 5 + .../test/positive2.tf | 11 ++ .../test/positive_expected_result.json | 14 ++ .../gcp_api_key_restrictions_manual/README.md | 54 ++++++++ .../metadata.json | 13 ++ .../query.rego | 33 +++++ .../test/negative1.tf | 4 + .../test/positive1.tf | 6 + .../test/positive2.tf | 11 ++ .../test/positive_expected_result.json | 14 ++ .../README.md | 57 ++++++++ .../metadata.json | 13 ++ .../query.rego | 42 ++++++ .../test/negative1.tf | 10 ++ .../test/positive1.tf | 11 ++ .../test/positive2.tf | 6 + .../test/positive_expected_result.json | 14 ++ .../README.md | 49 +++++++ .../metadata.json | 13 ++ .../query.rego | 51 ++++++++ .../test/negative1.tf | 6 + .../test/positive1.tf | 4 + .../test/positive2.tf | 6 + .../test/positive3.tf | 6 + .../test/positive_expected_result.json | 20 +++ .../README.md | 51 ++++++++ .../metadata.json | 13 ++ .../query.rego | 33 +++++ .../test/negative1.tf | 6 + .../test/positive1.tf | 9 ++ .../test/positive2.tf | 9 ++ .../test/positive_expected_result.json | 14 ++ .../README.md | 46 +++++++ .../metadata.json | 13 ++ .../query.rego | 51 ++++++++ .../test/negative1.tf | 8 ++ .../test/negative2.tf | 8 ++ .../test/positive1.tf | 5 + .../test/positive2.tf | 8 ++ .../test/positive3.tf | 8 ++ .../test/positive_expected_result.json | 20 +++ .../gcp/gcp_gke_manual_iam_check/README.md | 38 ++++++ .../gcp_gke_manual_iam_check/metadata.json | 13 ++ .../gcp/gcp_gke_manual_iam_check/query.rego | 33 +++++ .../test/negative1.tf | 5 + .../test/positive1.tf | 9 ++ .../test/positive2.tf | 9 ++ .../test/positive_expected_result.json | 14 ++ .../README.md | 47 +++++++ .../metadata.json | 13 ++ .../query.rego | 65 ++++++++++ .../test/negative1.tf | 8 ++ .../test/negative2.tf | 9 ++ .../test/positive1.tf | 9 ++ .../test/positive2.tf | 9 ++ .../test/positive3.tf | 9 ++ .../test/positive4.tf | 10 ++ .../test/positive_expected_result.json | 26 ++++ .../gcp/gcp_gke_sandbox_disabled/README.md | 52 ++++++++ .../gcp_gke_sandbox_disabled/metadata.json | 13 ++ .../gcp/gcp_gke_sandbox_disabled/query.rego | 65 ++++++++++ .../test/negative1.tf | 12 ++ .../test/negative2.tf | 12 ++ .../test/positive1.tf | 9 ++ .../test/positive2.tf | 9 ++ .../test/positive3.tf | 9 ++ .../test/positive4.tf | 10 ++ .../test/positive_expected_result.json | 26 ++++ .../README.md | 50 +++++++ .../metadata.json | 13 ++ .../query.rego | 52 ++++++++ .../test/negative1.tf | 8 ++ .../test/positive1.tf | 5 + .../test/positive2.tf | 8 ++ .../test/positive3.tf | 8 ++ .../test/positive_expected_result.json | 20 +++ .../gcp_gke_security_posture_manual/README.md | 45 +++++++ .../metadata.json | 13 ++ .../query.rego | 33 +++++ .../test/negative1.tf | 6 + .../test/negative2.tf | 6 + .../test/positive1.tf | 5 + .../test/positive2.tf | 7 + .../test/positive_expected_result.json | 14 ++ .../README.md | 54 ++++++++ .../metadata.json | 13 ++ .../query.rego | 33 +++++ .../test/negative1.tf | 4 + .../test/positive1.tf | 5 + .../test/positive2.tf | 8 ++ .../test/positive_expected_result.json | 14 ++ .../README.md | 47 +++++++ .../metadata.json | 13 ++ .../query.rego | 33 +++++ .../test/negative1.tf | 8 ++ .../test/positive1.tf | 5 + .../test/positive2.tf | 7 + .../test/positive_expected_result.json | 14 ++ .../README.md | 53 ++++++++ .../metadata.json | 13 ++ .../query.rego | 51 ++++++++ .../test/negative1.tf | 8 ++ .../test/positive1.tf | 4 + .../test/positive2.tf | 6 + .../test/positive3.tf | 6 + .../test/positive_expected_result.json | 20 +++ .../README.md | 40 ++++++ .../metadata.json | 13 ++ .../query.rego | 25 ++++ .../test/negative1.tf | 11 ++ .../test/positive1.tf | 12 ++ .../test/positive2.tf | 18 +++ .../test/positive_expected_result.json | 14 ++ .../README.md | 51 ++++++++ .../metadata.json | 13 ++ .../query.rego | 49 +++++++ .../test/negative1.tf | 11 ++ .../test/positive1.tf | 12 ++ .../test/positive2.tf | 11 ++ .../test/positive_expected_result.json | 14 ++ .../README.md | 65 ++++++++++ .../metadata.json | 13 ++ .../query.rego | 40 ++++++ .../test/negative1.tf | 10 ++ .../test/positive1.tf | 7 + .../test/positive2.tf | 10 ++ .../test/positive_expected_result.json | 14 ++ .../README.md | 86 ++++++++++++ .../metadata.json | 13 ++ .../query.rego | 54 ++++++++ .../test/negative1.tf | 11 ++ .../test/positive1.tf | 8 ++ .../test/positive2.tf | 11 ++ .../test/positive3.tf | 13 ++ .../test/positive_expected_result.json | 20 +++ .../README.md | 58 +++++++++ .../metadata.json | 13 ++ .../query.rego | 17 +++ .../test/negative1.tf | 12 ++ .../test/positive1.tf | 10 ++ .../test/positive_expected_result.json | 8 ++ .../README.md | 65 ++++++++++ .../metadata.json | 13 ++ .../query.rego | 33 +++++ .../test/negative1.tf | 8 ++ .../test/positive1.tf | 6 + .../test/positive2.tf | 8 ++ .../test/positive_expected_result.json | 14 ++ .../ibm_cis_dns_not_proxied_manual/README.md | 77 +++++++++++ .../metadata.json | 13 ++ .../ibm_cis_dns_not_proxied_manual/query.rego | 33 +++++ .../test/negative1.tf | 10 ++ .../test/positive1.tf | 10 ++ .../test/positive2.tf | 8 ++ .../test/positive_expected_result.json | 14 ++ .../ibm/ibm_cis_waf_enabled_manual/README.md | 72 +++++++++++ .../ibm_cis_waf_enabled_manual/metadata.json | 13 ++ .../ibm/ibm_cis_waf_enabled_manual/query.rego | 34 +++++ .../test/negative1.tf | 7 + .../test/positive1.tf | 7 + .../test/positive2.tf | 6 + .../test/positive_expected_result.json | 14 ++ .../README.md | 75 +++++++++++ .../metadata.json | 13 ++ .../query.rego | 36 ++++++ .../test/negative1.tf | 10 ++ .../test/positive1.tf | 6 + .../test/positive2.tf | 10 ++ .../test/positive_expected_result.json | 14 ++ .../README.md | 62 +++++++++ .../metadata.json | 13 ++ .../query.rego | 17 +++ .../test/negative1.tf | 15 +++ .../test/positive1.tf | 13 ++ .../test/positive_expected_result.json | 8 ++ .../README.md | 54 ++++++++ .../metadata.json | 13 ++ .../query.rego | 19 +++ .../test/negative1.tf | 18 +++ .../test/positive1.tf | 13 ++ .../test/positive_expected_result.json | 8 ++ .../README.md | 60 +++++++++ .../metadata.json | 13 ++ .../query.rego | 17 +++ .../test/negative1.tf | 13 ++ .../test/positive1.tf | 11 ++ .../test/positive_expected_result.json | 8 ++ .../README.md | 57 ++++++++ .../metadata.json | 13 ++ .../query.rego | 17 +++ .../test/negative1.tf | 13 ++ .../test/positive1.tf | 11 ++ .../test/positive_expected_result.json | 8 ++ .../README.md | 69 ++++++++++ .../metadata.json | 13 ++ .../query.rego | 34 +++++ .../test/negative1.tf | 7 + .../test/positive1.tf | 4 + .../test/positive2.tf | 3 + .../test/positive_expected_result.json | 14 ++ .../ibm_iam_account_mfa_disabled/README.md | 46 +++++++ .../metadata.json | 13 ++ .../ibm_iam_account_mfa_disabled/query.rego | 50 +++++++ .../test/negative1.tf | 4 + .../test/positive1.tf | 8 ++ .../test/positive2.tf | 4 + .../test/positive3.tf | 4 + .../test/positive_expected_result.json | 20 +++ .../ibm_iam_api_key_unused_manual/README.md | 60 +++++++++ .../metadata.json | 13 ++ .../ibm_iam_api_key_unused_manual/query.rego | 29 +++++ .../test/negative1.tf | 8 ++ .../test/positive1.tf | 3 + .../test/positive2.tf | 4 + .../test/positive_expected_result.json | 14 ++ .../ibm_iam_owner_api_key_manual/README.md | 50 +++++++ .../metadata.json | 13 ++ .../ibm_iam_owner_api_key_manual/query.rego | 15 +++ .../test/negative1.tf | 9 ++ .../test/positive1.tf | 3 + .../test/positive_expected_result.json | 8 ++ .../README.md | 65 ++++++++++ .../metadata.json | 13 ++ .../query.rego | 15 +++ .../test/negative1.tf | 14 ++ .../test/positive1.tf | 8 ++ .../test/positive_expected_result.json | 8 ++ .../README.md | 50 +++++++ .../metadata.json | 13 ++ .../query.rego | 38 ++++++ .../test/negative1.tf | 4 + .../test/positive1.tf | 8 ++ .../test/positive2.tf | 8 ++ .../test/positive_expected_result.json | 14 ++ .../README.md | 56 ++++++++ .../metadata.json | 13 ++ .../query.rego | 35 +++++ .../test/negative1.tf | 4 + .../test/positive1.tf | 3 + .../test/positive2.tf | 3 + .../test/positive_expected_result.json | 14 ++ .../README.md | 55 ++++++++ .../metadata.json | 13 ++ .../query.rego | 21 +++ .../test/negative1.tf | 13 ++ .../test/positive1.tf | 8 ++ .../test/positive_expected_result.json | 8 ++ .../README.md | 55 ++++++++ .../metadata.json | 13 ++ .../query.rego | 22 ++++ .../test/negative1.tf | 11 ++ .../test/positive1.tf | 8 ++ .../test/positive_expected_result.json | 8 ++ .../README.md | 51 ++++++++ .../metadata.json | 13 ++ .../query.rego | 39 ++++++ .../test/negative1.tf | 10 ++ .../test/positive1.tf | 8 ++ .../test/positive2.tf | 10 ++ .../test/positive_expected_result.json | 14 ++ .../ibm_kms_key_rotation_disabled/README.md | 63 +++++++++ .../metadata.json | 13 ++ .../ibm_kms_key_rotation_disabled/query.rego | 47 +++++++ .../test/negative1.tf | 9 ++ .../test/positive1.tf | 5 + .../test/positive2.tf | 9 ++ .../test/positive3.tf | 9 ++ .../test/positive_expected_result.json | 20 +++ .../ibm_logdna_archiving_disabled/README.md | 53 ++++++++ .../metadata.json | 13 ++ .../ibm_logdna_archiving_disabled/query.rego | 22 ++++ .../test/negative1.tf | 15 +++ .../test/positive1.tf | 5 + .../test/positive_expected_result.json | 8 ++ .../ibm_logdna_view_without_alert/README.md | 51 ++++++++ .../metadata.json | 13 ++ .../ibm_logdna_view_without_alert/query.rego | 22 ++++ .../test/negative1.tf | 15 +++ .../test/positive1.tf | 4 + .../test/positive_expected_result.json | 8 ++ .../README.md | 95 ++++++++++++++ .../metadata.json | 13 ++ .../query.rego | 24 ++++ .../test/negative1.tf | 17 +++ .../test/positive1.tf | 9 ++ .../test/positive2.tf | 17 +++ .../test/positive3.tf | 17 +++ .../test/positive_expected_result.json | 20 +++ .../README.md | 76 +++++++++++ .../metadata.json | 13 ++ .../query.rego | 52 ++++++++ .../test/negative1.tf | 10 ++ .../test/positive1.tf | 9 ++ .../test/positive2.tf | 9 ++ .../test/positive3.tf | 10 ++ .../test/positive_expected_result.json | 20 +++ .../README.md | 71 ++++++++++ .../metadata.json | 13 ++ .../query.rego | 48 +++++++ .../test/negative1.tf | 10 ++ .../test/positive1.tf | 6 + .../test/positive2.tf | 10 ++ .../test/positive3.tf | 10 ++ .../test/positive_expected_result.json | 20 +++ .../README.md | 73 +++++++++++ .../metadata.json | 13 ++ .../query.rego | 48 +++++++ .../test/negative1.tf | 10 ++ .../test/positive1.tf | 6 + .../test/positive2.tf | 10 ++ .../test/positive3.tf | 10 ++ .../test/positive_expected_result.json | 20 +++ .../oci_default_tags_not_defined/README.md | 69 ++++++++++ .../metadata.json | 13 ++ .../oci_default_tags_not_defined/query.rego | 20 +++ .../test/negative1.tf | 22 ++++ .../test/positive1.tf | 8 ++ .../test/positive_expected_result.json | 8 ++ .../README.md | 122 ++++++++++++++++++ .../metadata.json | 13 ++ .../query.rego | 77 +++++++++++ .../test/negative1.tf | 23 ++++ .../test/positive1.tf | 8 ++ .../test/positive2.tf | 20 +++ .../test/positive3.tf | 24 ++++ .../test/positive_expected_result.json | 20 +++ .../README.md | 49 +++++++ .../metadata.json | 13 ++ .../query.rego | 47 +++++++ .../test/negative1.tf | 7 + .../test/positive1.tf | 7 + .../test/positive2.tf | 7 + .../test/positive3.tf | 7 + .../test/positive_expected_result.json | 20 +++ .../oci_iam_password_policy_length/README.md | 55 ++++++++ .../metadata.json | 13 ++ .../oci_iam_password_policy_length/query.rego | 62 +++++++++ .../test/negative1.tf | 9 ++ .../test/positive1.tf | 9 ++ .../test/positive2.tf | 8 ++ .../test/positive3.tf | 4 + .../test/positive_expected_result.json | 20 +++ .../oci_iam_password_reuse_manual/README.md | 55 ++++++++ .../metadata.json | 13 ++ .../oci_iam_password_reuse_manual/query.rego | 47 +++++++ .../test/negative1.tf | 9 ++ .../test/positive1.tf | 9 ++ .../test/positive2.tf | 8 ++ .../test/positive3.tf | 8 ++ .../test/positive_expected_result.json | 20 +++ .../README.md | 65 ++++++++++ .../metadata.json | 13 ++ .../query.rego | 76 +++++++++++ .../test/negative1.tf | 25 ++++ .../test/positive1.tf | 10 ++ .../test/positive2.tf | 25 ++++ .../test/positive3.tf | 26 ++++ .../test/positive_expected_result.json | 20 +++ .../oci_iam_service_admins_manual/README.md | 46 +++++++ .../metadata.json | 13 ++ .../oci_iam_service_admins_manual/query.rego | 19 +++ .../test/negative1.tf | 14 ++ .../test/negative2.tf | 13 ++ .../test/positive1.tf | 23 ++++ .../test/positive_expected_result.json | 14 ++ .../README.md | 57 ++++++++ .../metadata.json | 13 ++ .../query.rego | 78 +++++++++++ .../test/negative1.tf | 27 ++++ .../test/positive1.tf | 8 ++ .../test/positive2.tf | 25 ++++ .../test/positive3.tf | 28 ++++ .../test/positive_expected_result.json | 20 +++ .../README.md | 47 +++++++ .../metadata.json | 13 ++ .../query.rego | 43 ++++++ .../test/negative1.tf | 23 ++++ .../test/positive1.tf | 20 +++ .../test/positive2.tf | 24 ++++ .../test/positive_expected_result.json | 14 ++ .../README.md | 45 +++++++ .../metadata.json | 13 ++ .../query.rego | 43 ++++++ .../test/negative1.tf | 23 ++++ .../test/positive1.tf | 20 +++ .../test/positive2.tf | 24 ++++ .../test/positive_expected_result.json | 14 ++ .../oci_instance_transit_encryption/README.md | 54 ++++++++ .../metadata.json | 13 ++ .../query.rego | 58 +++++++++ .../test/negative1.tf | 14 ++ .../test/positive1.tf | 20 +++ .../test/positive2.tf | 15 +++ .../test/positive3.tf | 15 +++ .../test/positive_expected_result.json | 20 +++ .../README.md | 45 +++++++ .../metadata.json | 13 ++ .../query.rego | 43 ++++++ .../test/negative1.tf | 23 ++++ .../test/positive1.tf | 20 +++ .../test/positive2.tf | 24 ++++ .../test/positive_expected_result.json | 14 ++ .../README.md | 67 ++++++++++ .../metadata.json | 13 ++ .../query.rego | 88 +++++++++++++ .../test/negative1.tf | 43 ++++++ .../test/positive1.tf | 8 ++ .../test/positive2.tf | 25 ++++ .../test/positive3.tf | 38 ++++++ .../test/positive_expected_result.json | 20 +++ .../README.md | 44 +++++++ .../metadata.json | 13 ++ .../query.rego | 44 +++++++ .../test/negative1.tf | 16 +++ .../test/positive1.tf | 8 ++ .../test/positive2.tf | 9 ++ .../test/positive_expected_result.json | 14 ++ .../README.md | 53 ++++++++ .../metadata.json | 13 ++ .../query.rego | 76 +++++++++++ .../test/negative1.tf | 25 ++++ .../test/positive1.tf | 9 ++ .../test/positive2.tf | 24 ++++ .../test/positive3.tf | 26 ++++ .../test/positive_expected_result.json | 20 +++ .../README.md | 34 +++++ .../metadata.json | 13 ++ .../query.rego | 32 +++++ .../test/negative1.tf | 12 ++ .../test/positive1.tf | 11 ++ .../test/positive2.tf | 12 ++ .../test/positive_expected_result.json | 14 ++ .../README.md | 34 +++++ .../metadata.json | 13 ++ .../query.rego | 34 +++++ .../test/negative1.tf | 12 ++ .../test/positive1.tf | 11 ++ .../test/positive2.tf | 12 ++ .../test/positive_expected_result.json | 14 ++ .../README.md | 35 +++++ .../metadata.json | 13 ++ .../query.rego | 52 ++++++++ .../test/negative1.tf | 17 +++ .../test/positive1.tf | 12 ++ .../test/positive2.tf | 10 ++ .../test/positive_expected_result.json | 14 ++ .../README.md | 53 ++++++++ .../metadata.json | 13 ++ .../query.rego | 76 +++++++++++ .../test/negative1.tf | 25 ++++ .../test/positive1.tf | 9 ++ .../test/positive2.tf | 24 ++++ .../test/positive3.tf | 26 ++++ .../test/positive_expected_result.json | 20 +++ .../README.md | 54 ++++++++ .../metadata.json | 13 ++ .../query.rego | 75 +++++++++++ .../test/negative1.tf | 24 ++++ .../test/positive1.tf | 9 ++ .../test/positive2.tf | 23 ++++ .../test/positive3.tf | 25 ++++ .../test/positive_expected_result.json | 20 +++ .../README.md | 40 ++++++ .../metadata.json | 13 ++ .../query.rego | 29 +++++ .../test/negative1.tf | 13 ++ .../test/negative2.tf | 13 ++ .../test/positive1.tf | 13 ++ .../test/positive2.tf | 13 ++ .../test/positive_expected_result.json | 14 ++ .../README.md | 40 ++++++ .../metadata.json | 13 ++ .../query.rego | 44 +++++++ .../test/negative1.tf | 11 ++ .../test/negative2.tf | 11 ++ .../test/positive1.tf | 9 ++ .../test/positive2.tf | 11 ++ .../test/positive_expected_result.json | 14 ++ .../README.md | 56 ++++++++ .../metadata.json | 13 ++ .../query.rego | 73 +++++++++++ .../test/negative1.tf | 21 +++ .../test/positive1.tf | 6 + .../test/positive2.tf | 15 +++ .../test/positive3.tf | 15 +++ .../test/positive4.tf | 14 ++ .../test/positive_expected_result.json | 26 ++++ .../README.md | 54 ++++++++ .../metadata.json | 13 ++ .../query.rego | 75 +++++++++++ .../test/negative1.tf | 24 ++++ .../test/positive1.tf | 9 ++ .../test/positive2.tf | 23 ++++ .../test/positive3.tf | 25 ++++ .../test/positive_expected_result.json | 20 +++ 709 files changed, 15864 insertions(+) create mode 100644 assets/queries/terraform/azure/azure_app_service_application_insights_not_configured/README.md create mode 100644 assets/queries/terraform/azure/azure_app_service_application_insights_not_configured/metadata.json create mode 100644 assets/queries/terraform/azure/azure_app_service_application_insights_not_configured/query.rego create mode 100644 assets/queries/terraform/azure/azure_app_service_application_insights_not_configured/test/negative1.tf create mode 100644 assets/queries/terraform/azure/azure_app_service_application_insights_not_configured/test/negative2.tf create mode 100644 assets/queries/terraform/azure/azure_app_service_application_insights_not_configured/test/positive1.tf create mode 100644 assets/queries/terraform/azure/azure_app_service_application_insights_not_configured/test/positive2.tf create mode 100644 assets/queries/terraform/azure/azure_app_service_application_insights_not_configured/test/positive_expected_result.json create mode 100644 assets/queries/terraform/azure/azure_app_service_http_logs_disabled/README.md create mode 100644 assets/queries/terraform/azure/azure_app_service_http_logs_disabled/metadata.json create mode 100644 assets/queries/terraform/azure/azure_app_service_http_logs_disabled/query.rego create mode 100644 assets/queries/terraform/azure/azure_app_service_http_logs_disabled/test/negative1.tf create mode 100644 assets/queries/terraform/azure/azure_app_service_http_logs_disabled/test/positive1.tf create mode 100644 assets/queries/terraform/azure/azure_app_service_http_logs_disabled/test/positive2.tf create mode 100644 assets/queries/terraform/azure/azure_app_service_http_logs_disabled/test/positive_expected_result.json create mode 100644 assets/queries/terraform/azure/azure_backup_vault_cmk_encryption_disabled/README.md create mode 100644 assets/queries/terraform/azure/azure_backup_vault_cmk_encryption_disabled/metadata.json create mode 100644 assets/queries/terraform/azure/azure_backup_vault_cmk_encryption_disabled/query.rego create mode 100644 assets/queries/terraform/azure/azure_backup_vault_cmk_encryption_disabled/test/negative1.tf create mode 100644 assets/queries/terraform/azure/azure_backup_vault_cmk_encryption_disabled/test/positive1.tf create mode 100644 assets/queries/terraform/azure/azure_backup_vault_cmk_encryption_disabled/test/positive_expected_result.json create mode 100644 assets/queries/terraform/azure/azure_backup_vault_cross_region_restore_disabled/README.md create mode 100644 assets/queries/terraform/azure/azure_backup_vault_cross_region_restore_disabled/metadata.json create mode 100644 assets/queries/terraform/azure/azure_backup_vault_cross_region_restore_disabled/query.rego create mode 100644 assets/queries/terraform/azure/azure_backup_vault_cross_region_restore_disabled/test/negative1.tf create mode 100644 assets/queries/terraform/azure/azure_backup_vault_cross_region_restore_disabled/test/positive1.tf create mode 100644 assets/queries/terraform/azure/azure_backup_vault_cross_region_restore_disabled/test/positive2.tf create mode 100644 assets/queries/terraform/azure/azure_backup_vault_cross_region_restore_disabled/test/positive_expected_result.json create mode 100644 assets/queries/terraform/azure/azure_backup_vault_infrastructure_encryption_disabled/README.md create mode 100644 assets/queries/terraform/azure/azure_backup_vault_infrastructure_encryption_disabled/metadata.json create mode 100644 assets/queries/terraform/azure/azure_backup_vault_infrastructure_encryption_disabled/query.rego create mode 100644 assets/queries/terraform/azure/azure_backup_vault_infrastructure_encryption_disabled/test/negative1.tf create mode 100644 assets/queries/terraform/azure/azure_backup_vault_infrastructure_encryption_disabled/test/positive1.tf create mode 100644 assets/queries/terraform/azure/azure_backup_vault_infrastructure_encryption_disabled/test/positive_expected_result.json create mode 100644 assets/queries/terraform/azure/azure_bastion_host_missing/README.md create mode 100644 assets/queries/terraform/azure/azure_bastion_host_missing/metadata.json create mode 100644 assets/queries/terraform/azure/azure_bastion_host_missing/query.rego create mode 100644 assets/queries/terraform/azure/azure_bastion_host_missing/test/negative1.tf create mode 100644 assets/queries/terraform/azure/azure_bastion_host_missing/test/positive1.tf create mode 100644 assets/queries/terraform/azure/azure_bastion_host_missing/test/positive_expected_result.json create mode 100644 assets/queries/terraform/azure/azure_defender_easm_enabled_manual/README.md create mode 100644 assets/queries/terraform/azure/azure_defender_easm_enabled_manual/metadata.json create mode 100644 assets/queries/terraform/azure/azure_defender_easm_enabled_manual/query.rego create mode 100644 assets/queries/terraform/azure/azure_defender_easm_enabled_manual/test/positive1.tf create mode 100644 assets/queries/terraform/azure/azure_defender_easm_enabled_manual/test/positive_expected_result.json create mode 100644 assets/queries/terraform/azure/azure_elastic_san_public_access_enabled/README.md create mode 100644 assets/queries/terraform/azure/azure_elastic_san_public_access_enabled/metadata.json create mode 100644 assets/queries/terraform/azure/azure_elastic_san_public_access_enabled/query.rego create mode 100644 assets/queries/terraform/azure/azure_elastic_san_public_access_enabled/test/negative1.tf create mode 100644 assets/queries/terraform/azure/azure_elastic_san_public_access_enabled/test/positive1.tf create mode 100644 assets/queries/terraform/azure/azure_elastic_san_public_access_enabled/test/positive_expected_result.json create mode 100644 assets/queries/terraform/azure/azure_elastic_san_volume_group_cmk_encryption_disabled/README.md create mode 100644 assets/queries/terraform/azure/azure_elastic_san_volume_group_cmk_encryption_disabled/metadata.json create mode 100644 assets/queries/terraform/azure/azure_elastic_san_volume_group_cmk_encryption_disabled/query.rego create mode 100644 assets/queries/terraform/azure/azure_elastic_san_volume_group_cmk_encryption_disabled/test/negative1.tf create mode 100644 assets/queries/terraform/azure/azure_elastic_san_volume_group_cmk_encryption_disabled/test/positive1.tf create mode 100644 assets/queries/terraform/azure/azure_elastic_san_volume_group_cmk_encryption_disabled/test/positive2.tf create mode 100644 assets/queries/terraform/azure/azure_elastic_san_volume_group_cmk_encryption_disabled/test/positive_expected_result.json create mode 100644 assets/queries/terraform/azure/azure_iot_hub_defender_disabled/README.md create mode 100644 assets/queries/terraform/azure/azure_iot_hub_defender_disabled/metadata.json create mode 100644 assets/queries/terraform/azure/azure_iot_hub_defender_disabled/query.rego create mode 100644 assets/queries/terraform/azure/azure_iot_hub_defender_disabled/test/negative1.tf create mode 100644 assets/queries/terraform/azure/azure_iot_hub_defender_disabled/test/positive1.tf create mode 100644 assets/queries/terraform/azure/azure_iot_hub_defender_disabled/test/positive_expected_result.json create mode 100644 assets/queries/terraform/azure/azure_key_vault_key_rotation_disabled/README.md create mode 100644 assets/queries/terraform/azure/azure_key_vault_key_rotation_disabled/metadata.json create mode 100644 assets/queries/terraform/azure/azure_key_vault_key_rotation_disabled/query.rego create mode 100644 assets/queries/terraform/azure/azure_key_vault_key_rotation_disabled/test/negative1.tf create mode 100644 assets/queries/terraform/azure/azure_key_vault_key_rotation_disabled/test/positive1.tf create mode 100644 assets/queries/terraform/azure/azure_key_vault_key_rotation_disabled/test/positive_expected_result.json create mode 100644 assets/queries/terraform/azure/azure_managed_lustre_cmk_encryption_disabled/README.md create mode 100644 assets/queries/terraform/azure/azure_managed_lustre_cmk_encryption_disabled/metadata.json create mode 100644 assets/queries/terraform/azure/azure_managed_lustre_cmk_encryption_disabled/query.rego create mode 100644 assets/queries/terraform/azure/azure_managed_lustre_cmk_encryption_disabled/test/negative1.tf create mode 100644 assets/queries/terraform/azure/azure_managed_lustre_cmk_encryption_disabled/test/positive1.tf create mode 100644 assets/queries/terraform/azure/azure_managed_lustre_cmk_encryption_disabled/test/positive_expected_result.json create mode 100644 assets/queries/terraform/azure/azure_mysql_audit_log_enabled_manual/README.md create mode 100644 assets/queries/terraform/azure/azure_mysql_audit_log_enabled_manual/metadata.json create mode 100644 assets/queries/terraform/azure/azure_mysql_audit_log_enabled_manual/query.rego create mode 100644 assets/queries/terraform/azure/azure_mysql_audit_log_enabled_manual/test/positive1.tf create mode 100644 assets/queries/terraform/azure/azure_mysql_audit_log_enabled_manual/test/positive2.tf create mode 100644 assets/queries/terraform/azure/azure_mysql_audit_log_enabled_manual/test/positive_expected_result.json create mode 100644 assets/queries/terraform/azure/azure_mysql_audit_log_events_connection_manual/README.md create mode 100644 assets/queries/terraform/azure/azure_mysql_audit_log_events_connection_manual/metadata.json create mode 100644 assets/queries/terraform/azure/azure_mysql_audit_log_events_connection_manual/query.rego create mode 100644 assets/queries/terraform/azure/azure_mysql_audit_log_events_connection_manual/test/positive1.tf create mode 100644 assets/queries/terraform/azure/azure_mysql_audit_log_events_connection_manual/test/positive2.tf create mode 100644 assets/queries/terraform/azure/azure_mysql_audit_log_events_connection_manual/test/positive_expected_result.json create mode 100644 assets/queries/terraform/azure/azure_netapp_account_cmk_encryption_disabled/README.md create mode 100644 assets/queries/terraform/azure/azure_netapp_account_cmk_encryption_disabled/metadata.json create mode 100644 assets/queries/terraform/azure/azure_netapp_account_cmk_encryption_disabled/query.rego create mode 100644 assets/queries/terraform/azure/azure_netapp_account_cmk_encryption_disabled/test/negative1.tf create mode 100644 assets/queries/terraform/azure/azure_netapp_account_cmk_encryption_disabled/test/positive1.tf create mode 100644 assets/queries/terraform/azure/azure_netapp_account_cmk_encryption_disabled/test/positive_expected_result.json create mode 100644 assets/queries/terraform/azure/azure_paas_private_endpoint_missing/README.md create mode 100644 assets/queries/terraform/azure/azure_paas_private_endpoint_missing/metadata.json create mode 100644 assets/queries/terraform/azure/azure_paas_private_endpoint_missing/query.rego create mode 100644 assets/queries/terraform/azure/azure_paas_private_endpoint_missing/test/negative1.tf create mode 100644 assets/queries/terraform/azure/azure_paas_private_endpoint_missing/test/positive1.tf create mode 100644 assets/queries/terraform/azure/azure_paas_private_endpoint_missing/test/positive2.tf create mode 100644 assets/queries/terraform/azure/azure_paas_private_endpoint_missing/test/positive_expected_result.json create mode 100644 assets/queries/terraform/azure/azure_production_workload_basic_consumption_sku/README.md create mode 100644 assets/queries/terraform/azure/azure_production_workload_basic_consumption_sku/metadata.json create mode 100644 assets/queries/terraform/azure/azure_production_workload_basic_consumption_sku/query.rego create mode 100644 assets/queries/terraform/azure/azure_production_workload_basic_consumption_sku/test/negative1.tf create mode 100644 assets/queries/terraform/azure/azure_production_workload_basic_consumption_sku/test/negative2.tf create mode 100644 assets/queries/terraform/azure/azure_production_workload_basic_consumption_sku/test/positive1.tf create mode 100644 assets/queries/terraform/azure/azure_production_workload_basic_consumption_sku/test/positive2.tf create mode 100644 assets/queries/terraform/azure/azure_production_workload_basic_consumption_sku/test/positive_expected_result.json create mode 100644 assets/queries/terraform/azure/azure_recovery_services_vault_cmk_encryption_disabled/README.md create mode 100644 assets/queries/terraform/azure/azure_recovery_services_vault_cmk_encryption_disabled/metadata.json create mode 100644 assets/queries/terraform/azure/azure_recovery_services_vault_cmk_encryption_disabled/query.rego create mode 100644 assets/queries/terraform/azure/azure_recovery_services_vault_cmk_encryption_disabled/test/negative1.tf create mode 100644 assets/queries/terraform/azure/azure_recovery_services_vault_cmk_encryption_disabled/test/positive1.tf create mode 100644 assets/queries/terraform/azure/azure_recovery_services_vault_cmk_encryption_disabled/test/positive_expected_result.json create mode 100644 assets/queries/terraform/azure/azure_recovery_services_vault_cross_region_restore_disabled/README.md create mode 100644 assets/queries/terraform/azure/azure_recovery_services_vault_cross_region_restore_disabled/metadata.json create mode 100644 assets/queries/terraform/azure/azure_recovery_services_vault_cross_region_restore_disabled/query.rego create mode 100644 assets/queries/terraform/azure/azure_recovery_services_vault_cross_region_restore_disabled/test/negative1.tf create mode 100644 assets/queries/terraform/azure/azure_recovery_services_vault_cross_region_restore_disabled/test/positive1.tf create mode 100644 assets/queries/terraform/azure/azure_recovery_services_vault_cross_region_restore_disabled/test/positive2.tf create mode 100644 assets/queries/terraform/azure/azure_recovery_services_vault_cross_region_restore_disabled/test/positive_expected_result.json create mode 100644 assets/queries/terraform/azure/azure_recovery_services_vault_infrastructure_encryption_disabled/README.md create mode 100644 assets/queries/terraform/azure/azure_recovery_services_vault_infrastructure_encryption_disabled/metadata.json create mode 100644 assets/queries/terraform/azure/azure_recovery_services_vault_infrastructure_encryption_disabled/query.rego create mode 100644 assets/queries/terraform/azure/azure_recovery_services_vault_infrastructure_encryption_disabled/test/negative1.tf create mode 100644 assets/queries/terraform/azure/azure_recovery_services_vault_infrastructure_encryption_disabled/test/positive1.tf create mode 100644 assets/queries/terraform/azure/azure_recovery_services_vault_infrastructure_encryption_disabled/test/positive2.tf create mode 100644 assets/queries/terraform/azure/azure_recovery_services_vault_infrastructure_encryption_disabled/test/positive_expected_result.json create mode 100644 assets/queries/terraform/azure/azure_sql_server_tde_cmk_disabled/README.md create mode 100644 assets/queries/terraform/azure/azure_sql_server_tde_cmk_disabled/metadata.json create mode 100644 assets/queries/terraform/azure/azure_sql_server_tde_cmk_disabled/query.rego create mode 100644 assets/queries/terraform/azure/azure_sql_server_tde_cmk_disabled/test/negative1.tf create mode 100644 assets/queries/terraform/azure/azure_sql_server_tde_cmk_disabled/test/positive1.tf create mode 100644 assets/queries/terraform/azure/azure_sql_server_tde_cmk_disabled/test/positive_expected_result.json create mode 100644 assets/queries/terraform/azure/azure_storage_account_geo_redundancy_disabled/README.md create mode 100644 assets/queries/terraform/azure/azure_storage_account_geo_redundancy_disabled/metadata.json create mode 100644 assets/queries/terraform/azure/azure_storage_account_geo_redundancy_disabled/query.rego create mode 100644 assets/queries/terraform/azure/azure_storage_account_geo_redundancy_disabled/test/negative1.tf create mode 100644 assets/queries/terraform/azure/azure_storage_account_geo_redundancy_disabled/test/positive1.tf create mode 100644 assets/queries/terraform/azure/azure_storage_account_geo_redundancy_disabled/test/positive_expected_result.json create mode 100644 assets/queries/terraform/azure/azure_storage_account_infrastructure_encryption_disabled/README.md create mode 100644 assets/queries/terraform/azure/azure_storage_account_infrastructure_encryption_disabled/metadata.json create mode 100644 assets/queries/terraform/azure/azure_storage_account_infrastructure_encryption_disabled/query.rego create mode 100644 assets/queries/terraform/azure/azure_storage_account_infrastructure_encryption_disabled/test/negative1.tf create mode 100644 assets/queries/terraform/azure/azure_storage_account_infrastructure_encryption_disabled/test/positive1.tf create mode 100644 assets/queries/terraform/azure/azure_storage_account_infrastructure_encryption_disabled/test/positive2.tf create mode 100644 assets/queries/terraform/azure/azure_storage_account_infrastructure_encryption_disabled/test/positive_expected_result.json create mode 100644 assets/queries/terraform/azure/azure_storage_account_read_only_lock_missing/README.md create mode 100644 assets/queries/terraform/azure/azure_storage_account_read_only_lock_missing/metadata.json create mode 100644 assets/queries/terraform/azure/azure_storage_account_read_only_lock_missing/query.rego create mode 100644 assets/queries/terraform/azure/azure_storage_account_read_only_lock_missing/test/negative1.tf create mode 100644 assets/queries/terraform/azure/azure_storage_account_read_only_lock_missing/test/positive1.tf create mode 100644 assets/queries/terraform/azure/azure_storage_account_read_only_lock_missing/test/positive2.tf create mode 100644 assets/queries/terraform/azure/azure_storage_account_read_only_lock_missing/test/positive_expected_result.json create mode 100644 assets/queries/terraform/azure/azure_storage_account_versioning_disabled/README.md create mode 100644 assets/queries/terraform/azure/azure_storage_account_versioning_disabled/metadata.json create mode 100644 assets/queries/terraform/azure/azure_storage_account_versioning_disabled/query.rego create mode 100644 assets/queries/terraform/azure/azure_storage_account_versioning_disabled/test/negative1.tf create mode 100644 assets/queries/terraform/azure/azure_storage_account_versioning_disabled/test/positive1.tf create mode 100644 assets/queries/terraform/azure/azure_storage_account_versioning_disabled/test/positive2.tf create mode 100644 assets/queries/terraform/azure/azure_storage_account_versioning_disabled/test/positive3.tf create mode 100644 assets/queries/terraform/azure/azure_storage_account_versioning_disabled/test/positive_expected_result.json create mode 100644 assets/queries/terraform/azure/azure_storage_blob_logging_disabled/README.md create mode 100644 assets/queries/terraform/azure/azure_storage_blob_logging_disabled/metadata.json create mode 100644 assets/queries/terraform/azure/azure_storage_blob_logging_disabled/query.rego create mode 100644 assets/queries/terraform/azure/azure_storage_blob_logging_disabled/test/negative1.tf create mode 100644 assets/queries/terraform/azure/azure_storage_blob_logging_disabled/test/positive1.tf create mode 100644 assets/queries/terraform/azure/azure_storage_blob_logging_disabled/test/positive2.tf create mode 100644 assets/queries/terraform/azure/azure_storage_blob_logging_disabled/test/positive3.tf create mode 100644 assets/queries/terraform/azure/azure_storage_blob_logging_disabled/test/positive_expected_result.json create mode 100644 assets/queries/terraform/azure/azure_storage_container_immutability_not_locked/README.md create mode 100644 assets/queries/terraform/azure/azure_storage_container_immutability_not_locked/metadata.json create mode 100644 assets/queries/terraform/azure/azure_storage_container_immutability_not_locked/query.rego create mode 100644 assets/queries/terraform/azure/azure_storage_container_immutability_not_locked/test/negative1.tf create mode 100644 assets/queries/terraform/azure/azure_storage_container_immutability_not_locked/test/positive1.tf create mode 100644 assets/queries/terraform/azure/azure_storage_container_immutability_not_locked/test/positive2.tf create mode 100644 assets/queries/terraform/azure/azure_storage_container_immutability_not_locked/test/positive_expected_result.json create mode 100644 assets/queries/terraform/azure/azure_storage_critical_data_cmk_manual/README.md create mode 100644 assets/queries/terraform/azure/azure_storage_critical_data_cmk_manual/metadata.json create mode 100644 assets/queries/terraform/azure/azure_storage_critical_data_cmk_manual/query.rego create mode 100644 assets/queries/terraform/azure/azure_storage_critical_data_cmk_manual/test/negative1.tf create mode 100644 assets/queries/terraform/azure/azure_storage_critical_data_cmk_manual/test/positive1.tf create mode 100644 assets/queries/terraform/azure/azure_storage_critical_data_cmk_manual/test/positive2.tf create mode 100644 assets/queries/terraform/azure/azure_storage_critical_data_cmk_manual/test/positive_expected_result.json create mode 100644 assets/queries/terraform/azure/azure_storage_queue_logging_disabled/README.md create mode 100644 assets/queries/terraform/azure/azure_storage_queue_logging_disabled/metadata.json create mode 100644 assets/queries/terraform/azure/azure_storage_queue_logging_disabled/query.rego create mode 100644 assets/queries/terraform/azure/azure_storage_queue_logging_disabled/test/negative1.tf create mode 100644 assets/queries/terraform/azure/azure_storage_queue_logging_disabled/test/negative2.tf create mode 100644 assets/queries/terraform/azure/azure_storage_queue_logging_disabled/test/positive1.tf create mode 100644 assets/queries/terraform/azure/azure_storage_queue_logging_disabled/test/positive2.tf create mode 100644 assets/queries/terraform/azure/azure_storage_queue_logging_disabled/test/positive3.tf create mode 100644 assets/queries/terraform/azure/azure_storage_queue_logging_disabled/test/positive4.tf create mode 100644 assets/queries/terraform/azure/azure_storage_queue_logging_disabled/test/positive_expected_result.json create mode 100644 assets/queries/terraform/azure/azure_storage_table_logging_disabled/README.md create mode 100644 assets/queries/terraform/azure/azure_storage_table_logging_disabled/metadata.json create mode 100644 assets/queries/terraform/azure/azure_storage_table_logging_disabled/query.rego create mode 100644 assets/queries/terraform/azure/azure_storage_table_logging_disabled/test/negative1.tf create mode 100644 assets/queries/terraform/azure/azure_storage_table_logging_disabled/test/positive1.tf create mode 100644 assets/queries/terraform/azure/azure_storage_table_logging_disabled/test/positive2.tf create mode 100644 assets/queries/terraform/azure/azure_storage_table_logging_disabled/test/positive3.tf create mode 100644 assets/queries/terraform/azure/azure_storage_table_logging_disabled/test/positive_expected_result.json create mode 100644 assets/queries/terraform/gcp/gcp_access_approval_disabled/README.md create mode 100644 assets/queries/terraform/gcp/gcp_access_approval_disabled/metadata.json create mode 100644 assets/queries/terraform/gcp/gcp_access_approval_disabled/query.rego create mode 100644 assets/queries/terraform/gcp/gcp_access_approval_disabled/test/negative1.tf create mode 100644 assets/queries/terraform/gcp/gcp_access_approval_disabled/test/positive1.tf create mode 100644 assets/queries/terraform/gcp/gcp_access_approval_disabled/test/positive2.tf create mode 100644 assets/queries/terraform/gcp/gcp_access_approval_disabled/test/positive_expected_result.json create mode 100644 assets/queries/terraform/gcp/gcp_api_key_api_targets_missing/README.md create mode 100644 assets/queries/terraform/gcp/gcp_api_key_api_targets_missing/metadata.json create mode 100644 assets/queries/terraform/gcp/gcp_api_key_api_targets_missing/query.rego create mode 100644 assets/queries/terraform/gcp/gcp_api_key_api_targets_missing/test/negative1.tf create mode 100644 assets/queries/terraform/gcp/gcp_api_key_api_targets_missing/test/positive1.tf create mode 100644 assets/queries/terraform/gcp/gcp_api_key_api_targets_missing/test/positive2.tf create mode 100644 assets/queries/terraform/gcp/gcp_api_key_api_targets_missing/test/positive_expected_result.json create mode 100644 assets/queries/terraform/gcp/gcp_api_key_restrictions_manual/README.md create mode 100644 assets/queries/terraform/gcp/gcp_api_key_restrictions_manual/metadata.json create mode 100644 assets/queries/terraform/gcp/gcp_api_key_restrictions_manual/query.rego create mode 100644 assets/queries/terraform/gcp/gcp_api_key_restrictions_manual/test/negative1.tf create mode 100644 assets/queries/terraform/gcp/gcp_api_key_restrictions_manual/test/positive1.tf create mode 100644 assets/queries/terraform/gcp/gcp_api_key_restrictions_manual/test/positive2.tf create mode 100644 assets/queries/terraform/gcp/gcp_api_key_restrictions_manual/test/positive_expected_result.json create mode 100644 assets/queries/terraform/gcp/gcp_app_engine_https_enforcement_manual/README.md create mode 100644 assets/queries/terraform/gcp/gcp_app_engine_https_enforcement_manual/metadata.json create mode 100644 assets/queries/terraform/gcp/gcp_app_engine_https_enforcement_manual/query.rego create mode 100644 assets/queries/terraform/gcp/gcp_app_engine_https_enforcement_manual/test/negative1.tf create mode 100644 assets/queries/terraform/gcp/gcp_app_engine_https_enforcement_manual/test/positive1.tf create mode 100644 assets/queries/terraform/gcp/gcp_app_engine_https_enforcement_manual/test/positive2.tf create mode 100644 assets/queries/terraform/gcp/gcp_app_engine_https_enforcement_manual/test/positive_expected_result.json create mode 100644 assets/queries/terraform/gcp/gcp_compute_logging_service_disabled/README.md create mode 100644 assets/queries/terraform/gcp/gcp_compute_logging_service_disabled/metadata.json create mode 100644 assets/queries/terraform/gcp/gcp_compute_logging_service_disabled/query.rego create mode 100644 assets/queries/terraform/gcp/gcp_compute_logging_service_disabled/test/negative1.tf create mode 100644 assets/queries/terraform/gcp/gcp_compute_logging_service_disabled/test/positive1.tf create mode 100644 assets/queries/terraform/gcp/gcp_compute_logging_service_disabled/test/positive2.tf create mode 100644 assets/queries/terraform/gcp/gcp_compute_logging_service_disabled/test/positive3.tf create mode 100644 assets/queries/terraform/gcp/gcp_compute_logging_service_disabled/test/positive_expected_result.json create mode 100644 assets/queries/terraform/gcp/gcp_gke_default_service_account_used/README.md create mode 100644 assets/queries/terraform/gcp/gcp_gke_default_service_account_used/metadata.json create mode 100644 assets/queries/terraform/gcp/gcp_gke_default_service_account_used/query.rego create mode 100644 assets/queries/terraform/gcp/gcp_gke_default_service_account_used/test/negative1.tf create mode 100644 assets/queries/terraform/gcp/gcp_gke_default_service_account_used/test/positive1.tf create mode 100644 assets/queries/terraform/gcp/gcp_gke_default_service_account_used/test/positive2.tf create mode 100644 assets/queries/terraform/gcp/gcp_gke_default_service_account_used/test/positive_expected_result.json create mode 100644 assets/queries/terraform/gcp/gcp_gke_image_vulnerability_scanning_disabled/README.md create mode 100644 assets/queries/terraform/gcp/gcp_gke_image_vulnerability_scanning_disabled/metadata.json create mode 100644 assets/queries/terraform/gcp/gcp_gke_image_vulnerability_scanning_disabled/query.rego create mode 100644 assets/queries/terraform/gcp/gcp_gke_image_vulnerability_scanning_disabled/test/negative1.tf create mode 100644 assets/queries/terraform/gcp/gcp_gke_image_vulnerability_scanning_disabled/test/negative2.tf create mode 100644 assets/queries/terraform/gcp/gcp_gke_image_vulnerability_scanning_disabled/test/positive1.tf create mode 100644 assets/queries/terraform/gcp/gcp_gke_image_vulnerability_scanning_disabled/test/positive2.tf create mode 100644 assets/queries/terraform/gcp/gcp_gke_image_vulnerability_scanning_disabled/test/positive3.tf create mode 100644 assets/queries/terraform/gcp/gcp_gke_image_vulnerability_scanning_disabled/test/positive_expected_result.json create mode 100644 assets/queries/terraform/gcp/gcp_gke_manual_iam_check/README.md create mode 100644 assets/queries/terraform/gcp/gcp_gke_manual_iam_check/metadata.json create mode 100644 assets/queries/terraform/gcp/gcp_gke_manual_iam_check/query.rego create mode 100644 assets/queries/terraform/gcp/gcp_gke_manual_iam_check/test/negative1.tf create mode 100644 assets/queries/terraform/gcp/gcp_gke_manual_iam_check/test/positive1.tf create mode 100644 assets/queries/terraform/gcp/gcp_gke_manual_iam_check/test/positive2.tf create mode 100644 assets/queries/terraform/gcp/gcp_gke_manual_iam_check/test/positive_expected_result.json create mode 100644 assets/queries/terraform/gcp/gcp_gke_metadata_server_disabled/README.md create mode 100644 assets/queries/terraform/gcp/gcp_gke_metadata_server_disabled/metadata.json create mode 100644 assets/queries/terraform/gcp/gcp_gke_metadata_server_disabled/query.rego create mode 100644 assets/queries/terraform/gcp/gcp_gke_metadata_server_disabled/test/negative1.tf create mode 100644 assets/queries/terraform/gcp/gcp_gke_metadata_server_disabled/test/negative2.tf create mode 100644 assets/queries/terraform/gcp/gcp_gke_metadata_server_disabled/test/positive1.tf create mode 100644 assets/queries/terraform/gcp/gcp_gke_metadata_server_disabled/test/positive2.tf create mode 100644 assets/queries/terraform/gcp/gcp_gke_metadata_server_disabled/test/positive3.tf create mode 100644 assets/queries/terraform/gcp/gcp_gke_metadata_server_disabled/test/positive4.tf create mode 100644 assets/queries/terraform/gcp/gcp_gke_metadata_server_disabled/test/positive_expected_result.json create mode 100644 assets/queries/terraform/gcp/gcp_gke_sandbox_disabled/README.md create mode 100644 assets/queries/terraform/gcp/gcp_gke_sandbox_disabled/metadata.json create mode 100644 assets/queries/terraform/gcp/gcp_gke_sandbox_disabled/query.rego create mode 100644 assets/queries/terraform/gcp/gcp_gke_sandbox_disabled/test/negative1.tf create mode 100644 assets/queries/terraform/gcp/gcp_gke_sandbox_disabled/test/negative2.tf create mode 100644 assets/queries/terraform/gcp/gcp_gke_sandbox_disabled/test/positive1.tf create mode 100644 assets/queries/terraform/gcp/gcp_gke_sandbox_disabled/test/positive2.tf create mode 100644 assets/queries/terraform/gcp/gcp_gke_sandbox_disabled/test/positive3.tf create mode 100644 assets/queries/terraform/gcp/gcp_gke_sandbox_disabled/test/positive4.tf create mode 100644 assets/queries/terraform/gcp/gcp_gke_sandbox_disabled/test/positive_expected_result.json create mode 100644 assets/queries/terraform/gcp/gcp_gke_secrets_encryption_cmek_disabled/README.md create mode 100644 assets/queries/terraform/gcp/gcp_gke_secrets_encryption_cmek_disabled/metadata.json create mode 100644 assets/queries/terraform/gcp/gcp_gke_secrets_encryption_cmek_disabled/query.rego create mode 100644 assets/queries/terraform/gcp/gcp_gke_secrets_encryption_cmek_disabled/test/negative1.tf create mode 100644 assets/queries/terraform/gcp/gcp_gke_secrets_encryption_cmek_disabled/test/positive1.tf create mode 100644 assets/queries/terraform/gcp/gcp_gke_secrets_encryption_cmek_disabled/test/positive2.tf create mode 100644 assets/queries/terraform/gcp/gcp_gke_secrets_encryption_cmek_disabled/test/positive3.tf create mode 100644 assets/queries/terraform/gcp/gcp_gke_secrets_encryption_cmek_disabled/test/positive_expected_result.json create mode 100644 assets/queries/terraform/gcp/gcp_gke_security_posture_manual/README.md create mode 100644 assets/queries/terraform/gcp/gcp_gke_security_posture_manual/metadata.json create mode 100644 assets/queries/terraform/gcp/gcp_gke_security_posture_manual/query.rego create mode 100644 assets/queries/terraform/gcp/gcp_gke_security_posture_manual/test/negative1.tf create mode 100644 assets/queries/terraform/gcp/gcp_gke_security_posture_manual/test/negative2.tf create mode 100644 assets/queries/terraform/gcp/gcp_gke_security_posture_manual/test/positive1.tf create mode 100644 assets/queries/terraform/gcp/gcp_gke_security_posture_manual/test/positive2.tf create mode 100644 assets/queries/terraform/gcp/gcp_gke_security_posture_manual/test/positive_expected_result.json create mode 100644 assets/queries/terraform/gcp/gcp_gke_workload_identity_manual/README.md create mode 100644 assets/queries/terraform/gcp/gcp_gke_workload_identity_manual/metadata.json create mode 100644 assets/queries/terraform/gcp/gcp_gke_workload_identity_manual/query.rego create mode 100644 assets/queries/terraform/gcp/gcp_gke_workload_identity_manual/test/negative1.tf create mode 100644 assets/queries/terraform/gcp/gcp_gke_workload_identity_manual/test/positive1.tf create mode 100644 assets/queries/terraform/gcp/gcp_gke_workload_identity_manual/test/positive2.tf create mode 100644 assets/queries/terraform/gcp/gcp_gke_workload_identity_manual/test/positive_expected_result.json create mode 100644 assets/queries/terraform/gcp/gcp_http_load_balancer_logging_disabled/README.md create mode 100644 assets/queries/terraform/gcp/gcp_http_load_balancer_logging_disabled/metadata.json create mode 100644 assets/queries/terraform/gcp/gcp_http_load_balancer_logging_disabled/query.rego create mode 100644 assets/queries/terraform/gcp/gcp_http_load_balancer_logging_disabled/test/negative1.tf create mode 100644 assets/queries/terraform/gcp/gcp_http_load_balancer_logging_disabled/test/positive1.tf create mode 100644 assets/queries/terraform/gcp/gcp_http_load_balancer_logging_disabled/test/positive2.tf create mode 100644 assets/queries/terraform/gcp/gcp_http_load_balancer_logging_disabled/test/positive_expected_result.json create mode 100644 assets/queries/terraform/gcp/gcp_iap_backend_service_disabled/README.md create mode 100644 assets/queries/terraform/gcp/gcp_iap_backend_service_disabled/metadata.json create mode 100644 assets/queries/terraform/gcp/gcp_iap_backend_service_disabled/query.rego create mode 100644 assets/queries/terraform/gcp/gcp_iap_backend_service_disabled/test/negative1.tf create mode 100644 assets/queries/terraform/gcp/gcp_iap_backend_service_disabled/test/positive1.tf create mode 100644 assets/queries/terraform/gcp/gcp_iap_backend_service_disabled/test/positive2.tf create mode 100644 assets/queries/terraform/gcp/gcp_iap_backend_service_disabled/test/positive3.tf create mode 100644 assets/queries/terraform/gcp/gcp_iap_backend_service_disabled/test/positive_expected_result.json create mode 100644 assets/queries/terraform/gcp/gcp_sql_postgresql_log_error_verbosity_verbose/README.md create mode 100644 assets/queries/terraform/gcp/gcp_sql_postgresql_log_error_verbosity_verbose/metadata.json create mode 100644 assets/queries/terraform/gcp/gcp_sql_postgresql_log_error_verbosity_verbose/query.rego create mode 100644 assets/queries/terraform/gcp/gcp_sql_postgresql_log_error_verbosity_verbose/test/negative1.tf create mode 100644 assets/queries/terraform/gcp/gcp_sql_postgresql_log_error_verbosity_verbose/test/positive1.tf create mode 100644 assets/queries/terraform/gcp/gcp_sql_postgresql_log_error_verbosity_verbose/test/positive2.tf create mode 100644 assets/queries/terraform/gcp/gcp_sql_postgresql_log_error_verbosity_verbose/test/positive_expected_result.json create mode 100644 assets/queries/terraform/gcp/gcp_sql_postgresql_log_statement_improperly_set/README.md create mode 100644 assets/queries/terraform/gcp/gcp_sql_postgresql_log_statement_improperly_set/metadata.json create mode 100644 assets/queries/terraform/gcp/gcp_sql_postgresql_log_statement_improperly_set/query.rego create mode 100644 assets/queries/terraform/gcp/gcp_sql_postgresql_log_statement_improperly_set/test/negative1.tf create mode 100644 assets/queries/terraform/gcp/gcp_sql_postgresql_log_statement_improperly_set/test/positive1.tf create mode 100644 assets/queries/terraform/gcp/gcp_sql_postgresql_log_statement_improperly_set/test/positive2.tf create mode 100644 assets/queries/terraform/gcp/gcp_sql_postgresql_log_statement_improperly_set/test/positive_expected_result.json create mode 100644 assets/queries/terraform/ibm/ibm_activity_tracker_global_events_disabled/README.md create mode 100644 assets/queries/terraform/ibm/ibm_activity_tracker_global_events_disabled/metadata.json create mode 100644 assets/queries/terraform/ibm/ibm_activity_tracker_global_events_disabled/query.rego create mode 100644 assets/queries/terraform/ibm/ibm_activity_tracker_global_events_disabled/test/negative1.tf create mode 100644 assets/queries/terraform/ibm/ibm_activity_tracker_global_events_disabled/test/positive1.tf create mode 100644 assets/queries/terraform/ibm/ibm_activity_tracker_global_events_disabled/test/positive2.tf create mode 100644 assets/queries/terraform/ibm/ibm_activity_tracker_global_events_disabled/test/positive_expected_result.json create mode 100644 assets/queries/terraform/ibm/ibm_activity_tracker_platform_logs_disabled/README.md create mode 100644 assets/queries/terraform/ibm/ibm_activity_tracker_platform_logs_disabled/metadata.json create mode 100644 assets/queries/terraform/ibm/ibm_activity_tracker_platform_logs_disabled/query.rego create mode 100644 assets/queries/terraform/ibm/ibm_activity_tracker_platform_logs_disabled/test/negative1.tf create mode 100644 assets/queries/terraform/ibm/ibm_activity_tracker_platform_logs_disabled/test/positive1.tf create mode 100644 assets/queries/terraform/ibm/ibm_activity_tracker_platform_logs_disabled/test/positive2.tf create mode 100644 assets/queries/terraform/ibm/ibm_activity_tracker_platform_logs_disabled/test/positive3.tf create mode 100644 assets/queries/terraform/ibm/ibm_activity_tracker_platform_logs_disabled/test/positive_expected_result.json create mode 100644 assets/queries/terraform/ibm/ibm_block_storage_customer_encryption_unified/README.md create mode 100644 assets/queries/terraform/ibm/ibm_block_storage_customer_encryption_unified/metadata.json create mode 100644 assets/queries/terraform/ibm/ibm_block_storage_customer_encryption_unified/query.rego create mode 100644 assets/queries/terraform/ibm/ibm_block_storage_customer_encryption_unified/test/negative1.tf create mode 100644 assets/queries/terraform/ibm/ibm_block_storage_customer_encryption_unified/test/positive1.tf create mode 100644 assets/queries/terraform/ibm/ibm_block_storage_customer_encryption_unified/test/positive_expected_result.json create mode 100644 assets/queries/terraform/ibm/ibm_certificate_manager_auto_renew_disabled/README.md create mode 100644 assets/queries/terraform/ibm/ibm_certificate_manager_auto_renew_disabled/metadata.json create mode 100644 assets/queries/terraform/ibm/ibm_certificate_manager_auto_renew_disabled/query.rego create mode 100644 assets/queries/terraform/ibm/ibm_certificate_manager_auto_renew_disabled/test/negative1.tf create mode 100644 assets/queries/terraform/ibm/ibm_certificate_manager_auto_renew_disabled/test/positive1.tf create mode 100644 assets/queries/terraform/ibm/ibm_certificate_manager_auto_renew_disabled/test/positive2.tf create mode 100644 assets/queries/terraform/ibm/ibm_certificate_manager_auto_renew_disabled/test/positive_expected_result.json create mode 100644 assets/queries/terraform/ibm/ibm_cis_dns_not_proxied_manual/README.md create mode 100644 assets/queries/terraform/ibm/ibm_cis_dns_not_proxied_manual/metadata.json create mode 100644 assets/queries/terraform/ibm/ibm_cis_dns_not_proxied_manual/query.rego create mode 100644 assets/queries/terraform/ibm/ibm_cis_dns_not_proxied_manual/test/negative1.tf create mode 100644 assets/queries/terraform/ibm/ibm_cis_dns_not_proxied_manual/test/positive1.tf create mode 100644 assets/queries/terraform/ibm/ibm_cis_dns_not_proxied_manual/test/positive2.tf create mode 100644 assets/queries/terraform/ibm/ibm_cis_dns_not_proxied_manual/test/positive_expected_result.json create mode 100644 assets/queries/terraform/ibm/ibm_cis_waf_enabled_manual/README.md create mode 100644 assets/queries/terraform/ibm/ibm_cis_waf_enabled_manual/metadata.json create mode 100644 assets/queries/terraform/ibm/ibm_cis_waf_enabled_manual/query.rego create mode 100644 assets/queries/terraform/ibm/ibm_cis_waf_enabled_manual/test/negative1.tf create mode 100644 assets/queries/terraform/ibm/ibm_cis_waf_enabled_manual/test/positive1.tf create mode 100644 assets/queries/terraform/ibm/ibm_cis_waf_enabled_manual/test/positive2.tf create mode 100644 assets/queries/terraform/ibm/ibm_cis_waf_enabled_manual/test/positive_expected_result.json create mode 100644 assets/queries/terraform/ibm/ibm_cloudant_cmk_encryption_manual/README.md create mode 100644 assets/queries/terraform/ibm/ibm_cloudant_cmk_encryption_manual/metadata.json create mode 100644 assets/queries/terraform/ibm/ibm_cloudant_cmk_encryption_manual/query.rego create mode 100644 assets/queries/terraform/ibm/ibm_cloudant_cmk_encryption_manual/test/negative1.tf create mode 100644 assets/queries/terraform/ibm/ibm_cloudant_cmk_encryption_manual/test/positive1.tf create mode 100644 assets/queries/terraform/ibm/ibm_cloudant_cmk_encryption_manual/test/positive2.tf create mode 100644 assets/queries/terraform/ibm/ibm_cloudant_cmk_encryption_manual/test/positive_expected_result.json create mode 100644 assets/queries/terraform/ibm/ibm_container_cluster_entitlement_check/README.md create mode 100644 assets/queries/terraform/ibm/ibm_container_cluster_entitlement_check/metadata.json create mode 100644 assets/queries/terraform/ibm/ibm_container_cluster_entitlement_check/query.rego create mode 100644 assets/queries/terraform/ibm/ibm_container_cluster_entitlement_check/test/negative1.tf create mode 100644 assets/queries/terraform/ibm/ibm_container_cluster_entitlement_check/test/positive1.tf create mode 100644 assets/queries/terraform/ibm/ibm_container_cluster_entitlement_check/test/positive_expected_result.json create mode 100644 assets/queries/terraform/ibm/ibm_container_registry_va_alerts_missing/README.md create mode 100644 assets/queries/terraform/ibm/ibm_container_registry_va_alerts_missing/metadata.json create mode 100644 assets/queries/terraform/ibm/ibm_container_registry_va_alerts_missing/query.rego create mode 100644 assets/queries/terraform/ibm/ibm_container_registry_va_alerts_missing/test/negative1.tf create mode 100644 assets/queries/terraform/ibm/ibm_container_registry_va_alerts_missing/test/positive1.tf create mode 100644 assets/queries/terraform/ibm/ibm_container_registry_va_alerts_missing/test/positive_expected_result.json create mode 100644 assets/queries/terraform/ibm/ibm_cos_bucket_customer_encryption_unified/README.md create mode 100644 assets/queries/terraform/ibm/ibm_cos_bucket_customer_encryption_unified/metadata.json create mode 100644 assets/queries/terraform/ibm/ibm_cos_bucket_customer_encryption_unified/query.rego create mode 100644 assets/queries/terraform/ibm/ibm_cos_bucket_customer_encryption_unified/test/negative1.tf create mode 100644 assets/queries/terraform/ibm/ibm_cos_bucket_customer_encryption_unified/test/positive1.tf create mode 100644 assets/queries/terraform/ibm/ibm_cos_bucket_customer_encryption_unified/test/positive_expected_result.json create mode 100644 assets/queries/terraform/ibm/ibm_database_cmk_encryption_manual/README.md create mode 100644 assets/queries/terraform/ibm/ibm_database_cmk_encryption_manual/metadata.json create mode 100644 assets/queries/terraform/ibm/ibm_database_cmk_encryption_manual/query.rego create mode 100644 assets/queries/terraform/ibm/ibm_database_cmk_encryption_manual/test/negative1.tf create mode 100644 assets/queries/terraform/ibm/ibm_database_cmk_encryption_manual/test/positive1.tf create mode 100644 assets/queries/terraform/ibm/ibm_database_cmk_encryption_manual/test/positive_expected_result.json create mode 100644 assets/queries/terraform/ibm/ibm_iam_account_ip_restrictions_manual/README.md create mode 100644 assets/queries/terraform/ibm/ibm_iam_account_ip_restrictions_manual/metadata.json create mode 100644 assets/queries/terraform/ibm/ibm_iam_account_ip_restrictions_manual/query.rego create mode 100644 assets/queries/terraform/ibm/ibm_iam_account_ip_restrictions_manual/test/negative1.tf create mode 100644 assets/queries/terraform/ibm/ibm_iam_account_ip_restrictions_manual/test/positive1.tf create mode 100644 assets/queries/terraform/ibm/ibm_iam_account_ip_restrictions_manual/test/positive2.tf create mode 100644 assets/queries/terraform/ibm/ibm_iam_account_ip_restrictions_manual/test/positive_expected_result.json create mode 100644 assets/queries/terraform/ibm/ibm_iam_account_mfa_disabled/README.md create mode 100644 assets/queries/terraform/ibm/ibm_iam_account_mfa_disabled/metadata.json create mode 100644 assets/queries/terraform/ibm/ibm_iam_account_mfa_disabled/query.rego create mode 100644 assets/queries/terraform/ibm/ibm_iam_account_mfa_disabled/test/negative1.tf create mode 100644 assets/queries/terraform/ibm/ibm_iam_account_mfa_disabled/test/positive1.tf create mode 100644 assets/queries/terraform/ibm/ibm_iam_account_mfa_disabled/test/positive2.tf create mode 100644 assets/queries/terraform/ibm/ibm_iam_account_mfa_disabled/test/positive3.tf create mode 100644 assets/queries/terraform/ibm/ibm_iam_account_mfa_disabled/test/positive_expected_result.json create mode 100644 assets/queries/terraform/ibm/ibm_iam_api_key_unused_manual/README.md create mode 100644 assets/queries/terraform/ibm/ibm_iam_api_key_unused_manual/metadata.json create mode 100644 assets/queries/terraform/ibm/ibm_iam_api_key_unused_manual/query.rego create mode 100644 assets/queries/terraform/ibm/ibm_iam_api_key_unused_manual/test/negative1.tf create mode 100644 assets/queries/terraform/ibm/ibm_iam_api_key_unused_manual/test/positive1.tf create mode 100644 assets/queries/terraform/ibm/ibm_iam_api_key_unused_manual/test/positive2.tf create mode 100644 assets/queries/terraform/ibm/ibm_iam_api_key_unused_manual/test/positive_expected_result.json create mode 100644 assets/queries/terraform/ibm/ibm_iam_owner_api_key_manual/README.md create mode 100644 assets/queries/terraform/ibm/ibm_iam_owner_api_key_manual/metadata.json create mode 100644 assets/queries/terraform/ibm/ibm_iam_owner_api_key_manual/query.rego create mode 100644 assets/queries/terraform/ibm/ibm_iam_owner_api_key_manual/test/negative1.tf create mode 100644 assets/queries/terraform/ibm/ibm_iam_owner_api_key_manual/test/positive1.tf create mode 100644 assets/queries/terraform/ibm/ibm_iam_owner_api_key_manual/test/positive_expected_result.json create mode 100644 assets/queries/terraform/ibm/ibm_iam_policy_assigned_to_user_manual/README.md create mode 100644 assets/queries/terraform/ibm/ibm_iam_policy_assigned_to_user_manual/metadata.json create mode 100644 assets/queries/terraform/ibm/ibm_iam_policy_assigned_to_user_manual/query.rego create mode 100644 assets/queries/terraform/ibm/ibm_iam_policy_assigned_to_user_manual/test/negative1.tf create mode 100644 assets/queries/terraform/ibm/ibm_iam_policy_assigned_to_user_manual/test/positive1.tf create mode 100644 assets/queries/terraform/ibm/ibm_iam_policy_assigned_to_user_manual/test/positive_expected_result.json create mode 100644 assets/queries/terraform/ibm/ibm_iam_restrict_apikey_creation_manual/README.md create mode 100644 assets/queries/terraform/ibm/ibm_iam_restrict_apikey_creation_manual/metadata.json create mode 100644 assets/queries/terraform/ibm/ibm_iam_restrict_apikey_creation_manual/query.rego create mode 100644 assets/queries/terraform/ibm/ibm_iam_restrict_apikey_creation_manual/test/negative1.tf create mode 100644 assets/queries/terraform/ibm/ibm_iam_restrict_apikey_creation_manual/test/positive1.tf create mode 100644 assets/queries/terraform/ibm/ibm_iam_restrict_apikey_creation_manual/test/positive2.tf create mode 100644 assets/queries/terraform/ibm/ibm_iam_restrict_apikey_creation_manual/test/positive_expected_result.json create mode 100644 assets/queries/terraform/ibm/ibm_iam_session_expiration_too_long/README.md create mode 100644 assets/queries/terraform/ibm/ibm_iam_session_expiration_too_long/metadata.json create mode 100644 assets/queries/terraform/ibm/ibm_iam_session_expiration_too_long/query.rego create mode 100644 assets/queries/terraform/ibm/ibm_iam_session_expiration_too_long/test/negative1.tf create mode 100644 assets/queries/terraform/ibm/ibm_iam_session_expiration_too_long/test/positive1.tf create mode 100644 assets/queries/terraform/ibm/ibm_iam_session_expiration_too_long/test/positive2.tf create mode 100644 assets/queries/terraform/ibm/ibm_iam_session_expiration_too_long/test/positive_expected_result.json create mode 100644 assets/queries/terraform/ibm/ibm_iks_cluster_logging_disabled/README.md create mode 100644 assets/queries/terraform/ibm/ibm_iks_cluster_logging_disabled/metadata.json create mode 100644 assets/queries/terraform/ibm/ibm_iks_cluster_logging_disabled/query.rego create mode 100644 assets/queries/terraform/ibm/ibm_iks_cluster_logging_disabled/test/negative1.tf create mode 100644 assets/queries/terraform/ibm/ibm_iks_cluster_logging_disabled/test/positive1.tf create mode 100644 assets/queries/terraform/ibm/ibm_iks_cluster_logging_disabled/test/positive_expected_result.json create mode 100644 assets/queries/terraform/ibm/ibm_iks_cluster_monitoring_disabled/README.md create mode 100644 assets/queries/terraform/ibm/ibm_iks_cluster_monitoring_disabled/metadata.json create mode 100644 assets/queries/terraform/ibm/ibm_iks_cluster_monitoring_disabled/query.rego create mode 100644 assets/queries/terraform/ibm/ibm_iks_cluster_monitoring_disabled/test/negative1.tf create mode 100644 assets/queries/terraform/ibm/ibm_iks_cluster_monitoring_disabled/test/positive1.tf create mode 100644 assets/queries/terraform/ibm/ibm_iks_cluster_monitoring_disabled/test/positive_expected_result.json create mode 100644 assets/queries/terraform/ibm/ibm_instance_os_disk_encryption_manual/README.md create mode 100644 assets/queries/terraform/ibm/ibm_instance_os_disk_encryption_manual/metadata.json create mode 100644 assets/queries/terraform/ibm/ibm_instance_os_disk_encryption_manual/query.rego create mode 100644 assets/queries/terraform/ibm/ibm_instance_os_disk_encryption_manual/test/negative1.tf create mode 100644 assets/queries/terraform/ibm/ibm_instance_os_disk_encryption_manual/test/positive1.tf create mode 100644 assets/queries/terraform/ibm/ibm_instance_os_disk_encryption_manual/test/positive2.tf create mode 100644 assets/queries/terraform/ibm/ibm_instance_os_disk_encryption_manual/test/positive_expected_result.json create mode 100644 assets/queries/terraform/ibm/ibm_kms_key_rotation_disabled/README.md create mode 100644 assets/queries/terraform/ibm/ibm_kms_key_rotation_disabled/metadata.json create mode 100644 assets/queries/terraform/ibm/ibm_kms_key_rotation_disabled/query.rego create mode 100644 assets/queries/terraform/ibm/ibm_kms_key_rotation_disabled/test/negative1.tf create mode 100644 assets/queries/terraform/ibm/ibm_kms_key_rotation_disabled/test/positive1.tf create mode 100644 assets/queries/terraform/ibm/ibm_kms_key_rotation_disabled/test/positive2.tf create mode 100644 assets/queries/terraform/ibm/ibm_kms_key_rotation_disabled/test/positive3.tf create mode 100644 assets/queries/terraform/ibm/ibm_kms_key_rotation_disabled/test/positive_expected_result.json create mode 100644 assets/queries/terraform/ibm/ibm_logdna_archiving_disabled/README.md create mode 100644 assets/queries/terraform/ibm/ibm_logdna_archiving_disabled/metadata.json create mode 100644 assets/queries/terraform/ibm/ibm_logdna_archiving_disabled/query.rego create mode 100644 assets/queries/terraform/ibm/ibm_logdna_archiving_disabled/test/negative1.tf create mode 100644 assets/queries/terraform/ibm/ibm_logdna_archiving_disabled/test/positive1.tf create mode 100644 assets/queries/terraform/ibm/ibm_logdna_archiving_disabled/test/positive_expected_result.json create mode 100644 assets/queries/terraform/ibm/ibm_logdna_view_without_alert/README.md create mode 100644 assets/queries/terraform/ibm/ibm_logdna_view_without_alert/metadata.json create mode 100644 assets/queries/terraform/ibm/ibm_logdna_view_without_alert/query.rego create mode 100644 assets/queries/terraform/ibm/ibm_logdna_view_without_alert/test/negative1.tf create mode 100644 assets/queries/terraform/ibm/ibm_logdna_view_without_alert/test/positive1.tf create mode 100644 assets/queries/terraform/ibm/ibm_logdna_view_without_alert/test/positive_expected_result.json create mode 100644 assets/queries/terraform/oci/oci_cloud_guard_problem_event_rule_missing/README.md create mode 100644 assets/queries/terraform/oci/oci_cloud_guard_problem_event_rule_missing/metadata.json create mode 100644 assets/queries/terraform/oci/oci_cloud_guard_problem_event_rule_missing/query.rego create mode 100644 assets/queries/terraform/oci/oci_cloud_guard_problem_event_rule_missing/test/negative1.tf create mode 100644 assets/queries/terraform/oci/oci_cloud_guard_problem_event_rule_missing/test/positive1.tf create mode 100644 assets/queries/terraform/oci/oci_cloud_guard_problem_event_rule_missing/test/positive2.tf create mode 100644 assets/queries/terraform/oci/oci_cloud_guard_problem_event_rule_missing/test/positive3.tf create mode 100644 assets/queries/terraform/oci/oci_cloud_guard_problem_event_rule_missing/test/positive_expected_result.json create mode 100644 assets/queries/terraform/oci/oci_cloud_guard_root_compartment_disabled/README.md create mode 100644 assets/queries/terraform/oci/oci_cloud_guard_root_compartment_disabled/metadata.json create mode 100644 assets/queries/terraform/oci/oci_cloud_guard_root_compartment_disabled/query.rego create mode 100644 assets/queries/terraform/oci/oci_cloud_guard_root_compartment_disabled/test/negative1.tf create mode 100644 assets/queries/terraform/oci/oci_cloud_guard_root_compartment_disabled/test/positive1.tf create mode 100644 assets/queries/terraform/oci/oci_cloud_guard_root_compartment_disabled/test/positive2.tf create mode 100644 assets/queries/terraform/oci/oci_cloud_guard_root_compartment_disabled/test/positive3.tf create mode 100644 assets/queries/terraform/oci/oci_cloud_guard_root_compartment_disabled/test/positive_expected_result.json create mode 100644 assets/queries/terraform/oci/oci_compute_legacy_metadata_enabled/README.md create mode 100644 assets/queries/terraform/oci/oci_compute_legacy_metadata_enabled/metadata.json create mode 100644 assets/queries/terraform/oci/oci_compute_legacy_metadata_enabled/query.rego create mode 100644 assets/queries/terraform/oci/oci_compute_legacy_metadata_enabled/test/negative1.tf create mode 100644 assets/queries/terraform/oci/oci_compute_legacy_metadata_enabled/test/positive1.tf create mode 100644 assets/queries/terraform/oci/oci_compute_legacy_metadata_enabled/test/positive2.tf create mode 100644 assets/queries/terraform/oci/oci_compute_legacy_metadata_enabled/test/positive3.tf create mode 100644 assets/queries/terraform/oci/oci_compute_legacy_metadata_enabled/test/positive_expected_result.json create mode 100644 assets/queries/terraform/oci/oci_compute_secure_boot_disabled/README.md create mode 100644 assets/queries/terraform/oci/oci_compute_secure_boot_disabled/metadata.json create mode 100644 assets/queries/terraform/oci/oci_compute_secure_boot_disabled/query.rego create mode 100644 assets/queries/terraform/oci/oci_compute_secure_boot_disabled/test/negative1.tf create mode 100644 assets/queries/terraform/oci/oci_compute_secure_boot_disabled/test/positive1.tf create mode 100644 assets/queries/terraform/oci/oci_compute_secure_boot_disabled/test/positive2.tf create mode 100644 assets/queries/terraform/oci/oci_compute_secure_boot_disabled/test/positive3.tf create mode 100644 assets/queries/terraform/oci/oci_compute_secure_boot_disabled/test/positive_expected_result.json create mode 100644 assets/queries/terraform/oci/oci_default_tags_not_defined/README.md create mode 100644 assets/queries/terraform/oci/oci_default_tags_not_defined/metadata.json create mode 100644 assets/queries/terraform/oci/oci_default_tags_not_defined/query.rego create mode 100644 assets/queries/terraform/oci/oci_default_tags_not_defined/test/negative1.tf create mode 100644 assets/queries/terraform/oci/oci_default_tags_not_defined/test/positive1.tf create mode 100644 assets/queries/terraform/oci/oci_default_tags_not_defined/test/positive_expected_result.json create mode 100644 assets/queries/terraform/oci/oci_iam_group_change_event_rule_missing/README.md create mode 100644 assets/queries/terraform/oci/oci_iam_group_change_event_rule_missing/metadata.json create mode 100644 assets/queries/terraform/oci/oci_iam_group_change_event_rule_missing/query.rego create mode 100644 assets/queries/terraform/oci/oci_iam_group_change_event_rule_missing/test/negative1.tf create mode 100644 assets/queries/terraform/oci/oci_iam_group_change_event_rule_missing/test/positive1.tf create mode 100644 assets/queries/terraform/oci/oci_iam_group_change_event_rule_missing/test/positive2.tf create mode 100644 assets/queries/terraform/oci/oci_iam_group_change_event_rule_missing/test/positive3.tf create mode 100644 assets/queries/terraform/oci/oci_iam_group_change_event_rule_missing/test/positive_expected_result.json create mode 100644 assets/queries/terraform/oci/oci_iam_password_expiration_manual/README.md create mode 100644 assets/queries/terraform/oci/oci_iam_password_expiration_manual/metadata.json create mode 100644 assets/queries/terraform/oci/oci_iam_password_expiration_manual/query.rego create mode 100644 assets/queries/terraform/oci/oci_iam_password_expiration_manual/test/negative1.tf create mode 100644 assets/queries/terraform/oci/oci_iam_password_expiration_manual/test/positive1.tf create mode 100644 assets/queries/terraform/oci/oci_iam_password_expiration_manual/test/positive2.tf create mode 100644 assets/queries/terraform/oci/oci_iam_password_expiration_manual/test/positive3.tf create mode 100644 assets/queries/terraform/oci/oci_iam_password_expiration_manual/test/positive_expected_result.json create mode 100644 assets/queries/terraform/oci/oci_iam_password_policy_length/README.md create mode 100644 assets/queries/terraform/oci/oci_iam_password_policy_length/metadata.json create mode 100644 assets/queries/terraform/oci/oci_iam_password_policy_length/query.rego create mode 100644 assets/queries/terraform/oci/oci_iam_password_policy_length/test/negative1.tf create mode 100644 assets/queries/terraform/oci/oci_iam_password_policy_length/test/positive1.tf create mode 100644 assets/queries/terraform/oci/oci_iam_password_policy_length/test/positive2.tf create mode 100644 assets/queries/terraform/oci/oci_iam_password_policy_length/test/positive3.tf create mode 100644 assets/queries/terraform/oci/oci_iam_password_policy_length/test/positive_expected_result.json create mode 100644 assets/queries/terraform/oci/oci_iam_password_reuse_manual/README.md create mode 100644 assets/queries/terraform/oci/oci_iam_password_reuse_manual/metadata.json create mode 100644 assets/queries/terraform/oci/oci_iam_password_reuse_manual/query.rego create mode 100644 assets/queries/terraform/oci/oci_iam_password_reuse_manual/test/negative1.tf create mode 100644 assets/queries/terraform/oci/oci_iam_password_reuse_manual/test/positive1.tf create mode 100644 assets/queries/terraform/oci/oci_iam_password_reuse_manual/test/positive2.tf create mode 100644 assets/queries/terraform/oci/oci_iam_password_reuse_manual/test/positive3.tf create mode 100644 assets/queries/terraform/oci/oci_iam_password_reuse_manual/test/positive_expected_result.json create mode 100644 assets/queries/terraform/oci/oci_iam_policy_change_event_rule_missing/README.md create mode 100644 assets/queries/terraform/oci/oci_iam_policy_change_event_rule_missing/metadata.json create mode 100644 assets/queries/terraform/oci/oci_iam_policy_change_event_rule_missing/query.rego create mode 100644 assets/queries/terraform/oci/oci_iam_policy_change_event_rule_missing/test/negative1.tf create mode 100644 assets/queries/terraform/oci/oci_iam_policy_change_event_rule_missing/test/positive1.tf create mode 100644 assets/queries/terraform/oci/oci_iam_policy_change_event_rule_missing/test/positive2.tf create mode 100644 assets/queries/terraform/oci/oci_iam_policy_change_event_rule_missing/test/positive3.tf create mode 100644 assets/queries/terraform/oci/oci_iam_policy_change_event_rule_missing/test/positive_expected_result.json create mode 100644 assets/queries/terraform/oci/oci_iam_service_admins_manual/README.md create mode 100644 assets/queries/terraform/oci/oci_iam_service_admins_manual/metadata.json create mode 100644 assets/queries/terraform/oci/oci_iam_service_admins_manual/query.rego create mode 100644 assets/queries/terraform/oci/oci_iam_service_admins_manual/test/negative1.tf create mode 100644 assets/queries/terraform/oci/oci_iam_service_admins_manual/test/negative2.tf create mode 100644 assets/queries/terraform/oci/oci_iam_service_admins_manual/test/positive1.tf create mode 100644 assets/queries/terraform/oci/oci_iam_service_admins_manual/test/positive_expected_result.json create mode 100644 assets/queries/terraform/oci/oci_iam_user_change_event_rule_missing/README.md create mode 100644 assets/queries/terraform/oci/oci_iam_user_change_event_rule_missing/metadata.json create mode 100644 assets/queries/terraform/oci/oci_iam_user_change_event_rule_missing/query.rego create mode 100644 assets/queries/terraform/oci/oci_iam_user_change_event_rule_missing/test/negative1.tf create mode 100644 assets/queries/terraform/oci/oci_iam_user_change_event_rule_missing/test/positive1.tf create mode 100644 assets/queries/terraform/oci/oci_iam_user_change_event_rule_missing/test/positive2.tf create mode 100644 assets/queries/terraform/oci/oci_iam_user_change_event_rule_missing/test/positive3.tf create mode 100644 assets/queries/terraform/oci/oci_iam_user_change_event_rule_missing/test/positive_expected_result.json create mode 100644 assets/queries/terraform/oci/oci_idp_change_event_rule_missing/README.md create mode 100644 assets/queries/terraform/oci/oci_idp_change_event_rule_missing/metadata.json create mode 100644 assets/queries/terraform/oci/oci_idp_change_event_rule_missing/query.rego create mode 100644 assets/queries/terraform/oci/oci_idp_change_event_rule_missing/test/negative1.tf create mode 100644 assets/queries/terraform/oci/oci_idp_change_event_rule_missing/test/positive1.tf create mode 100644 assets/queries/terraform/oci/oci_idp_change_event_rule_missing/test/positive2.tf create mode 100644 assets/queries/terraform/oci/oci_idp_change_event_rule_missing/test/positive_expected_result.json create mode 100644 assets/queries/terraform/oci/oci_idp_group_mapping_change_event_rule_missing/README.md create mode 100644 assets/queries/terraform/oci/oci_idp_group_mapping_change_event_rule_missing/metadata.json create mode 100644 assets/queries/terraform/oci/oci_idp_group_mapping_change_event_rule_missing/query.rego create mode 100644 assets/queries/terraform/oci/oci_idp_group_mapping_change_event_rule_missing/test/negative1.tf create mode 100644 assets/queries/terraform/oci/oci_idp_group_mapping_change_event_rule_missing/test/positive1.tf create mode 100644 assets/queries/terraform/oci/oci_idp_group_mapping_change_event_rule_missing/test/positive2.tf create mode 100644 assets/queries/terraform/oci/oci_idp_group_mapping_change_event_rule_missing/test/positive_expected_result.json create mode 100644 assets/queries/terraform/oci/oci_instance_transit_encryption/README.md create mode 100644 assets/queries/terraform/oci/oci_instance_transit_encryption/metadata.json create mode 100644 assets/queries/terraform/oci/oci_instance_transit_encryption/query.rego create mode 100644 assets/queries/terraform/oci/oci_instance_transit_encryption/test/negative1.tf create mode 100644 assets/queries/terraform/oci/oci_instance_transit_encryption/test/positive1.tf create mode 100644 assets/queries/terraform/oci/oci_instance_transit_encryption/test/positive2.tf create mode 100644 assets/queries/terraform/oci/oci_instance_transit_encryption/test/positive3.tf create mode 100644 assets/queries/terraform/oci/oci_instance_transit_encryption/test/positive_expected_result.json create mode 100644 assets/queries/terraform/oci/oci_local_user_authentication_event_rule_missing/README.md create mode 100644 assets/queries/terraform/oci/oci_local_user_authentication_event_rule_missing/metadata.json create mode 100644 assets/queries/terraform/oci/oci_local_user_authentication_event_rule_missing/query.rego create mode 100644 assets/queries/terraform/oci/oci_local_user_authentication_event_rule_missing/test/negative1.tf create mode 100644 assets/queries/terraform/oci/oci_local_user_authentication_event_rule_missing/test/positive1.tf create mode 100644 assets/queries/terraform/oci/oci_local_user_authentication_event_rule_missing/test/positive2.tf create mode 100644 assets/queries/terraform/oci/oci_local_user_authentication_event_rule_missing/test/positive_expected_result.json create mode 100644 assets/queries/terraform/oci/oci_network_gateway_change_event_rule_missing/README.md create mode 100644 assets/queries/terraform/oci/oci_network_gateway_change_event_rule_missing/metadata.json create mode 100644 assets/queries/terraform/oci/oci_network_gateway_change_event_rule_missing/query.rego create mode 100644 assets/queries/terraform/oci/oci_network_gateway_change_event_rule_missing/test/negative1.tf create mode 100644 assets/queries/terraform/oci/oci_network_gateway_change_event_rule_missing/test/positive1.tf create mode 100644 assets/queries/terraform/oci/oci_network_gateway_change_event_rule_missing/test/positive2.tf create mode 100644 assets/queries/terraform/oci/oci_network_gateway_change_event_rule_missing/test/positive3.tf create mode 100644 assets/queries/terraform/oci/oci_network_gateway_change_event_rule_missing/test/positive_expected_result.json create mode 100644 assets/queries/terraform/oci/oci_notification_topic_without_subscription/README.md create mode 100644 assets/queries/terraform/oci/oci_notification_topic_without_subscription/metadata.json create mode 100644 assets/queries/terraform/oci/oci_notification_topic_without_subscription/query.rego create mode 100644 assets/queries/terraform/oci/oci_notification_topic_without_subscription/test/negative1.tf create mode 100644 assets/queries/terraform/oci/oci_notification_topic_without_subscription/test/positive1.tf create mode 100644 assets/queries/terraform/oci/oci_notification_topic_without_subscription/test/positive2.tf create mode 100644 assets/queries/terraform/oci/oci_notification_topic_without_subscription/test/positive_expected_result.json create mode 100644 assets/queries/terraform/oci/oci_nsg_change_event_rule_missing/README.md create mode 100644 assets/queries/terraform/oci/oci_nsg_change_event_rule_missing/metadata.json create mode 100644 assets/queries/terraform/oci/oci_nsg_change_event_rule_missing/query.rego create mode 100644 assets/queries/terraform/oci/oci_nsg_change_event_rule_missing/test/negative1.tf create mode 100644 assets/queries/terraform/oci/oci_nsg_change_event_rule_missing/test/positive1.tf create mode 100644 assets/queries/terraform/oci/oci_nsg_change_event_rule_missing/test/positive2.tf create mode 100644 assets/queries/terraform/oci/oci_nsg_change_event_rule_missing/test/positive3.tf create mode 100644 assets/queries/terraform/oci/oci_nsg_change_event_rule_missing/test/positive_expected_result.json create mode 100644 assets/queries/terraform/oci/oci_objectstorage_bucket_logging_enabled/README.md create mode 100644 assets/queries/terraform/oci/oci_objectstorage_bucket_logging_enabled/metadata.json create mode 100644 assets/queries/terraform/oci/oci_objectstorage_bucket_logging_enabled/query.rego create mode 100644 assets/queries/terraform/oci/oci_objectstorage_bucket_logging_enabled/test/negative1.tf create mode 100644 assets/queries/terraform/oci/oci_objectstorage_bucket_logging_enabled/test/positive1.tf create mode 100644 assets/queries/terraform/oci/oci_objectstorage_bucket_logging_enabled/test/positive2.tf create mode 100644 assets/queries/terraform/oci/oci_objectstorage_bucket_logging_enabled/test/positive_expected_result.json create mode 100644 assets/queries/terraform/oci/oci_objectstorage_bucket_versioning_disabled/README.md create mode 100644 assets/queries/terraform/oci/oci_objectstorage_bucket_versioning_disabled/metadata.json create mode 100644 assets/queries/terraform/oci/oci_objectstorage_bucket_versioning_disabled/query.rego create mode 100644 assets/queries/terraform/oci/oci_objectstorage_bucket_versioning_disabled/test/negative1.tf create mode 100644 assets/queries/terraform/oci/oci_objectstorage_bucket_versioning_disabled/test/positive1.tf create mode 100644 assets/queries/terraform/oci/oci_objectstorage_bucket_versioning_disabled/test/positive2.tf create mode 100644 assets/queries/terraform/oci/oci_objectstorage_bucket_versioning_disabled/test/positive_expected_result.json create mode 100644 assets/queries/terraform/oci/oci_resource_created_in_root_compartment/README.md create mode 100644 assets/queries/terraform/oci/oci_resource_created_in_root_compartment/metadata.json create mode 100644 assets/queries/terraform/oci/oci_resource_created_in_root_compartment/query.rego create mode 100644 assets/queries/terraform/oci/oci_resource_created_in_root_compartment/test/negative1.tf create mode 100644 assets/queries/terraform/oci/oci_resource_created_in_root_compartment/test/positive1.tf create mode 100644 assets/queries/terraform/oci/oci_resource_created_in_root_compartment/test/positive2.tf create mode 100644 assets/queries/terraform/oci/oci_resource_created_in_root_compartment/test/positive_expected_result.json create mode 100644 assets/queries/terraform/oci/oci_route_table_change_event_rule_missing/README.md create mode 100644 assets/queries/terraform/oci/oci_route_table_change_event_rule_missing/metadata.json create mode 100644 assets/queries/terraform/oci/oci_route_table_change_event_rule_missing/query.rego create mode 100644 assets/queries/terraform/oci/oci_route_table_change_event_rule_missing/test/negative1.tf create mode 100644 assets/queries/terraform/oci/oci_route_table_change_event_rule_missing/test/positive1.tf create mode 100644 assets/queries/terraform/oci/oci_route_table_change_event_rule_missing/test/positive2.tf create mode 100644 assets/queries/terraform/oci/oci_route_table_change_event_rule_missing/test/positive3.tf create mode 100644 assets/queries/terraform/oci/oci_route_table_change_event_rule_missing/test/positive_expected_result.json create mode 100644 assets/queries/terraform/oci/oci_security_list_change_event_rule_missing/README.md create mode 100644 assets/queries/terraform/oci/oci_security_list_change_event_rule_missing/metadata.json create mode 100644 assets/queries/terraform/oci/oci_security_list_change_event_rule_missing/query.rego create mode 100644 assets/queries/terraform/oci/oci_security_list_change_event_rule_missing/test/negative1.tf create mode 100644 assets/queries/terraform/oci/oci_security_list_change_event_rule_missing/test/positive1.tf create mode 100644 assets/queries/terraform/oci/oci_security_list_change_event_rule_missing/test/positive2.tf create mode 100644 assets/queries/terraform/oci/oci_security_list_change_event_rule_missing/test/positive3.tf create mode 100644 assets/queries/terraform/oci/oci_security_list_change_event_rule_missing/test/positive_expected_result.json create mode 100644 assets/queries/terraform/oci/oci_storage_admin_no_delete_manual/README.md create mode 100644 assets/queries/terraform/oci/oci_storage_admin_no_delete_manual/metadata.json create mode 100644 assets/queries/terraform/oci/oci_storage_admin_no_delete_manual/query.rego create mode 100644 assets/queries/terraform/oci/oci_storage_admin_no_delete_manual/test/negative1.tf create mode 100644 assets/queries/terraform/oci/oci_storage_admin_no_delete_manual/test/negative2.tf create mode 100644 assets/queries/terraform/oci/oci_storage_admin_no_delete_manual/test/positive1.tf create mode 100644 assets/queries/terraform/oci/oci_storage_admin_no_delete_manual/test/positive2.tf create mode 100644 assets/queries/terraform/oci/oci_storage_admin_no_delete_manual/test/positive_expected_result.json create mode 100644 assets/queries/terraform/oci/oci_storage_cmk_encryption_unified/README.md create mode 100644 assets/queries/terraform/oci/oci_storage_cmk_encryption_unified/metadata.json create mode 100644 assets/queries/terraform/oci/oci_storage_cmk_encryption_unified/query.rego create mode 100644 assets/queries/terraform/oci/oci_storage_cmk_encryption_unified/test/negative1.tf create mode 100644 assets/queries/terraform/oci/oci_storage_cmk_encryption_unified/test/negative2.tf create mode 100644 assets/queries/terraform/oci/oci_storage_cmk_encryption_unified/test/positive1.tf create mode 100644 assets/queries/terraform/oci/oci_storage_cmk_encryption_unified/test/positive2.tf create mode 100644 assets/queries/terraform/oci/oci_storage_cmk_encryption_unified/test/positive_expected_result.json create mode 100644 assets/queries/terraform/oci/oci_subnet_flow_logging_disabled/README.md create mode 100644 assets/queries/terraform/oci/oci_subnet_flow_logging_disabled/metadata.json create mode 100644 assets/queries/terraform/oci/oci_subnet_flow_logging_disabled/query.rego create mode 100644 assets/queries/terraform/oci/oci_subnet_flow_logging_disabled/test/negative1.tf create mode 100644 assets/queries/terraform/oci/oci_subnet_flow_logging_disabled/test/positive1.tf create mode 100644 assets/queries/terraform/oci/oci_subnet_flow_logging_disabled/test/positive2.tf create mode 100644 assets/queries/terraform/oci/oci_subnet_flow_logging_disabled/test/positive3.tf create mode 100644 assets/queries/terraform/oci/oci_subnet_flow_logging_disabled/test/positive4.tf create mode 100644 assets/queries/terraform/oci/oci_subnet_flow_logging_disabled/test/positive_expected_result.json create mode 100644 assets/queries/terraform/oci/oci_vcn_change_event_rule_missing/README.md create mode 100644 assets/queries/terraform/oci/oci_vcn_change_event_rule_missing/metadata.json create mode 100644 assets/queries/terraform/oci/oci_vcn_change_event_rule_missing/query.rego create mode 100644 assets/queries/terraform/oci/oci_vcn_change_event_rule_missing/test/negative1.tf create mode 100644 assets/queries/terraform/oci/oci_vcn_change_event_rule_missing/test/positive1.tf create mode 100644 assets/queries/terraform/oci/oci_vcn_change_event_rule_missing/test/positive2.tf create mode 100644 assets/queries/terraform/oci/oci_vcn_change_event_rule_missing/test/positive3.tf create mode 100644 assets/queries/terraform/oci/oci_vcn_change_event_rule_missing/test/positive_expected_result.json diff --git a/assets/queries/terraform/azure/azure_app_service_application_insights_not_configured/README.md b/assets/queries/terraform/azure/azure_app_service_application_insights_not_configured/README.md new file mode 100644 index 00000000000..151365fca5b --- /dev/null +++ b/assets/queries/terraform/azure/azure_app_service_application_insights_not_configured/README.md @@ -0,0 +1,46 @@ +# Regla KICS: App Service Application Insights Not Configured + +## Descripción General + +Esta regla verifica que los servicios de Azure App Service y Function Apps tengan configurada la integración con **Application Insights**. + +Application Insights es una característica de Azure Monitor que proporciona gestión del rendimiento de aplicaciones (APM) y seguimiento de errores en tiempo real. Para vincular un App Service con Application Insights en Terraform, se debe definir `APPLICATIONINSIGHTS_CONNECTION_STRING` (recomendado) o `APPINSIGHTS_INSTRUMENTATIONKEY` dentro del bloque `app_settings`. + +## Lógica de la Regla + +La política itera sobre los recursos `azurerm_linux_web_app`, `azurerm_windows_web_app`, `azurerm_linux_function_app` y `azurerm_windows_function_app`. +Verifica la configuración en dos niveles: +1. **Ausencia de app_settings:** Si el bloque no está definido. +2. **Configuración incompleta:** Si el bloque existe pero no contiene las claves de conexión. + +## Casos de Fallo Detectados + +### Caso 1: Falta Configuración en app_settings + +* **Descripción:** El recurso no tiene el bloque `app_settings` definido. +* **Ubicación de la Alerta:** Nivel de recurso principal. + +### Caso 2: Claves de App Insights ausentes + +* **Descripción:** El bloque `app_settings` existe pero no contiene `APPLICATIONINSIGHTS_CONNECTION_STRING` ni `APPINSIGHTS_INSTRUMENTATIONKEY`. +* **Ubicación de la Alerta:** Atributo `app_settings`. + +## Recurso Involucrado + +* `azurerm_linux_web_app` +* `azurerm_windows_web_app` +* `azurerm_linux_function_app` +* `azurerm_windows_function_app` + +## Solución + +Defina `APPLICATIONINSIGHTS_CONNECTION_STRING` dentro de los `app_settings`. + +```terraform +resource "azurerm_linux_web_app" "example" { + name = "example-app" + # ... + app_settings = { + "APPLICATIONINSIGHTS_CONNECTION_STRING" = azurerm_application_insights.example.connection_string + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_app_service_application_insights_not_configured/metadata.json b/assets/queries/terraform/azure/azure_app_service_application_insights_not_configured/metadata.json new file mode 100644 index 00000000000..72ff8d8cb65 --- /dev/null +++ b/assets/queries/terraform/azure/azure_app_service_application_insights_not_configured/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "721c26ff-776f-4d7a-b151-45447712343b", + "queryName": "App Service Application Insights Not Configured", + "severity": "MEDIUM", + "category": "Observability", + "descriptionText": "Ensures that Azure App Services and Function Apps are linked to Application Insights for performance monitoring and error tracking.", + "descriptionUrl": "https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/linux_web_app#app_settings", + "platform": "Terraform", + "descriptionID": "721c26ff", + "cloudProvider": "azure", + "cwe": "CWE-778", + "riskScore": 5.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_app_service_application_insights_not_configured/query.rego b/assets/queries/terraform/azure/azure_app_service_application_insights_not_configured/query.rego new file mode 100644 index 00000000000..52d2cefd933 --- /dev/null +++ b/assets/queries/terraform/azure/azure_app_service_application_insights_not_configured/query.rego @@ -0,0 +1,44 @@ +package Cx + +targets := { + "azurerm_linux_web_app", + "azurerm_windows_web_app", + "azurerm_linux_function_app", + "azurerm_windows_function_app" +} + +# REGLA 1: El bloque 'app_settings' no existe en absoluto. +CxPolicy[result] { + doc := input.document[i] + resource_type := targets[t] + app := doc.resource[resource_type][name] + + not app.app_settings + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.%s.%s", [resource_type, name]), + "issueType": "MissingAttribute", + "keyExpectedValue": sprintf("'%s.%s' should have 'app_settings' defined", [resource_type, name]), + "keyActualValue": sprintf("'%s.%s' is missing 'app_settings'", [resource_type, name]), + } +} + +# REGLA 2: El bloque 'app_settings' existe pero no tiene ninguna clave de App Insights. +CxPolicy[result] { + doc := input.document[i] + resource_type := targets[t] + app := doc.resource[resource_type][name] + + app.app_settings + not app.app_settings["APPLICATIONINSIGHTS_CONNECTION_STRING"] + not app.app_settings["APPINSIGHTS_INSTRUMENTATIONKEY"] + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.%s.%s.app_settings", [resource_type, name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "'app_settings' should contain 'APPLICATIONINSIGHTS_CONNECTION_STRING'", + "keyActualValue": "'app_settings' does not contain Application Insights configuration", + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_app_service_application_insights_not_configured/test/negative1.tf b/assets/queries/terraform/azure/azure_app_service_application_insights_not_configured/test/negative1.tf new file mode 100644 index 00000000000..a37f36b739b --- /dev/null +++ b/assets/queries/terraform/azure/azure_app_service_application_insights_not_configured/test/negative1.tf @@ -0,0 +1,11 @@ +# Caso con Connection String (Recomendado) +resource "azurerm_linux_web_app" "pass_connection_string" { + name = "pass-app-1" + resource_group_name = "rg" + location = "West Europe" + service_plan_id = "plan-id" + + app_settings = { + "APPLICATIONINSIGHTS_CONNECTION_STRING" = "InstrumentationKey=0000;IngestionEndpoint=https://..." + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_app_service_application_insights_not_configured/test/negative2.tf b/assets/queries/terraform/azure/azure_app_service_application_insights_not_configured/test/negative2.tf new file mode 100644 index 00000000000..143f597f488 --- /dev/null +++ b/assets/queries/terraform/azure/azure_app_service_application_insights_not_configured/test/negative2.tf @@ -0,0 +1,11 @@ +# Caso con Instrumentation Key (Legacy) +resource "azurerm_windows_web_app" "pass_instrumentation_key" { + name = "pass-app-2" + resource_group_name = "rg" + location = "West Europe" + service_plan_id = "plan-id" + + app_settings = { + "APPINSIGHTS_INSTRUMENTATIONKEY" = "0000-0000-0000-0000" + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_app_service_application_insights_not_configured/test/positive1.tf b/assets/queries/terraform/azure/azure_app_service_application_insights_not_configured/test/positive1.tf new file mode 100644 index 00000000000..f44da5b65d5 --- /dev/null +++ b/assets/queries/terraform/azure/azure_app_service_application_insights_not_configured/test/positive1.tf @@ -0,0 +1,6 @@ +resource "azurerm_linux_web_app" "fail_no_settings" { + name = "fail-app-no-settings" + resource_group_name = "rg" + location = "West Europe" + service_plan_id = "plan-id" +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_app_service_application_insights_not_configured/test/positive2.tf b/assets/queries/terraform/azure/azure_app_service_application_insights_not_configured/test/positive2.tf new file mode 100644 index 00000000000..7ed5d045e78 --- /dev/null +++ b/assets/queries/terraform/azure/azure_app_service_application_insights_not_configured/test/positive2.tf @@ -0,0 +1,10 @@ +resource "azurerm_windows_web_app" "fail_incomplete_settings" { + name = "fail-app-incomplete" + resource_group_name = "rg" + location = "West Europe" + service_plan_id = "plan-id" + + app_settings = { + "WEBSITE_NODE_DEFAULT_VERSION" = "14.15.0" + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_app_service_application_insights_not_configured/test/positive_expected_result.json b/assets/queries/terraform/azure/azure_app_service_application_insights_not_configured/test/positive_expected_result.json new file mode 100644 index 00000000000..42119a5fba2 --- /dev/null +++ b/assets/queries/terraform/azure/azure_app_service_application_insights_not_configured/test/positive_expected_result.json @@ -0,0 +1,14 @@ +[ + { + "queryName": "App Service Application Insights Not Configured", + "severity": "MEDIUM", + "line": 1, + "fileName": "positive1.tf" + }, + { + "queryName": "App Service Application Insights Not Configured", + "severity": "MEDIUM", + "line": 7, + "fileName": "positive2.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_app_service_http_logs_disabled/README.md b/assets/queries/terraform/azure/azure_app_service_http_logs_disabled/README.md new file mode 100644 index 00000000000..9ee37293f24 --- /dev/null +++ b/assets/queries/terraform/azure/azure_app_service_http_logs_disabled/README.md @@ -0,0 +1,55 @@ +# Regla KICS: App Service HTTP Logs Disabled + +## Descripción General + +Esta regla verifica que los servicios de Azure App Service (`azurerm_linux_web_app` y `azurerm_windows_web_app`) tengan habilitados los logs HTTP. + +Los logs de HTTP registran las solicitudes web que recibe la aplicación, incluyendo la URL solicitada, el agente de usuario, la dirección IP del cliente y el código de estado de la respuesta. Esta información es fundamental para la auditoría de seguridad, el cumplimiento normativo y la resolución de problemas de tráfico. + +## Lógica de la Regla + +La política itera sobre los recursos de App Service y verifica la configuración en dos pasos: +1. **Ausencia de Logs:** Verifica si el bloque `logs` existe. +2. **Ausencia de Logs HTTP:** Si el bloque `logs` existe, verifica que contenga el sub-bloque `http_logs`. + +Si el logging HTTP no está explícitamente habilitado, se genera una alerta. + +## Casos de Fallo Detectados + +### Caso 1: Configuración de Logs Ausente + +* **Descripción:** El recurso App Service se define sin especificar ninguna configuración de `logs`. +* **Ubicación de la Alerta:** Nivel de recurso principal. + +### Caso 2: Bloque http_logs Omitido + +* **Descripción:** Se define el bloque `logs` (por ejemplo, para logs de aplicación), pero se omiten los logs de tráfico HTTP. +* **Ubicación de la Alerta:** Bloque `logs`. + +## Recurso Involucrado + +* `azurerm_linux_web_app` +* `azurerm_windows_web_app` + +## Solución + +Añada el bloque `logs` y configure `http_logs` definiendo un sistema de archivos (`file_system`) o un almacenamiento de blobs (`azure_blob_storage`). + +```terraform +resource "azurerm_linux_web_app" "example_secure" { + name = "example-linux-web-app-secure" + resource_group_name = azurerm_resource_group.example.name + location = azurerm_resource_group.example.location + service_plan_id = azurerm_service_plan.example.id + + site_config {} + + logs { + http_logs { + file_system { + retention_in_days = 7 + retention_in_mb = 35 + } + } + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_app_service_http_logs_disabled/metadata.json b/assets/queries/terraform/azure/azure_app_service_http_logs_disabled/metadata.json new file mode 100644 index 00000000000..b30be71a396 --- /dev/null +++ b/assets/queries/terraform/azure/azure_app_service_http_logs_disabled/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "72eb3dd8-6892-47d1-9965-a5682de9f4e7", + "queryName": "App Service HTTP Logs Disabled", + "severity": "MEDIUM", + "category": "Observability", + "descriptionText": "Ensures that HTTP logs are enabled for Azure App Services. HTTP logs provide records of HTTP requests to the web app, which are crucial for security monitoring and debugging.", + "descriptionUrl": "https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/linux_web_app#http_logs", + "platform": "Terraform", + "descriptionID": "72eb3dd8", + "cloudProvider": "azure", + "cwe": "CWE-778", + "riskScore": 5.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_app_service_http_logs_disabled/query.rego b/assets/queries/terraform/azure/azure_app_service_http_logs_disabled/query.rego new file mode 100644 index 00000000000..8ed6d368b34 --- /dev/null +++ b/assets/queries/terraform/azure/azure_app_service_http_logs_disabled/query.rego @@ -0,0 +1,38 @@ +package Cx + +targets := {"azurerm_linux_web_app", "azurerm_windows_web_app"} + +# REGLA 1: El bloque 'logs' no existe en el App Service. +CxPolicy[result] { + doc := input.document[i] + resource_type := targets[t] + app := doc.resource[resource_type][name] + + not app.logs + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.%s.%s", [resource_type, name]), + "issueType": "MissingAttribute", + "keyExpectedValue": sprintf("'%s.%s' should have a 'logs' block defined", [resource_type, name]), + "keyActualValue": sprintf("'%s.%s' is missing the 'logs' block", [resource_type, name]), + } +} + +# REGLA 2: El bloque 'logs' existe pero no tiene 'http_logs' configurado. +CxPolicy[result] { + doc := input.document[i] + resource_type := targets[t] + app := doc.resource[resource_type][name] + + app.logs + not app.logs.http_logs + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.%s.%s.logs", [resource_type, name]), + "issueType": "MissingAttribute", + "keyExpectedValue": sprintf("'%s.%s.logs' should have 'http_logs' configured", [resource_type, name]), + "keyActualValue": sprintf("'%s.%s.logs' is missing 'http_logs'", [resource_type, name]), + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_app_service_http_logs_disabled/test/negative1.tf b/assets/queries/terraform/azure/azure_app_service_http_logs_disabled/test/negative1.tf new file mode 100644 index 00000000000..5b6ce2ddf69 --- /dev/null +++ b/assets/queries/terraform/azure/azure_app_service_http_logs_disabled/test/negative1.tf @@ -0,0 +1,15 @@ +resource "azurerm_linux_web_app" "pass_app" { + name = "app-pass" + resource_group_name = "rg" + location = "West Europe" + service_plan_id = "plan-id" + + logs { + http_logs { + file_system { + retention_in_days = 7 + retention_in_mb = 35 + } + } + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_app_service_http_logs_disabled/test/positive1.tf b/assets/queries/terraform/azure/azure_app_service_http_logs_disabled/test/positive1.tf new file mode 100644 index 00000000000..e797b245c24 --- /dev/null +++ b/assets/queries/terraform/azure/azure_app_service_http_logs_disabled/test/positive1.tf @@ -0,0 +1,8 @@ +resource "azurerm_linux_web_app" "fail_no_logs" { + name = "app-fail-1" + resource_group_name = "rg" + location = "West Europe" + service_plan_id = "plan-id" + + site_config {} +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_app_service_http_logs_disabled/test/positive2.tf b/assets/queries/terraform/azure/azure_app_service_http_logs_disabled/test/positive2.tf new file mode 100644 index 00000000000..6078908f9d1 --- /dev/null +++ b/assets/queries/terraform/azure/azure_app_service_http_logs_disabled/test/positive2.tf @@ -0,0 +1,13 @@ +resource "azurerm_windows_web_app" "fail_incomplete_logs" { + name = "app-fail-2" + resource_group_name = "rg" + location = "West Europe" + service_plan_id = "plan-id" + + logs { + application_logs { + file_system_level = "Information" + } + # Falta http_logs + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_app_service_http_logs_disabled/test/positive_expected_result.json b/assets/queries/terraform/azure/azure_app_service_http_logs_disabled/test/positive_expected_result.json new file mode 100644 index 00000000000..2b9ba210834 --- /dev/null +++ b/assets/queries/terraform/azure/azure_app_service_http_logs_disabled/test/positive_expected_result.json @@ -0,0 +1,14 @@ +[ + { + "queryName": "App Service HTTP Logs Disabled", + "severity": "MEDIUM", + "line": 1, + "fileName": "positive1.tf" + }, + { + "queryName": "App Service HTTP Logs Disabled", + "severity": "MEDIUM", + "line": 7, + "fileName": "positive2.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_backup_vault_cmk_encryption_disabled/README.md b/assets/queries/terraform/azure/azure_backup_vault_cmk_encryption_disabled/README.md new file mode 100644 index 00000000000..deb4f3433de --- /dev/null +++ b/assets/queries/terraform/azure/azure_backup_vault_cmk_encryption_disabled/README.md @@ -0,0 +1,37 @@ +# Regla KICS: Backup Vault CMK Encryption Disabled + +## Descripción General + +Esta regla verifica que los almacenes de copias de seguridad de Azure (**Backup Vaults** del servicio Data Protection) estén cifrados utilizando **Customer-Managed Keys (CMK)**. + +El uso de claves gestionadas por el cliente proporciona un control total sobre el ciclo de vida de las claves (creación, rotación y revocación) y es un requisito común en entornos con altas exigencias de seguridad y cumplimiento. En Terraform, esto se configura mediante un recurso separado (`azurerm_data_protection_backup_vault_customer_managed_key`) que vincula el Vault con la clave almacenada en un Key Vault. + +## Lógica de la Regla + +La política realiza un análisis de relaciones entre recursos: +1. Identifica todos los recursos `azurerm_data_protection_backup_vault`. +2. Busca si existe un recurso `azurerm_data_protection_backup_vault_customer_managed_key` cuya propiedad `data_protection_backup_vault_id` apunte al Vault analizado. +3. Verifica que dicho recurso de asociación tenga definido el atributo `key_vault_key_id`. +4. Si no existe esta vinculación, se genera una alerta indicando que el Vault usa claves gestionadas por la plataforma (configuración por defecto). + +## Casos de Fallo Detectados + +### Caso 1: Backup Vault sin CMK + +* **Descripción:** Se define el Backup Vault pero no se encuentra el recurso de asociación de la clave de cifrado gestionada por el cliente. +* **Ubicación de la Alerta:** Sobre el recurso `azurerm_data_protection_backup_vault`. + +## Recurso Involucrado + +* `azurerm_data_protection_backup_vault` +* `azurerm_data_protection_backup_vault_customer_managed_key` + +## Solución + +Define el recurso de asociación `azurerm_data_protection_backup_vault_customer_managed_key` y vincúlalo al Vault y a la Key correspondiente. + +```terraform +resource "azurerm_data_protection_backup_vault_customer_managed_key" "example" { + data_protection_backup_vault_id = azurerm_data_protection_backup_vault.example.id + key_vault_key_id = azurerm_key_vault_key.example.id +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_backup_vault_cmk_encryption_disabled/metadata.json b/assets/queries/terraform/azure/azure_backup_vault_cmk_encryption_disabled/metadata.json new file mode 100644 index 00000000000..4b63512897a --- /dev/null +++ b/assets/queries/terraform/azure/azure_backup_vault_cmk_encryption_disabled/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "bf4474a7-7495-4292-af22-a97c1cc95d2b", + "queryName": "Backup Vault CMK Encryption Disabled", + "severity": "MEDIUM", + "category": "Encryption", + "descriptionText": "Ensures that Azure Backup Vaults (Data Protection) are encrypted using Customer-Managed Keys (CMK) stored in Azure Key Vault, instead of platform-managed keys.", + "descriptionUrl": "https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/data_protection_backup_vault_customer_managed_key", + "platform": "Terraform", + "descriptionID": "bf4474a7", + "cloudProvider": "azure", + "cwe": "CWE-326", + "riskScore": 5.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_backup_vault_cmk_encryption_disabled/query.rego b/assets/queries/terraform/azure/azure_backup_vault_cmk_encryption_disabled/query.rego new file mode 100644 index 00000000000..114fb74bd78 --- /dev/null +++ b/assets/queries/terraform/azure/azure_backup_vault_cmk_encryption_disabled/query.rego @@ -0,0 +1,25 @@ +package Cx + +has_cmk_configured(doc, vault_id) { + cmk := doc.resource.azurerm_data_protection_backup_vault_customer_managed_key[_] + cmk.data_protection_backup_vault_id == vault_id + cmk.key_vault_key_id +} + +# REGLA 1: El Backup Vault no tiene cifrado CMK configurado a través del recurso de asociación. +CxPolicy[result] { + doc := input.document[i] + vault := doc.resource.azurerm_data_protection_backup_vault[name] + + vault_id := sprintf("${azurerm_data_protection_backup_vault.%s.id}", [name]) + + not has_cmk_configured(doc, vault_id) + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.azurerm_data_protection_backup_vault.%s", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": sprintf("'azurerm_data_protection_backup_vault.%s' should be associated with an 'azurerm_data_protection_backup_vault_customer_managed_key' resource", [name]), + "keyActualValue": sprintf("'azurerm_data_protection_backup_vault.%s' is using Platform-Managed Keys (default)", [name]), + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_backup_vault_cmk_encryption_disabled/test/negative1.tf b/assets/queries/terraform/azure/azure_backup_vault_cmk_encryption_disabled/test/negative1.tf new file mode 100644 index 00000000000..8c6778ce2a1 --- /dev/null +++ b/assets/queries/terraform/azure/azure_backup_vault_cmk_encryption_disabled/test/negative1.tf @@ -0,0 +1,16 @@ +resource "azurerm_data_protection_backup_vault" "pass_vault" { + name = "backup-vault-secure" + resource_group_name = "rg-backup" + location = "East US" + datastore_type = "VaultStore" + redundancy = "LocallyRedundant" + + identity { + type = "SystemAssigned" + } +} + +resource "azurerm_data_protection_backup_vault_customer_managed_key" "pass_cmk" { + data_protection_backup_vault_id = azurerm_data_protection_backup_vault.pass_vault.id + key_vault_key_id = "https://example-kv.vault.azure.net/keys/example-key/version" +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_backup_vault_cmk_encryption_disabled/test/positive1.tf b/assets/queries/terraform/azure/azure_backup_vault_cmk_encryption_disabled/test/positive1.tf new file mode 100644 index 00000000000..d9b59238be8 --- /dev/null +++ b/assets/queries/terraform/azure/azure_backup_vault_cmk_encryption_disabled/test/positive1.tf @@ -0,0 +1,11 @@ +resource "azurerm_data_protection_backup_vault" "fail_vault" { + name = "backup-vault-insecure" + resource_group_name = "rg-backup" + location = "East US" + datastore_type = "VaultStore" + redundancy = "LocallyRedundant" + + identity { + type = "SystemAssigned" + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_backup_vault_cmk_encryption_disabled/test/positive_expected_result.json b/assets/queries/terraform/azure/azure_backup_vault_cmk_encryption_disabled/test/positive_expected_result.json new file mode 100644 index 00000000000..6ef8a5a5a91 --- /dev/null +++ b/assets/queries/terraform/azure/azure_backup_vault_cmk_encryption_disabled/test/positive_expected_result.json @@ -0,0 +1,8 @@ +[ + { + "queryName": "Backup Vault CMK Encryption Disabled", + "severity": "MEDIUM", + "line": 1, + "fileName": "positive1.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_backup_vault_cross_region_restore_disabled/README.md b/assets/queries/terraform/azure/azure_backup_vault_cross_region_restore_disabled/README.md new file mode 100644 index 00000000000..14f5f15e08b --- /dev/null +++ b/assets/queries/terraform/azure/azure_backup_vault_cross_region_restore_disabled/README.md @@ -0,0 +1,47 @@ +# Regla KICS: Backup Vault Cross Region Restore Disabled + +## Descripción General + +Esta regla verifica que la funcionalidad de **Restauración entre Regiones** (`cross_region_restore_enabled`) esté habilitada en los recursos `azurerm_data_protection_backup_vault`. + +Cross Region Restore (CRR) permite restaurar los datos de copia de seguridad en una región secundaria de Azure emparejada (Azure Paired Region). Esto es fundamental para garantizar la continuidad del negocio y la recuperación de datos en caso de que la región principal sufra una interrupción total o un desastre geográfico. + +**Nota:** Para utilizar CRR de manera efectiva, el almacén requiere que la redundancia esté configurada como `GeoRedundant`. + +## Lógica de la Regla + +La política evalúa el recurso `azurerm_data_protection_backup_vault` bajo dos escenarios: +1. **Atributo Ausente:** Si no se define explícitamente `cross_region_restore_enabled`, se genera una alerta sobre el recurso (ya que el valor por defecto en la plataforma suele ser false). +2. **Deshabilitado Explícitamente:** Si el atributo se establece como `false`, la alerta apunta directamente a la línea de la configuración incorrecta. + +## Casos de Fallo Detectados + +--- + +### Caso 1: Configuración Ausente +* **Descripción:** Se define el Backup Vault sin especificar la política de restauración entre regiones, dejando los datos vulnerables a fallos regionales. +* **Ubicación de la Alerta:** Nivel de recurso `azurerm_data_protection_backup_vault`. + +### Caso 2: CRR Deshabilitado +* **Descripción:** El atributo `cross_region_restore_enabled` está configurado explícitamente como `false`. +* **Ubicación de la Alerta:** Línea `cross_region_restore_enabled`. + +## Recurso Involucrado + +* `azurerm_data_protection_backup_vault` + +## Solución + +Establezca el atributo `cross_region_restore_enabled` en `true`. Es altamente recomendable verificar que el tipo de redundancia (`redundancy`) esté configurado como `GeoRedundant`. + +```terraform +resource "azurerm_data_protection_backup_vault" "example_secure" { + name = "vault-secure" + resource_group_name = azurerm_resource_group.example.name + location = azurerm_resource_group.example.location + datastore_type = "VaultStore" + redundancy = "GeoRedundant" + + # Solución técnica + cross_region_restore_enabled = true +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_backup_vault_cross_region_restore_disabled/metadata.json b/assets/queries/terraform/azure/azure_backup_vault_cross_region_restore_disabled/metadata.json new file mode 100644 index 00000000000..5bb3f8ea098 --- /dev/null +++ b/assets/queries/terraform/azure/azure_backup_vault_cross_region_restore_disabled/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "0117fb32-4265-444d-8030-a0a034958489", + "queryName": "Backup Vault Cross Region Restore Disabled", + "severity": "MEDIUM", + "category": "Backup", + "descriptionText": "Ensures that 'Cross Region Restore' is enabled for Azure Backup Vaults. This allows backup data to be restored in a secondary region, which is critical for disaster recovery scenarios.", + "descriptionUrl": "https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/data_protection_backup_vault#cross_region_restore_enabled", + "platform": "Terraform", + "descriptionID": "0117fb32", + "cloudProvider": "azure", + "cwe": "CWE-668", + "riskScore": 5.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_backup_vault_cross_region_restore_disabled/query.rego b/assets/queries/terraform/azure/azure_backup_vault_cross_region_restore_disabled/query.rego new file mode 100644 index 00000000000..899dc447728 --- /dev/null +++ b/assets/queries/terraform/azure/azure_backup_vault_cross_region_restore_disabled/query.rego @@ -0,0 +1,33 @@ +package Cx + +# REGLA 1: Configuración Ausente. +CxPolicy[result] { + doc := input.document[i] + vault := doc.resource.azurerm_data_protection_backup_vault[name] + + object.get(vault, "cross_region_restore_enabled", "undefined") == "undefined" + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.azurerm_data_protection_backup_vault.%s", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": sprintf("'azurerm_data_protection_backup_vault.%s' should have 'cross_region_restore_enabled' set to true", [name]), + "keyActualValue": sprintf("'azurerm_data_protection_backup_vault.%s' is missing 'cross_region_restore_enabled'", [name]), + } +} + +# REGLA 2: Configuración Incorrecta (Deshabilitado explícitamente). +CxPolicy[result] { + doc := input.document[i] + vault := doc.resource.azurerm_data_protection_backup_vault[name] + + vault.cross_region_restore_enabled == false + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.azurerm_data_protection_backup_vault.%s.cross_region_restore_enabled", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "'cross_region_restore_enabled' should be set to true", + "keyActualValue": "'cross_region_restore_enabled' is set to false", + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_backup_vault_cross_region_restore_disabled/test/negative1.tf b/assets/queries/terraform/azure/azure_backup_vault_cross_region_restore_disabled/test/negative1.tf new file mode 100644 index 00000000000..1897017fb9d --- /dev/null +++ b/assets/queries/terraform/azure/azure_backup_vault_cross_region_restore_disabled/test/negative1.tf @@ -0,0 +1,9 @@ +resource "azurerm_data_protection_backup_vault" "pass" { + name = "vault-ok" + resource_group_name = "rg-test" + location = "West Europe" + datastore_type = "VaultStore" + redundancy = "GeoRedundant" + + cross_region_restore_enabled = true +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_backup_vault_cross_region_restore_disabled/test/positive1.tf b/assets/queries/terraform/azure/azure_backup_vault_cross_region_restore_disabled/test/positive1.tf new file mode 100644 index 00000000000..b59e9f3d5a6 --- /dev/null +++ b/assets/queries/terraform/azure/azure_backup_vault_cross_region_restore_disabled/test/positive1.tf @@ -0,0 +1,8 @@ +resource "azurerm_data_protection_backup_vault" "fail_missing" { + name = "vault-missing-crr" + resource_group_name = "rg-test" + location = "West Europe" + datastore_type = "VaultStore" + redundancy = "GeoRedundant" + # FALLO: Falta cross_region_restore_enabled +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_backup_vault_cross_region_restore_disabled/test/positive2.tf b/assets/queries/terraform/azure/azure_backup_vault_cross_region_restore_disabled/test/positive2.tf new file mode 100644 index 00000000000..8a9cf10f0da --- /dev/null +++ b/assets/queries/terraform/azure/azure_backup_vault_cross_region_restore_disabled/test/positive2.tf @@ -0,0 +1,9 @@ +resource "azurerm_data_protection_backup_vault" "fail_explicit" { + name = "vault-disabled-crr" + resource_group_name = "rg-test" + location = "West Europe" + datastore_type = "VaultStore" + redundancy = "GeoRedundant" + + cross_region_restore_enabled = false # FALLO +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_backup_vault_cross_region_restore_disabled/test/positive_expected_result.json b/assets/queries/terraform/azure/azure_backup_vault_cross_region_restore_disabled/test/positive_expected_result.json new file mode 100644 index 00000000000..bcfd1994ee5 --- /dev/null +++ b/assets/queries/terraform/azure/azure_backup_vault_cross_region_restore_disabled/test/positive_expected_result.json @@ -0,0 +1,14 @@ +[ + { + "queryName": "Backup Vault Cross Region Restore Disabled", + "severity": "MEDIUM", + "line": 1, + "fileName": "positive1.tf" + }, + { + "queryName": "Backup Vault Cross Region Restore Disabled", + "severity": "MEDIUM", + "line": 8, + "fileName": "positive2.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_backup_vault_infrastructure_encryption_disabled/README.md b/assets/queries/terraform/azure/azure_backup_vault_infrastructure_encryption_disabled/README.md new file mode 100644 index 00000000000..9ca0aa51be4 --- /dev/null +++ b/assets/queries/terraform/azure/azure_backup_vault_infrastructure_encryption_disabled/README.md @@ -0,0 +1,42 @@ +# Regla KICS: Backup Vault Infrastructure Encryption Disabled + +## Descripción General + +Esta regla verifica la preparación para el cifrado avanzado en los almacenes de respaldo de Azure (**Data Protection Backup Vaults**). + +Para que un Backup Vault pueda soportar capas de cifrado adicionales o gestionadas por el cliente (CMK), es obligatorio que el recurso tenga una identidad asignada (`identity`). Sin una identidad, el almacén solo puede utilizar el cifrado predeterminado de la plataforma. + +## Lógica de la Regla + +La política audita el recurso `azurerm_data_protection_backup_vault`: +1. Verifica la existencia del bloque `identity`. +2. Si el bloque está ausente, se considera que el recurso no está preparado para configuraciones de cifrado de infraestructura o gestionado por el cliente. + +## Casos de Fallo Detectados + +### Caso 1: Identidad no configurada + +* **Descripción:** El Backup Vault no tiene una identidad (SystemAssigned o UserAssigned), lo que impide la vinculación con claves de cifrado externas. +* **Ubicación de la Alerta:** Nivel de recurso `azurerm_data_protection_backup_vault`. + +## Recurso Involucrado + +* `azurerm_data_protection_backup_vault` + +## Solución + +Añada un bloque `identity` al recurso. + +```terraform +resource "azurerm_data_protection_backup_vault" "example" { + name = "vault-secure" + resource_group_name = "rg-example" + location = "West Europe" + datastore_type = "VaultStore" + redundancy = "LocallyRedundant" + + # Solución técnica para habilitar capacidades de cifrado + identity { + type = "SystemAssigned" + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_backup_vault_infrastructure_encryption_disabled/metadata.json b/assets/queries/terraform/azure/azure_backup_vault_infrastructure_encryption_disabled/metadata.json new file mode 100644 index 00000000000..e766039db6a --- /dev/null +++ b/assets/queries/terraform/azure/azure_backup_vault_infrastructure_encryption_disabled/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "f7bf03d5-ae0e-4b36-aab3-f8d2346a8843", + "queryName": "Backup Vault Infrastructure Encryption Disabled", + "severity": "MEDIUM", + "category": "Encryption", + "descriptionText": "Ensures that 'Infrastructure Encryption' is enabled for Azure Backup Vaults. This provides a second layer of encryption (double encryption) for data at rest.", + "descriptionUrl": "https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/data_protection_backup_vault#infrastructure_encryption_enabled", + "platform": "Terraform", + "descriptionID": "f7bf03d5", + "cloudProvider": "azure", + "cwe": "CWE-312", + "riskScore": 5.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_backup_vault_infrastructure_encryption_disabled/query.rego b/assets/queries/terraform/azure/azure_backup_vault_infrastructure_encryption_disabled/query.rego new file mode 100644 index 00000000000..42de971f8c9 --- /dev/null +++ b/assets/queries/terraform/azure/azure_backup_vault_infrastructure_encryption_disabled/query.rego @@ -0,0 +1,17 @@ +package Cx + +# REGLA 1: Falta el bloque 'identity', necesario para gestionar cifrado avanzado. +CxPolicy[result] { + doc := input.document[i] + vault := doc.resource.azurerm_data_protection_backup_vault[name] + + not vault.identity + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.azurerm_data_protection_backup_vault.%s", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": sprintf("'azurerm_data_protection_backup_vault.%s' should have an 'identity' block to support advanced encryption", [name]), + "keyActualValue": sprintf("'azurerm_data_protection_backup_vault.%s' is missing the 'identity' block", [name]), + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_backup_vault_infrastructure_encryption_disabled/test/negative1.tf b/assets/queries/terraform/azure/azure_backup_vault_infrastructure_encryption_disabled/test/negative1.tf new file mode 100644 index 00000000000..339fa323b7f --- /dev/null +++ b/assets/queries/terraform/azure/azure_backup_vault_infrastructure_encryption_disabled/test/negative1.tf @@ -0,0 +1,11 @@ +resource "azurerm_data_protection_backup_vault" "pass" { + name = "vault-with-identity" + resource_group_name = "rg" + location = "West Europe" + datastore_type = "VaultStore" + redundancy = "LocallyRedundant" + + identity { + type = "SystemAssigned" + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_backup_vault_infrastructure_encryption_disabled/test/positive1.tf b/assets/queries/terraform/azure/azure_backup_vault_infrastructure_encryption_disabled/test/positive1.tf new file mode 100644 index 00000000000..f058b9766a1 --- /dev/null +++ b/assets/queries/terraform/azure/azure_backup_vault_infrastructure_encryption_disabled/test/positive1.tf @@ -0,0 +1,8 @@ +resource "azurerm_data_protection_backup_vault" "fail" { + name = "vault-no-identity" + resource_group_name = "rg" + location = "West Europe" + datastore_type = "VaultStore" + redundancy = "LocallyRedundant" + # FALLO: No tiene bloque identity +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_backup_vault_infrastructure_encryption_disabled/test/positive_expected_result.json b/assets/queries/terraform/azure/azure_backup_vault_infrastructure_encryption_disabled/test/positive_expected_result.json new file mode 100644 index 00000000000..4979de3f2ab --- /dev/null +++ b/assets/queries/terraform/azure/azure_backup_vault_infrastructure_encryption_disabled/test/positive_expected_result.json @@ -0,0 +1,8 @@ +[ + { + "queryName": "Backup Vault Infrastructure Encryption Disabled", + "severity": "MEDIUM", + "line": 1, + "fileName": "positive1.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_bastion_host_missing/README.md b/assets/queries/terraform/azure/azure_bastion_host_missing/README.md new file mode 100644 index 00000000000..63ef51ce4f3 --- /dev/null +++ b/assets/queries/terraform/azure/azure_bastion_host_missing/README.md @@ -0,0 +1,50 @@ +# Regla KICS: Azure Bastion Host Missing + +## Descripción General + +Esta regla verifica que, si se están desplegando redes virtuales (`azurerm_virtual_network`), exista al menos un recurso **Azure Bastion Host** (`azurerm_bastion_host`) definido en la configuración. + +Azure Bastion es un servicio PaaS que se aprovisiona dentro de una red virtual. Proporciona conectividad RDP y SSH segura directamente desde el portal de Azure a través de SSL. Esto elimina la necesidad de exponer puertos administrativos (22, 3389) a Internet o gestionar complejas VPNs y Jumpboxes para tareas de mantenimiento, reduciendo drásticamente la superficie de ataque. + +## Lógica de la Regla + +La política realiza un análisis de presencia de recursos a nivel de documento: +1. Verifica si existe algún recurso `azurerm_virtual_network`. +2. Si existen redes, busca si hay algún recurso `azurerm_bastion_host` definido en el mismo archivo/contexto. +3. Si existen redes pero no se encuentra ningún Bastion Host, se genera una alerta sobre la red virtual. + +## Casos de Fallo Detectados + +### Caso 1: VNet sin Bastion Host + +* **Descripción:** Se define infraestructura de red, pero no se incluye el servicio de Bastion, lo que sugiere que el acceso administrativo podría estar realizándose de forma insegura mediante IPs públicas directas o puertos abiertos en los grupos de seguridad (NSG). +* **Ubicación de la Alerta:** Sobre el recurso `azurerm_virtual_network`. + +## Recurso Involucrado + +* `azurerm_virtual_network` +* `azurerm_bastion_host` + +## Solución + +Para solucionar el problema, define un recurso `azurerm_bastion_host` y asegúrate de crear la subred obligatoria llamada `AzureBastionSubnet`. + +```terraform +resource "azurerm_subnet" "example_bastion" { + name = "AzureBastionSubnet" + resource_group_name = azurerm_resource_group.example.name + virtual_network_name = azurerm_virtual_network.example.name + address_prefixes = ["10.0.1.0/24"] +} + +resource "azurerm_bastion_host" "example" { + name = "production-bastion" + location = azurerm_resource_group.example.location + resource_group_name = azurerm_resource_group.example.name + + ip_configuration { + name = "configuration" + subnet_id = azurerm_subnet.example_bastion.id + public_ip_address_id = azurerm_public_ip.example.id + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_bastion_host_missing/metadata.json b/assets/queries/terraform/azure/azure_bastion_host_missing/metadata.json new file mode 100644 index 00000000000..b21e74e168d --- /dev/null +++ b/assets/queries/terraform/azure/azure_bastion_host_missing/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "a3e941c5-7708-40d1-943b-7093446ef5e6", + "queryName": "Azure Bastion Host Missing", + "severity": "MEDIUM", + "category": "Networking and Firewall", + "descriptionText": "Ensures that an Azure Bastion Host is present when Virtual Networks are defined. Azure Bastion provides secure and seamless RDP/SSH connectivity to your virtual machines directly from the Azure portal over SSL.", + "descriptionUrl": "https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/bastion_host", + "platform": "Terraform", + "descriptionID": "a3e941c5", + "cloudProvider": "azure", + "cwe": "CWE-284", + "riskScore": 5.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_bastion_host_missing/query.rego b/assets/queries/terraform/azure/azure_bastion_host_missing/query.rego new file mode 100644 index 00000000000..0f5dabba3ac --- /dev/null +++ b/assets/queries/terraform/azure/azure_bastion_host_missing/query.rego @@ -0,0 +1,20 @@ +package Cx + +# REGLA 1: Existe una VNet, pero no existe ningún recurso azurerm_bastion_host en el documento. +CxPolicy[result] { + doc := input.document[i] + + vnet := doc.resource.azurerm_virtual_network[name] + + bastions := [b | b := doc.resource.azurerm_bastion_host[_]] + + count(bastions) == 0 + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.azurerm_virtual_network.%s", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": sprintf("An 'azurerm_bastion_host' resource should be defined to protect Virtual Network '%s'", [name]), + "keyActualValue": "No 'azurerm_bastion_host' resource was found in the configuration", + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_bastion_host_missing/test/negative1.tf b/assets/queries/terraform/azure/azure_bastion_host_missing/test/negative1.tf new file mode 100644 index 00000000000..88141688bb2 --- /dev/null +++ b/assets/queries/terraform/azure/azure_bastion_host_missing/test/negative1.tf @@ -0,0 +1,18 @@ +resource "azurerm_virtual_network" "pass_vnet" { + name = "secure-network" + resource_group_name = "rg-test" + location = "West Europe" + address_space = ["10.0.0.0/16"] +} + +resource "azurerm_bastion_host" "pass_bastion" { + name = "bastion-host" + location = "West Europe" + resource_group_name = "rg-test" + + ip_configuration { + name = "config" + subnet_id = "dummy-id" + public_ip_address_id = "dummy-pip-id" + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_bastion_host_missing/test/positive1.tf b/assets/queries/terraform/azure/azure_bastion_host_missing/test/positive1.tf new file mode 100644 index 00000000000..2c9968f79d6 --- /dev/null +++ b/assets/queries/terraform/azure/azure_bastion_host_missing/test/positive1.tf @@ -0,0 +1,6 @@ +resource "azurerm_virtual_network" "fail_vnet" { + name = "vulnerable-network" + resource_group_name = "rg-test" + location = "West Europe" + address_space = ["10.0.0.0/16"] +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_bastion_host_missing/test/positive_expected_result.json b/assets/queries/terraform/azure/azure_bastion_host_missing/test/positive_expected_result.json new file mode 100644 index 00000000000..865959bff36 --- /dev/null +++ b/assets/queries/terraform/azure/azure_bastion_host_missing/test/positive_expected_result.json @@ -0,0 +1,8 @@ +[ + { + "queryName": "Azure Bastion Host Missing", + "severity": "MEDIUM", + "line": 1, + "fileName": "positive1.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_defender_easm_enabled_manual/README.md b/assets/queries/terraform/azure/azure_defender_easm_enabled_manual/README.md new file mode 100644 index 00000000000..c4411fe5e3f --- /dev/null +++ b/assets/queries/terraform/azure/azure_defender_easm_enabled_manual/README.md @@ -0,0 +1,36 @@ +# Regla KICS: Microsoft Defender EASM Enabled (Manual) + +## Descripción General + +Esta regla funciona como un **recordatorio de cumplimiento manual**. Su objetivo es asegurar que se haya habilitado **Microsoft Defender External Attack Surface Monitoring (EASM)** para monitorear la exposición de los activos de Azure a Internet. + +EASM realiza un descubrimiento continuo de tus activos digitales (direcciones IP, dominios, certificados SSL, etc.) para identificar vulnerabilidades y riesgos en la "sombra" (Shadow IT). Dado que el proveedor actual de Terraform para Azure no cuenta con recursos nativos para gestionar este servicio, la verificación debe realizarse directamente en la plataforma. + +## Lógica de la Regla + +Debido a las limitaciones del análisis estático para este servicio específico: +1. La regla identifica la presencia de recursos `azurerm_resource_group`. +2. Genera una alerta de severidad **INFO** por cada grupo detectado. +3. Actúa como un check-list para que el equipo de seguridad valide el estado del servicio en el Portal de Azure. + +## Casos de Fallo Detectados + +### Caso 1: Verificación Manual Requerida + +* **Descripción:** Se ha detectado infraestructura desplegada, pero el estado de protección de EASM no es visible para KICS. +* **Ubicación de la Alerta:** Sobre el recurso `azurerm_resource_group`. + +## Recurso Involucrado + +* `azurerm_resource_group` + +## Solución + +Esta alerta no se resuelve mediante cambios en el código HCL estándar. + +**Pasos de remediación manual:** +1. Acceda al [Portal de Azure](https://portal.azure.com). +2. En el buscador superior, escriba **"Microsoft Defender EASM"**. +3. Verifique si existe un recurso EASM configurado y realizando escaneos activos. +4. Si no existe, considere su creación para mejorar la postura de seguridad externa. +5. Documente la verificación para cerrar el hallazgo en el reporte de KICS. \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_defender_easm_enabled_manual/metadata.json b/assets/queries/terraform/azure/azure_defender_easm_enabled_manual/metadata.json new file mode 100644 index 00000000000..9f1fe2d2c5a --- /dev/null +++ b/assets/queries/terraform/azure/azure_defender_easm_enabled_manual/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "b9f8511b-bd48-4e9d-8439-94e49e671490", + "queryName": "Microsoft Defender EASM Enabled (Manual)", + "severity": "INFO", + "category": "Observability", + "descriptionText": "Ensures that Microsoft Defender External Attack Surface Monitoring (EASM) is enabled. Since EASM cannot be fully configured or verified via standard Terraform resources yet, this rule serves as a manual reminder to verify the service in the Azure Portal.", + "descriptionUrl": "https://azure.microsoft.com/en-us/products/defender-easm/", + "platform": "Terraform", + "descriptionID": "b9f8511b", + "cloudProvider": "azure", + "cwe": "CWE-778", + "riskScore": 0.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_defender_easm_enabled_manual/query.rego b/assets/queries/terraform/azure/azure_defender_easm_enabled_manual/query.rego new file mode 100644 index 00000000000..b0084fd4d98 --- /dev/null +++ b/assets/queries/terraform/azure/azure_defender_easm_enabled_manual/query.rego @@ -0,0 +1,15 @@ +package Cx + +# REGLA 1: Genera un aviso manual por cada Resource Group encontrado. +CxPolicy[result] { + doc := input.document[i] + rg := doc.resource.azurerm_resource_group[name] + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.azurerm_resource_group.%s", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": sprintf("Microsoft Defender EASM should be verified manually for resource group '%s'", [name]), + "keyActualValue": "EASM status cannot be verified statically via Terraform (Manual Verification Required)", + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_defender_easm_enabled_manual/test/positive1.tf b/assets/queries/terraform/azure/azure_defender_easm_enabled_manual/test/positive1.tf new file mode 100644 index 00000000000..dd2fcad1cd6 --- /dev/null +++ b/assets/queries/terraform/azure/azure_defender_easm_enabled_manual/test/positive1.tf @@ -0,0 +1,4 @@ +resource "azurerm_resource_group" "audit_me" { + name = "rg-production-safety" + location = "East US" +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_defender_easm_enabled_manual/test/positive_expected_result.json b/assets/queries/terraform/azure/azure_defender_easm_enabled_manual/test/positive_expected_result.json new file mode 100644 index 00000000000..ccef5c4313c --- /dev/null +++ b/assets/queries/terraform/azure/azure_defender_easm_enabled_manual/test/positive_expected_result.json @@ -0,0 +1,8 @@ +[ + { + "queryName": "Microsoft Defender EASM Enabled (Manual)", + "severity": "INFO", + "line": 1, + "fileName": "positive1.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_elastic_san_public_access_enabled/README.md b/assets/queries/terraform/azure/azure_elastic_san_public_access_enabled/README.md new file mode 100644 index 00000000000..fc1bb17ee44 --- /dev/null +++ b/assets/queries/terraform/azure/azure_elastic_san_public_access_enabled/README.md @@ -0,0 +1,52 @@ +# Regla KICS: Elastic SAN Public Network Access Enabled + +## Descripción General + +Esta regla de KICS verifica que los grupos de volúmenes de Azure Elastic SAN (`azurerm_elastic_san_volume_group`) tengan restringido el acceso desde redes públicas. + +En la arquitectura de Azure Elastic SAN, la seguridad y el aislamiento de red no se gestionan en el recurso raíz de la SAN, sino a nivel de **Volume Group**. Para garantizar que los volúmenes de datos no sean accesibles desde Internet, es imprescindible definir el bloque `network_rule`. La sola presencia de este bloque activa una política de denegación implícita para cualquier tráfico que no provenga de las subredes autorizadas, asegurando que la infraestructura solo sea accesible a través de la red privada. + +## Lógica de la Regla + +La política audita el recurso `azurerm_elastic_san_volume_group` analizando la siguiente condición: +1. **Existencia del Bloque de Red:** Se verifica la presencia del bloque `network_rule`. Si este bloque no está definido, el grupo de volúmenes carece de restricciones perimetrales, permitiendo potencialmente el acceso público. + +## Caso de Fallo Detectado + +A continuación se describe el escenario que esta política detectará. + +--- + +### Caso Único: Configuración de Red Ausente + +* **Descripción:** El grupo de volúmenes se define sin el bloque `network_rule`, lo que significa que no se están aplicando reglas de filtrado de IP o de red virtual para proteger los datos. +* **Ejemplo de Código Terraform Problemático:** + ```terraform + resource "azurerm_elastic_san_volume_group" "example_insecure" { + name = "insecure-vg" + elastic_san_id = azurerm_elastic_san.example.id + + # El recurso carece del bloque network_rule, + # quedando expuesto a redes públicas. + } + ``` +* **Ubicación de la Alerta:** Sobre el recurso raíz `azurerm_elastic_san_volume_group`. + +## Recurso Involucrado + +* `azurerm_elastic_san_volume_group` + +## Solución + +Para solucionar este riesgo de seguridad, defina el bloque `network_rule` vinculándolo a una subred autorizada mediante el atributo `subnet_id`. + +```terraform +resource "azurerm_elastic_san_volume_group" "secure_vg" { + name = "secure-volume-group" + elastic_san_id = azurerm_elastic_san.example.id + + # SOLUCIÓN: Definir reglas de red para denegar el acceso público + network_rule { + subnet_id = azurerm_subnet.example.id + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_elastic_san_public_access_enabled/metadata.json b/assets/queries/terraform/azure/azure_elastic_san_public_access_enabled/metadata.json new file mode 100644 index 00000000000..9bf0857c145 --- /dev/null +++ b/assets/queries/terraform/azure/azure_elastic_san_public_access_enabled/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "660863a5-bb45-4907-8afe-d7478177a446", + "queryName": "Elastic SAN Public Network Access Enabled", + "severity": "HIGH", + "category": "Networking and Firewall", + "descriptionText": "Ensures that 'public_network_access_enabled' is set to false for Azure Elastic SAN. Disabling public access ensures that the SAN is only accessible via private endpoints within the virtual network.", + "descriptionUrl": "https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/elastic_san#public_network_access_enabled", + "platform": "Terraform", + "cloudProvider": "azure", + "cwe": "CWE-284", + "descriptionID": "660863a5", + "riskScore": 9.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_elastic_san_public_access_enabled/query.rego b/assets/queries/terraform/azure/azure_elastic_san_public_access_enabled/query.rego new file mode 100644 index 00000000000..ab3e86102fe --- /dev/null +++ b/assets/queries/terraform/azure/azure_elastic_san_public_access_enabled/query.rego @@ -0,0 +1,18 @@ +package Cx + +# REGLA 1: El bloque 'network_rule' no está definido. +# En azurerm_elastic_san_volume_group, la ausencia del bloque permite el acceso público. +CxPolicy[result] { + doc := input.document[i] + vg := doc.resource.azurerm_elastic_san_volume_group[name] + + not vg.network_rule + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.azurerm_elastic_san_volume_group.%s", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": sprintf("'azurerm_elastic_san_volume_group.%s' should have a 'network_rule' block to restrict public access", [name]), + "keyActualValue": sprintf("'azurerm_elastic_san_volume_group.%s' is missing the 'network_rule' block", [name]), + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_elastic_san_public_access_enabled/test/negative1.tf b/assets/queries/terraform/azure/azure_elastic_san_public_access_enabled/test/negative1.tf new file mode 100644 index 00000000000..b2d4f4b0b26 --- /dev/null +++ b/assets/queries/terraform/azure/azure_elastic_san_public_access_enabled/test/negative1.tf @@ -0,0 +1,8 @@ +resource "azurerm_elastic_san_volume_group" "pass" { + name = "secure-vg" + elastic_san_id = "san-id" + + network_rule { + subnet_id = "/subscriptions/0000/resourceGroups/rg/providers/Microsoft.Network/virtualNetworks/vnet/subnets/s1" + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_elastic_san_public_access_enabled/test/positive1.tf b/assets/queries/terraform/azure/azure_elastic_san_public_access_enabled/test/positive1.tf new file mode 100644 index 00000000000..f03f3c5de58 --- /dev/null +++ b/assets/queries/terraform/azure/azure_elastic_san_public_access_enabled/test/positive1.tf @@ -0,0 +1,5 @@ +resource "azurerm_elastic_san_volume_group" "fail" { + name = "insecure-vg" + elastic_san_id = "san-id" + # Falla por no tener network_rule +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_elastic_san_public_access_enabled/test/positive_expected_result.json b/assets/queries/terraform/azure/azure_elastic_san_public_access_enabled/test/positive_expected_result.json new file mode 100644 index 00000000000..dbe5fa31bfe --- /dev/null +++ b/assets/queries/terraform/azure/azure_elastic_san_public_access_enabled/test/positive_expected_result.json @@ -0,0 +1,8 @@ +[ + { + "queryName": "Elastic SAN Public Network Access Enabled", + "severity": "HIGH", + "line": 1, + "fileName": "positive1.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_elastic_san_volume_group_cmk_encryption_disabled/README.md b/assets/queries/terraform/azure/azure_elastic_san_volume_group_cmk_encryption_disabled/README.md new file mode 100644 index 00000000000..fc8dfbdb8da --- /dev/null +++ b/assets/queries/terraform/azure/azure_elastic_san_volume_group_cmk_encryption_disabled/README.md @@ -0,0 +1,68 @@ +# Regla KICS: Elastic SAN Volume Group CMK Encryption Disabled + +## Descripción General + +Esta regla de KICS verifica que los grupos de volúmenes de Azure Elastic SAN (`azurerm_elastic_san_volume_group`) estén configurados para utilizar claves gestionadas por el cliente (Customer-Managed Keys - CMK) para el cifrado de datos en reposo. + +El uso de CMK sobre las claves gestionadas por la plataforma por defecto es una práctica de seguridad crítica. Proporciona a las organizaciones un control total sobre el ciclo de vida de las claves criptográficas, incluyendo políticas de acceso, rotación y revocación. Esto es esencial para cumplir con requisitos regulatorios y garantizar que el acceso a los datos almacenados en la SAN esté protegido por claves bajo el control directo del cliente. + +## Lógica de la Regla + +La política analiza el recurso `azurerm_elastic_san_volume_group` validando dos pilares fundamentales: +1. **Configuración del Tipo:** El atributo `encryption_type` debe estar establecido inequívocamente como `EncryptionAtRestWithCustomerManagedKey`. +2. **Infraestructura de Soporte:** Si se selecciona CMK, el recurso debe incluir obligatoriamente los bloques `encryption` (que vincula la clave) e `identity` (que permite el acceso a la misma). + +## Casos de Fallo Detectados + +A continuación se describen los escenarios que esta política detectará. + +--- +### Caso 1: Cifrado CMK no Configurado (Uso de Claves de Plataforma) + +* **Descripción:** El recurso carece del atributo de cifrado CMK o utiliza el valor por defecto gestionado por la plataforma. +* **Ejemplo de Código Terraform Problemático:** + ```terraform + resource "azurerm_elastic_san_volume_group" "fail_platform" { + name = "example-vg" + elastic_san_id = azurerm_elastic_san.example.id + } + ``` +* **Ubicación de la Alerta:** Recurso raíz `azurerm_elastic_san_volume_group`. + +--- +### Caso 2: CMK Seleccionado con Bloques Técnicos Ausentes + +* **Descripción:** Se ha activado el tipo de cifrado CMK, pero falta uno o ambos bloques (`encryption` / `identity`) necesarios para que el cifrado sea operativo. +* **Ejemplo de Código Terraform Problemático:** + ```terraform + resource "azurerm_elastic_san_volume_group" "fail_missing_blocks" { + encryption_type = "EncryptionAtRestWithCustomerManagedKey" + # Falta bloque encryption o bloque identity + } + ``` +* **Ubicación de la Alerta:** Recurso raíz `azurerm_elastic_san_volume_group`. + +## Recurso Involucrado + +* `azurerm_elastic_san_volume_group` + +## Solución + +Para solucionar el problema, asegúrese de declarar el tipo de cifrado CMK e incluir los bloques técnicos requeridos con sus parámetros obligatorios. + +```terraform +resource "azurerm_elastic_san_volume_group" "secure_vg" { + name = "secure-volume-group" + elastic_san_id = azurerm_elastic_san.example.id + + encryption_type = "EncryptionAtRestWithCustomerManagedKey" + + encryption { + key_vault_key_id = azurerm_key_vault_key.example.id + } + + identity { + type = "UserAssigned" + identity_ids = [azurerm_user_assigned_identity.example.id] + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_elastic_san_volume_group_cmk_encryption_disabled/metadata.json b/assets/queries/terraform/azure/azure_elastic_san_volume_group_cmk_encryption_disabled/metadata.json new file mode 100644 index 00000000000..0da8c3ce89b --- /dev/null +++ b/assets/queries/terraform/azure/azure_elastic_san_volume_group_cmk_encryption_disabled/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "3b101ad5-3602-4a9e-b6ca-16b7b31b153c", + "queryName": "Elastic SAN Volume Group CMK Encryption Disabled", + "severity": "MEDIUM", + "category": "Encryption", + "descriptionText": "Ensures that Azure Elastic SAN Volume Groups are encrypted using Customer-Managed Keys (CMK). Using CMK provides control over key lifecycle and access policies, unlike the default platform-managed keys.", + "descriptionUrl": "https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/elastic_san_volume_group#encryption_type", + "platform": "Terraform", + "cloudProvider": "azure", + "cwe": "CWE-326", + "descriptionID": "3b101ad5", + "riskScore": 5.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_elastic_san_volume_group_cmk_encryption_disabled/query.rego b/assets/queries/terraform/azure/azure_elastic_san_volume_group_cmk_encryption_disabled/query.rego new file mode 100644 index 00000000000..9cee7397bb0 --- /dev/null +++ b/assets/queries/terraform/azure/azure_elastic_san_volume_group_cmk_encryption_disabled/query.rego @@ -0,0 +1,38 @@ +package Cx + +# REGLA 1: El tipo de cifrado no es CMK o no está definido. +CxPolicy[result] { + doc := input.document[i] + vg := doc.resource.azurerm_elastic_san_volume_group[name] + + object.get(vg, "encryption_type", "undefined") != "EncryptionAtRestWithCustomerManagedKey" + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.azurerm_elastic_san_volume_group.%s", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "'encryption_type' should be set to 'EncryptionAtRestWithCustomerManagedKey'", + "keyActualValue": sprintf("'encryption_type' is set to '%v'", [object.get(vg, "encryption_type", "PlatformKey (Default)")]), + } +} + +# REGLA 2: Si el tipo es CMK, debe existir tanto el bloque 'encryption' como el bloque 'identity'. +CxPolicy[result] { + doc := input.document[i] + vg := doc.resource.azurerm_elastic_san_volume_group[name] + vg.encryption_type == "EncryptionAtRestWithCustomerManagedKey" + + required_blocks := {"encryption", "identity"} + existing_blocks := {b | vg[b]} + missing := required_blocks - existing_blocks + + count(missing) > 0 + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.azurerm_elastic_san_volume_group.%s", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": sprintf("'azurerm_elastic_san_volume_group.%s' should have both 'encryption' and 'identity' blocks for CMK", [name]), + "keyActualValue": sprintf("'azurerm_elastic_san_volume_group.%s' is missing the following block(s): %s", [name, concat(", ", missing)]), + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_elastic_san_volume_group_cmk_encryption_disabled/test/negative1.tf b/assets/queries/terraform/azure/azure_elastic_san_volume_group_cmk_encryption_disabled/test/negative1.tf new file mode 100644 index 00000000000..b7475da1de0 --- /dev/null +++ b/assets/queries/terraform/azure/azure_elastic_san_volume_group_cmk_encryption_disabled/test/negative1.tf @@ -0,0 +1,14 @@ +resource "azurerm_elastic_san_volume_group" "pass" { + name = "secure-vg" + elastic_san_id = "san-id" + encryption_type = "EncryptionAtRestWithCustomerManagedKey" + + encryption { + key_vault_key_id = "https://kv.vault.azure.net/keys/key/v1" + } + + identity { + type = "UserAssigned" + identity_ids = ["managed_id"] + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_elastic_san_volume_group_cmk_encryption_disabled/test/positive1.tf b/assets/queries/terraform/azure/azure_elastic_san_volume_group_cmk_encryption_disabled/test/positive1.tf new file mode 100644 index 00000000000..a3a7012a077 --- /dev/null +++ b/assets/queries/terraform/azure/azure_elastic_san_volume_group_cmk_encryption_disabled/test/positive1.tf @@ -0,0 +1,5 @@ +resource "azurerm_elastic_san_volume_group" "fail_type" { + name = "vg-fail-type" + elastic_san_id = "san-id" + # Falla por no tener encryption_type CMK +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_elastic_san_volume_group_cmk_encryption_disabled/test/positive2.tf b/assets/queries/terraform/azure/azure_elastic_san_volume_group_cmk_encryption_disabled/test/positive2.tf new file mode 100644 index 00000000000..8eb87a597a9 --- /dev/null +++ b/assets/queries/terraform/azure/azure_elastic_san_volume_group_cmk_encryption_disabled/test/positive2.tf @@ -0,0 +1,6 @@ +resource "azurerm_elastic_san_volume_group" "fail_blocks" { + name = "vg-fail-blocks" + elastic_san_id = "san-id" + encryption_type = "EncryptionAtRestWithCustomerManagedKey" + # Falla por faltar bloques encryption e identity +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_elastic_san_volume_group_cmk_encryption_disabled/test/positive_expected_result.json b/assets/queries/terraform/azure/azure_elastic_san_volume_group_cmk_encryption_disabled/test/positive_expected_result.json new file mode 100644 index 00000000000..c0f988b83b3 --- /dev/null +++ b/assets/queries/terraform/azure/azure_elastic_san_volume_group_cmk_encryption_disabled/test/positive_expected_result.json @@ -0,0 +1,14 @@ +[ + { + "queryName": "Elastic SAN Volume Group CMK Encryption Disabled", + "severity": "MEDIUM", + "line": 1, + "fileName": "positive.tf" + }, + { + "queryName": "Elastic SAN Volume Group CMK Encryption Disabled", + "severity": "MEDIUM", + "line": 1, + "fileName": "positive.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_iot_hub_defender_disabled/README.md b/assets/queries/terraform/azure/azure_iot_hub_defender_disabled/README.md new file mode 100644 index 00000000000..c40af7851dc --- /dev/null +++ b/assets/queries/terraform/azure/azure_iot_hub_defender_disabled/README.md @@ -0,0 +1,72 @@ +# Regla KICS: Azure IoT Hub Defender Disabled + +## Descripción General + +Esta regla de KICS verifica que los recursos **Azure IoT Hub** (`azurerm_iothub`) estén protegidos por **Microsoft Defender for IoT**. + +Microsoft Defender for IoT proporciona una capa esencial de seguridad para entornos de Internet de las cosas, ofreciendo detección de amenazas en tiempo real y gestión de la postura de seguridad. La falta de esta protección deja a los dispositivos IoT y a la infraestructura central del Hub vulnerables ante ataques dirigidos, movimientos laterales y exfiltración de datos. En Terraform, esta protección se habilita vinculando el IoT Hub con un recurso del tipo `azurerm_iot_security_solution`. + +## Lógica de la Regla + +La política analiza la configuración de Terraform realizando los siguientes pasos: +1. **Identificación de Hubs:** Selecciona todos los recursos de tipo `azurerm_iothub`. +2. **Verificación de Soluciones:** Busca recursos `azurerm_iot_security_solution` en el documento. +3. **Validación de Vinculación:** Comprueba si el identificador del Hub está presente en la lista `iothub_ids` de alguna solución de seguridad definida, contemplando formatos directos o interpolados. +4. **Generación de Alerta:** Si un Hub no está referenciado en ninguna solución, se genera un hallazgo de seguridad. + +## Caso de Fallo Detectado + +A continuación se describe el escenario que esta política detectará. + +--- + +### Caso Único: IoT Hub sin Defender asociado + +* **Descripción:** Se define un recurso `azurerm_iothub` pero no se encuentra ningún recurso `azurerm_iot_security_solution` que lo incluya en su lista de IDs protegidos. +* **Ejemplo de Código Terraform Problemático:** + ```terraform + resource "azurerm_iothub" "fail_hub" { + name = "insecure-iothub" + resource_group_name = azurerm_resource_group.example.name + location = azurerm_resource_group.example.location + + sku { + name = "S1" + capacity = "1" + } + } + + # No existe azurerm_iot_security_solution que referencie a fail_hub + ``` +* **Ubicación de la Alerta:** Sobre el recurso raíz `azurerm_iothub`. + +## Recurso Involucrado + +* `azurerm_iothub` +* `azurerm_iot_security_solution` + +## Solución + +Para solucionar este riesgo, asegúrese de definir un recurso `azurerm_iot_security_solution` e incluir el ID del IoT Hub en el atributo `iothub_ids`. + +```terraform +resource "azurerm_iothub" "example" { + name = "secure-iothub" + resource_group_name = azurerm_resource_group.example.name + location = azurerm_resource_group.example.location + + sku { + name = "S1" + capacity = "1" + } +} + +# SOLUCIÓN: Definir la solución de seguridad y vincular el Hub +resource "azurerm_iot_security_solution" "example" { + name = "iot-security-solution" + resource_group_name = azurerm_resource_group.example.name + location = azurerm_resource_group.example.location + display_name = "Iot Security Solution" + + iothub_ids = [azurerm_iothub.example.id] +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_iot_hub_defender_disabled/metadata.json b/assets/queries/terraform/azure/azure_iot_hub_defender_disabled/metadata.json new file mode 100644 index 00000000000..4c7522e2bb0 --- /dev/null +++ b/assets/queries/terraform/azure/azure_iot_hub_defender_disabled/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "13be738b-78fb-4d0f-a5ed-13e0f36fd385", + "queryName": "Azure IoT Hub Defender Disabled", + "severity": "MEDIUM", + "category": "Networking and Firewall", + "descriptionText": "Ensures that Microsoft Defender for IoT is enabled for Azure IoT Hubs. Defender for IoT provides threat detection and security posture management for IoT environments.", + "descriptionUrl": "https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/iot_security_solution", + "platform": "Terraform", + "cloudProvider": "azure", + "cwe": "CWE-693", + "descriptionID": "13be738b", + "riskScore": 5.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_iot_hub_defender_disabled/query.rego b/assets/queries/terraform/azure/azure_iot_hub_defender_disabled/query.rego new file mode 100644 index 00000000000..13c444b6108 --- /dev/null +++ b/assets/queries/terraform/azure/azure_iot_hub_defender_disabled/query.rego @@ -0,0 +1,33 @@ +package Cx + +# REGLA 1: IoT Hub sin solución de seguridad (azurerm_iot_security_solution) asociada. +CxPolicy[result] { + doc := input.document[i] + iot_hub := doc.resource.azurerm_iothub[hub_name] + + hub_ref := sprintf("azurerm_iothub.%s.id", [hub_name]) + + solutions := [sol | + sol := doc.resource.azurerm_iot_security_solution[_] + current_hub_id := sol.iothub_ids[_] + check_hub_id(current_hub_id, hub_ref) + ] + + count(solutions) == 0 + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.azurerm_iothub.%s", [hub_name]), + "issueType": "MissingAttribute", + "keyExpectedValue": sprintf("'azurerm_iothub.%s' should be included in 'iothub_ids' of an 'azurerm_iot_security_solution'", [hub_name]), + "keyActualValue": sprintf("'azurerm_iothub.%s' is not associated with any 'azurerm_iot_security_solution'", [hub_name]), + } +} + +check_hub_id(current, target) { + current == target +} + +check_hub_id(current, target) { + current == sprintf("${%s}", [target]) +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_iot_hub_defender_disabled/test/negative1.tf b/assets/queries/terraform/azure/azure_iot_hub_defender_disabled/test/negative1.tf new file mode 100644 index 00000000000..f28ce4531df --- /dev/null +++ b/assets/queries/terraform/azure/azure_iot_hub_defender_disabled/test/negative1.tf @@ -0,0 +1,18 @@ +resource "azurerm_iothub" "pass" { + name = "example-iothub-pass" + resource_group_name = "rg-test" + location = "West Europe" + + sku { + name = "S1" + capacity = "1" + } +} + +resource "azurerm_iot_security_solution" "pass" { + name = "example-security-solution" + resource_group_name = "rg-test" + location = "West Europe" + display_name = "Iot Security Solution" + iothub_ids = [azurerm_iothub.pass.id] +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_iot_hub_defender_disabled/test/positive1.tf b/assets/queries/terraform/azure/azure_iot_hub_defender_disabled/test/positive1.tf new file mode 100644 index 00000000000..d91f9e76260 --- /dev/null +++ b/assets/queries/terraform/azure/azure_iot_hub_defender_disabled/test/positive1.tf @@ -0,0 +1,10 @@ +resource "azurerm_iothub" "fail" { + name = "example-iothub-fail" + resource_group_name = "rg-test" + location = "West Europe" + + sku { + name = "S1" + capacity = "1" + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_iot_hub_defender_disabled/test/positive_expected_result.json b/assets/queries/terraform/azure/azure_iot_hub_defender_disabled/test/positive_expected_result.json new file mode 100644 index 00000000000..64e4501aad7 --- /dev/null +++ b/assets/queries/terraform/azure/azure_iot_hub_defender_disabled/test/positive_expected_result.json @@ -0,0 +1,8 @@ +[ + { + "queryName": "Azure IoT Hub Defender Disabled", + "severity": "MEDIUM", + "line": 1, + "fileName": "positive1.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_key_vault_key_rotation_disabled/README.md b/assets/queries/terraform/azure/azure_key_vault_key_rotation_disabled/README.md new file mode 100644 index 00000000000..520e46cf594 --- /dev/null +++ b/assets/queries/terraform/azure/azure_key_vault_key_rotation_disabled/README.md @@ -0,0 +1,64 @@ +# Regla KICS: Key Vault Key Rotation Disabled + +## Descripción General + +Esta regla de KICS verifica que las claves criptográficas almacenadas en Azure Key Vault (`azurerm_key_vault_key`) tengan configurada una política de rotación automática. + +La rotación automática de claves es una práctica de seguridad esencial que limita la cantidad de datos cifrados con una sola versión de clave y reduce significativamente el impacto en caso de que una clave se vea comprometida. Configurar una política de rotación asegura que las claves se renueven de forma proactiva sin intervención manual, garantizando la continuidad operativa y el cumplimiento de estándares de seguridad como PCI-DSS o HIPAA. + +## Lógica de la Regla + +La política analiza el recurso `azurerm_key_vault_key` realizando los siguientes pasos: +1. **Identificación de Claves:** Selecciona todos los recursos de tipo `azurerm_key_vault_key`. +2. **Verificación de Política:** Comprueba la existencia del bloque de configuración `rotation_policy`. +3. **Generación de Alerta:** Si el bloque está ausente, se considera que la clave no tiene una estrategia de rotación definida y se genera un hallazgo. + +## Caso de Fallo Detectado + +A continuación se describe el escenario que esta política detectará. + +--- + +### Caso Único: Clave sin política de rotación + +* **Descripción:** Se define una clave criptográfica en Key Vault pero se omite el bloque `rotation_policy`, dejando la responsabilidad de la rotación a procesos manuales o permitiendo que la clave permanezca indefinidamente. +* **Ejemplo de Código Terraform Problemático:** + ```terraform + resource "azurerm_key_vault_key" "fail_key" { + name = "example-key" + key_vault_id = azurerm_key_vault.example.id + key_type = "RSA" + key_size = 2048 + key_opts = ["decrypt", "encrypt", "sign", "verify"] + + # El recurso carece de la configuración rotation_policy + } + ``` +* **Ubicación de la Alerta:** Sobre el recurso raíz `azurerm_key_vault_key`. + +## Recurso Involucrado + +* `azurerm_key_vault_key` + +## Solución + +Para solucionar este riesgo, añada el bloque `rotation_policy` definiendo el tiempo de expiración y las reglas de rotación automática deseadas. + +```terraform +resource "azurerm_key_vault_key" "secure_key" { + name = "example-key" + key_vault_id = azurerm_key_vault.example.id + key_type = "RSA" + key_size = 2048 + key_opts = ["decrypt", "encrypt", "sign", "verify"] + + # SOLUCIÓN: Definir política de rotación automática + rotation_policy { + expire_after = "P90D" + notify_before_expiry = "P29D" + + automatic { + time_before_expiry = "P30D" + } + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_key_vault_key_rotation_disabled/metadata.json b/assets/queries/terraform/azure/azure_key_vault_key_rotation_disabled/metadata.json new file mode 100644 index 00000000000..6498e1bff29 --- /dev/null +++ b/assets/queries/terraform/azure/azure_key_vault_key_rotation_disabled/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "ca1ffce5-e2f9-4d8a-978e-719d1ed7c7fa", + "queryName": "Key Vault Key Rotation Disabled", + "severity": "MEDIUM", + "category": "Encryption", + "descriptionText": "Ensures that cryptographic keys in Azure Key Vault have an automatic rotation policy configured. Regular key rotation reduces the risk of compromised keys being used for extended periods.", + "descriptionUrl": "https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/key_vault_key#rotation_policy", + "platform": "Terraform", + "cloudProvider": "azure", + "cwe": "CWE-320", + "descriptionID": "ca1ffce5", + "riskScore": 5.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_key_vault_key_rotation_disabled/query.rego b/assets/queries/terraform/azure/azure_key_vault_key_rotation_disabled/query.rego new file mode 100644 index 00000000000..13ca1cc6da7 --- /dev/null +++ b/assets/queries/terraform/azure/azure_key_vault_key_rotation_disabled/query.rego @@ -0,0 +1,17 @@ +package Cx + +# REGLA 1: La clave del Key Vault no tiene política de rotación configurada. +CxPolicy[result] { + doc := input.document[i] + key := doc.resource.azurerm_key_vault_key[name] + + not key.rotation_policy + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.azurerm_key_vault_key.%s", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": sprintf("'azurerm_key_vault_key.%s' should have a 'rotation_policy' block defined", [name]), + "keyActualValue": sprintf("'azurerm_key_vault_key.%s' does not have a 'rotation_policy' block defined", [name]), + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_key_vault_key_rotation_disabled/test/negative1.tf b/assets/queries/terraform/azure/azure_key_vault_key_rotation_disabled/test/negative1.tf new file mode 100644 index 00000000000..af70d50c07c --- /dev/null +++ b/assets/queries/terraform/azure/azure_key_vault_key_rotation_disabled/test/negative1.tf @@ -0,0 +1,16 @@ +resource "azurerm_key_vault_key" "pass" { + name = "pass-key" + key_vault_id = "vault-id" + key_type = "RSA" + key_size = 2048 + key_opts = ["decrypt", "encrypt"] + + rotation_policy { + expire_after = "P90D" + notify_before_expiry = "P29D" + + automatic { + time_before_expiry = "P30D" + } + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_key_vault_key_rotation_disabled/test/positive1.tf b/assets/queries/terraform/azure/azure_key_vault_key_rotation_disabled/test/positive1.tf new file mode 100644 index 00000000000..4944d9b302e --- /dev/null +++ b/assets/queries/terraform/azure/azure_key_vault_key_rotation_disabled/test/positive1.tf @@ -0,0 +1,7 @@ +resource "azurerm_key_vault_key" "fail" { + name = "fail-key" + key_vault_id = "vault-id" + key_type = "RSA" + key_size = 2048 + key_opts = ["decrypt", "encrypt"] +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_key_vault_key_rotation_disabled/test/positive_expected_result.json b/assets/queries/terraform/azure/azure_key_vault_key_rotation_disabled/test/positive_expected_result.json new file mode 100644 index 00000000000..e3ffd2c068e --- /dev/null +++ b/assets/queries/terraform/azure/azure_key_vault_key_rotation_disabled/test/positive_expected_result.json @@ -0,0 +1,8 @@ +[ + { + "queryName": "Key Vault Key Rotation Disabled", + "severity": "MEDIUM", + "line": 1, + "fileName": "positive1.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_managed_lustre_cmk_encryption_disabled/README.md b/assets/queries/terraform/azure/azure_managed_lustre_cmk_encryption_disabled/README.md new file mode 100644 index 00000000000..8cb4d412bcc --- /dev/null +++ b/assets/queries/terraform/azure/azure_managed_lustre_cmk_encryption_disabled/README.md @@ -0,0 +1,60 @@ +# Regla KICS: Azure Managed Lustre Not Encrypted with CMK + +## Descripción General + +Esta regla de KICS verifica que los sistemas de archivos **Azure Managed Lustre** (`azurerm_managed_lustre_file_system`) estén configurados para utilizar claves gestionadas por el cliente (**Customer-Managed Keys - CMK**) para el cifrado de datos en reposo. + +Por defecto, Azure cifra los datos en Lustre utilizando claves gestionadas por la plataforma. Para cumplir con requisitos de soberanía de datos y gobernanza, se recomienda el uso de CMK mediante el bloque `encryption_key`. Esto otorga a las organizaciones control total sobre el ciclo de vida de la clave, permitiendo la rotación y revocación selectiva del acceso a los datos de alto rendimiento almacenados en el sistema de archivos. + +## Lógica de la Regla + +La política analiza el recurso `azurerm_managed_lustre_file_system` validando la siguiente condición: +1. **Presencia del Bloque de Cifrado:** Se verifica la existencia del bloque `encryption_key`. Si este bloque no está definido, el sistema de archivos utiliza por defecto el cifrado gestionado por Azure. + +## Caso de Fallo Detectado + +A continuación se describe el escenario que esta política detectará. + +--- + +### Caso Único: Uso de Claves Gestionadas por la Plataforma (Bloque Ausente) + +* **Descripción:** El sistema de archivos Lustre se define sin el bloque de configuración de cifrado por cliente, lo que delega la protección de los datos a las claves por defecto de la plataforma. +* **Ejemplo de Código Terraform Problemático:** + ```terraform + resource "azurerm_managed_lustre_file_system" "example_insecure" { + name = "insecure-lustre" + resource_group_name = "rg-example" + location = "West Europe" + sku_name = "AMLFS-Durable-Premium-250" + subnet_id = azurerm_subnet.example.id + storage_capacity_in_tb = 48 + + # El recurso carece del bloque encryption_key + } + ``` +* **Ubicación de la Alerta:** Sobre el recurso raíz `azurerm_managed_lustre_file_system`. + +## Recurso Involucrado + +* `azurerm_managed_lustre_file_system` + +## Solución + +Para solucionar este riesgo, defina el bloque `encryption_key` proporcionando la URL de la clave y el ID del Key Vault de origen. + +```terraform +resource "azurerm_managed_lustre_file_system" "secure_lustre" { + name = "secure-lustre" + resource_group_name = "rg-example" + location = "West Europe" + sku_name = "AMLFS-Durable-Premium-250" + subnet_id = azurerm_subnet.example.id + storage_capacity_in_tb = 48 + + # SOLUCIÓN: Configurar cifrado con CMK + encryption_key { + key_url = azurerm_key_vault_key.example.id + source_vault_id = azurerm_key_vault.example.id + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_managed_lustre_cmk_encryption_disabled/metadata.json b/assets/queries/terraform/azure/azure_managed_lustre_cmk_encryption_disabled/metadata.json new file mode 100644 index 00000000000..752e13181c2 --- /dev/null +++ b/assets/queries/terraform/azure/azure_managed_lustre_cmk_encryption_disabled/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "7fc653e2-af91-4379-9219-5095caba0ff6", + "queryName": "Azure Managed Lustre Not Encrypted with CMK", + "severity": "MEDIUM", + "category": "Encryption", + "descriptionText": "Ensures that Azure Managed Lustre file systems are encrypted using a Customer-Managed Key (CMK). By default, data is encrypted with platform-managed keys, but CMK provides full control over key rotation and access policies.", + "descriptionUrl": "https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/managed_lustre_file_system#key_encryption_key", + "platform": "Terraform", + "cloudProvider": "azure", + "cwe": "CWE-326", + "descriptionID": "7fc653e2", + "riskScore": 5.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_managed_lustre_cmk_encryption_disabled/query.rego b/assets/queries/terraform/azure/azure_managed_lustre_cmk_encryption_disabled/query.rego new file mode 100644 index 00000000000..d0b0f475678 --- /dev/null +++ b/assets/queries/terraform/azure/azure_managed_lustre_cmk_encryption_disabled/query.rego @@ -0,0 +1,17 @@ +package Cx + +# REGLA 1: El bloque 'encryption_key' no está definido. +CxPolicy[result] { + doc := input.document[i] + lustre := doc.resource.azurerm_managed_lustre_file_system[name] + + not lustre.encryption_key + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.azurerm_managed_lustre_file_system.%s", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": sprintf("'azurerm_managed_lustre_file_system.%s' should have an 'encryption_key' block defined", [name]), + "keyActualValue": sprintf("'azurerm_managed_lustre_file_system.%s' is missing the 'encryption_key' block", [name]), + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_managed_lustre_cmk_encryption_disabled/test/negative1.tf b/assets/queries/terraform/azure/azure_managed_lustre_cmk_encryption_disabled/test/negative1.tf new file mode 100644 index 00000000000..e259cb89f05 --- /dev/null +++ b/assets/queries/terraform/azure/azure_managed_lustre_cmk_encryption_disabled/test/negative1.tf @@ -0,0 +1,13 @@ +resource "azurerm_managed_lustre_file_system" "pass" { + name = "pass-lustre" + resource_group_name = "rg" + location = "West Europe" + sku_name = "AMLFS-Durable-Premium-250" + subnet_id = "subnet-id" + storage_capacity_in_tb = 48 + + encryption_key { + key_url = "https://kv.vault.azure.net/keys/key/v1" + source_vault_id = "/subscriptions/000/resourceGroups/rg/providers/Microsoft.KeyVault/vaults/kv" + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_managed_lustre_cmk_encryption_disabled/test/positive1.tf b/assets/queries/terraform/azure/azure_managed_lustre_cmk_encryption_disabled/test/positive1.tf new file mode 100644 index 00000000000..33d647029e0 --- /dev/null +++ b/assets/queries/terraform/azure/azure_managed_lustre_cmk_encryption_disabled/test/positive1.tf @@ -0,0 +1,8 @@ +resource "azurerm_managed_lustre_file_system" "fail" { + name = "fail-lustre" + resource_group_name = "rg" + location = "West Europe" + sku_name = "AMLFS-Durable-Premium-250" + subnet_id = "subnet-id" + storage_capacity_in_tb = 48 +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_managed_lustre_cmk_encryption_disabled/test/positive_expected_result.json b/assets/queries/terraform/azure/azure_managed_lustre_cmk_encryption_disabled/test/positive_expected_result.json new file mode 100644 index 00000000000..256fed25d3e --- /dev/null +++ b/assets/queries/terraform/azure/azure_managed_lustre_cmk_encryption_disabled/test/positive_expected_result.json @@ -0,0 +1,8 @@ +[ + { + "queryName": "Azure Managed Lustre Not Encrypted with CMK", + "severity": "MEDIUM", + "line": 1, + "fileName": "positive1.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_mysql_audit_log_enabled_manual/README.md b/assets/queries/terraform/azure/azure_mysql_audit_log_enabled_manual/README.md new file mode 100644 index 00000000000..2999db498d9 --- /dev/null +++ b/assets/queries/terraform/azure/azure_mysql_audit_log_enabled_manual/README.md @@ -0,0 +1,59 @@ +# Regla KICS: MySQL Audit Log Enabled (Manual) + +## Descripción General + +Esta regla de KICS actúa como un control manual para asegurar que el parámetro de servidor `audit_log_enabled` esté configurado en `ON` en las instancias de **Azure Database for MySQL**. + +Los registros de auditoría son fundamentales para la observabilidad y la seguridad de la base de datos. Permiten rastrear eventos específicos, como intentos de conexión, ejecución de consultas DDL y cambios en privilegios de usuario. Debido a que en Azure MySQL estos parámetros pueden gestionarse de forma externa al recurso del servidor (ya sea por el portal o mediante recursos de configuración independientes), se requiere una validación manual para confirmar el cumplimiento. + +## Lógica de la Regla + +Dado que la configuración de auditoría no reside obligatoriamente dentro del bloque principal del servidor MySQL en Terraform, el análisis estático se centra en: +1. **Identificación de Recursos:** La regla localiza todos los recursos de tipo `azurerm_mssql_server` y `azurerm_mysql_flexible_server`. +2. **Recordatorio de Auditoría:** Genera un hallazgo de severidad informativa para alertar al administrador de la necesidad de verificar el estado del parámetro `audit_log_enabled`. + +## Caso de Fallo Detectado + +A continuación se describe el escenario que esta política detectará. + +--- + +### Caso Único: Verificación Manual Requerida + +* **Descripción:** Se detecta la presencia de un servidor MySQL. Dado que el estado de la auditoría no siempre es verificable estáticamente desde el recurso del servidor, se solicita revisión manual. +* **Ejemplo de Código Terraform Problemático:** + ```terraform + resource "azurerm_mssql_server" "example_insecure" { + name = "mysql-server-audit-check" + resource_group_name = azurerm_resource_group.example.name + location = azurerm_resource_group.example.location + # El estado de audit_log_enabled no es visible aquí. + } + ``` +* **Ubicación de la Alerta:** Sobre el recurso del servidor MySQL detectado. + +## Recurso Involucrado + +* `azurerm_mssql_server` +* `azurerm_mysql_flexible_server` + +## Solución + +Existen dos métodos para asegurar que la auditoría esté habilitada: + +### Opción A: Verificación Manual (Portal de Azure) +1. Navegue a su servidor MySQL en el Portal de Azure. +2. Acceda a la sección **Parámetros del servidor**. +3. Busque el parámetro `audit_log_enabled` y confirme que su valor sea `ON`. + +### Opción B: Configuración como Código (Recomendado) +Utilice el recurso `azurerm_mysql_configuration` (para servidores Single Server) o `azurerm_mysql_flexible_server_configuration` (para Flexible Server) para forzar el parámetro: + +```terraform +# Ejemplo para MySQL Single Server +resource "azurerm_mysql_configuration" "audit_log" { + name = "audit_log_enabled" + resource_group_name = azurerm_resource_group.example.name + server_name = azurerm_mssql_server.example.name + value = "ON" +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_mysql_audit_log_enabled_manual/metadata.json b/assets/queries/terraform/azure/azure_mysql_audit_log_enabled_manual/metadata.json new file mode 100644 index 00000000000..11e04045ca4 --- /dev/null +++ b/assets/queries/terraform/azure/azure_mysql_audit_log_enabled_manual/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "8d8ac482-16c2-4eb7-bfab-e2d94d035498", + "queryName": "MySQL Audit Log Enabled (Manual)", + "severity": "INFO", + "category": "Observability", + "descriptionText": "Ensures that the 'audit_log_enabled' server parameter is set to 'ON' for Azure MySQL Database Servers. Since this parameter is often managed via runtime configurations or separate resources, manual verification is required.", + "descriptionUrl": "https://learn.microsoft.com/en-us/azure/mysql/single-server/concepts-audit-logs", + "platform": "Terraform", + "cloudProvider": "azure", + "cwe": "CWE-778", + "descriptionID": "8d8ac482", + "riskScore": 0.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_mysql_audit_log_enabled_manual/query.rego b/assets/queries/terraform/azure/azure_mysql_audit_log_enabled_manual/query.rego new file mode 100644 index 00000000000..5c28617c5cf --- /dev/null +++ b/assets/queries/terraform/azure/azure_mysql_audit_log_enabled_manual/query.rego @@ -0,0 +1,19 @@ +package Cx + +targets := {"azurerm_mssql_server", "azurerm_mysql_flexible_server"} + +# REGLA MANUAL: Detecta servidores MySQL para solicitar verificación de logs de auditoría. +CxPolicy[result] { + doc := input.document[i] + + resource_type := targets[t] + server := doc.resource[resource_type][name] + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.%s.%s", [resource_type, name]), + "issueType": "MissingAttribute", + "keyExpectedValue": sprintf("'audit_log_enabled' should be set to 'ON' for '%s.%s' (Manual Verification)", [resource_type, name]), + "keyActualValue": "Logging configuration requires manual verification or check of 'azurerm_mysql_configuration' resources", + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_mysql_audit_log_enabled_manual/test/positive1.tf b/assets/queries/terraform/azure/azure_mysql_audit_log_enabled_manual/test/positive1.tf new file mode 100644 index 00000000000..98f95c296a0 --- /dev/null +++ b/assets/queries/terraform/azure/azure_mysql_audit_log_enabled_manual/test/positive1.tf @@ -0,0 +1,8 @@ +resource "azurerm_mssql_server" "fail_single" { + name = "mysql-server-1" + resource_group_name = "rg-test" + location = "West Europe" + administrator_login = "mysqladmin" + administrator_login_password = "Password1234!" + version = "5.7" +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_mysql_audit_log_enabled_manual/test/positive2.tf b/assets/queries/terraform/azure/azure_mysql_audit_log_enabled_manual/test/positive2.tf new file mode 100644 index 00000000000..9cdb9785156 --- /dev/null +++ b/assets/queries/terraform/azure/azure_mysql_audit_log_enabled_manual/test/positive2.tf @@ -0,0 +1,7 @@ +resource "azurerm_mysql_flexible_server" "fail_flexible" { + name = "mysql-flex-server-1" + resource_group_name = "rg-test" + location = "West Europe" + administrator_login = "mysqladmin" + administrator_password = "Password1234!" +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_mysql_audit_log_enabled_manual/test/positive_expected_result.json b/assets/queries/terraform/azure/azure_mysql_audit_log_enabled_manual/test/positive_expected_result.json new file mode 100644 index 00000000000..17539402a93 --- /dev/null +++ b/assets/queries/terraform/azure/azure_mysql_audit_log_enabled_manual/test/positive_expected_result.json @@ -0,0 +1,14 @@ +[ + { + "queryName": "MySQL Audit Log Enabled (Manual)", + "severity": "INFO", + "line": 1, + "fileName": "positive1.tf" + }, + { + "queryName": "MySQL Audit Log Enabled (Manual)", + "severity": "INFO", + "line": 1, + "fileName": "positive2.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_mysql_audit_log_events_connection_manual/README.md b/assets/queries/terraform/azure/azure_mysql_audit_log_events_connection_manual/README.md new file mode 100644 index 00000000000..ade2b20eddb --- /dev/null +++ b/assets/queries/terraform/azure/azure_mysql_audit_log_events_connection_manual/README.md @@ -0,0 +1,59 @@ +# Regla KICS: MySQL Audit Log Events Connection (Manual) + +## Descripción General + +Esta regla de KICS actúa como un control manual para verificar que el parámetro de servidor `audit_log_events` incluya el valor `CONNECTION` en los servidores de **Azure Database for MySQL**. + +El registro de los eventos de tipo `CONNECTION` es una pieza fundamental de la estrategia de seguridad y cumplimiento. Permite a los administradores y auditores rastrear quién accede a la base de datos, detectar ataques de fuerza bruta (intentos fallidos) y auditar sesiones sospechosas. Sin esta configuración, se pierde visibilidad sobre el acceso inicial a los recursos de datos, dificultando la respuesta ante incidentes. + +## Lógica de la Regla + +Debido a que el análisis estático no siempre puede garantizar la verificación de una cadena de texto dentro de un recurso de configuración independiente (`azurerm_mysql_configuration`), esta regla aplica un enfoque preventivo: +1. **Identificación de Recursos:** Detecta todas las instancias de `azurerm_mssql_server` y `azurerm_mysql_flexible_server`. +2. **Recordatorio de Seguridad:** Genera una alerta informativa para que el auditor verifique específicamente que el evento de conexión esté siendo capturado. + +## Caso de Fallo Detectado + +A continuación se describe el escenario que esta política detectará. + +--- + +### Caso Único: Verificación Manual Requerida + +* **Descripción:** Se detecta un servidor MySQL aprovisionado. KICS emite una alerta para asegurar que los tipos de eventos auditados incluyan las conexiones, ya que esta configuración puede estar delegada a otros recursos o al portal. +* **Ejemplo de Código Terraform Problemático:** + ```terraform + resource "azurerm_mssql_server" "example_audit_check" { + name = "mysql-server-connection-check" + resource_group_name = azurerm_resource_group.example.name + location = azurerm_resource_group.example.location + # El contenido de audit_log_events no es verificable aquí. + } + ``` +* **Ubicación de la Alerta:** Sobre el recurso del servidor MySQL detectado. + +## Recurso Involucrado + +* `azurerm_mssql_server` +* `azurerm_mysql_flexible_server` + +## Solución + +### Opción A: Verificación Manual (Portal de Azure) +1. Navegue al servidor MySQL en el Portal de Azure. +2. Vaya a la sección **Parámetros del servidor**. +3. Localice el parámetro `audit_log_events`. +4. Asegúrese de que entre los valores seleccionados se incluya `CONNECTION`. + +### Opción B: Configuración mediante Terraform +Asegúrese de incluir explícitamente el valor en el recurso de configuración: + +```terraform +resource "azurerm_mysql_configuration" "audit_events" { + name = "audit_log_events" + resource_group_name = azurerm_resource_group.example.name + server_name = azurerm_mssql_server.example.name + + # SOLUCIÓN: Incluir CONNECTION en la lista de eventos + value = "CONNECTION,QUERY,DDL" +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_mysql_audit_log_events_connection_manual/metadata.json b/assets/queries/terraform/azure/azure_mysql_audit_log_events_connection_manual/metadata.json new file mode 100644 index 00000000000..d5160af4619 --- /dev/null +++ b/assets/queries/terraform/azure/azure_mysql_audit_log_events_connection_manual/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "fe65cd89-28ea-4301-9c50-6dee30553557", + "queryName": "MySQL Audit Log Events Connection (Manual)", + "severity": "INFO", + "category": "Observability", + "descriptionText": "Ensures that the 'audit_log_events' server parameter includes 'CONNECTION' for Azure MySQL Database Servers. This ensures that successful and failed connection attempts are logged.", + "descriptionUrl": "https://learn.microsoft.com/en-us/azure/mysql/single-server/concepts-audit-logs", + "platform": "Terraform", + "cloudProvider": "azure", + "cwe": "CWE-778", + "descriptionID": "fe65cd89", + "riskScore": 0.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_mysql_audit_log_events_connection_manual/query.rego b/assets/queries/terraform/azure/azure_mysql_audit_log_events_connection_manual/query.rego new file mode 100644 index 00000000000..91355c01e0e --- /dev/null +++ b/assets/queries/terraform/azure/azure_mysql_audit_log_events_connection_manual/query.rego @@ -0,0 +1,19 @@ +package Cx + +targets := {"azurerm_mssql_server", "azurerm_mysql_flexible_server"} + +# REGLA MANUAL: Detecta servidores MySQL para solicitar verificación del evento CONNECTION. +CxPolicy[result] { + doc := input.document[i] + + resource_type := targets[t] + server := doc.resource[resource_type][name] + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.%s.%s", [resource_type, name]), + "issueType": "MissingAttribute", + "keyExpectedValue": sprintf("'audit_log_events' should include 'CONNECTION' for '%s.%s' (Manual Verification)", [resource_type, name]), + "keyActualValue": "Audit log event configuration requires manual verification or check of 'azurerm_mysql_configuration'", + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_mysql_audit_log_events_connection_manual/test/positive1.tf b/assets/queries/terraform/azure/azure_mysql_audit_log_events_connection_manual/test/positive1.tf new file mode 100644 index 00000000000..d406e50d290 --- /dev/null +++ b/assets/queries/terraform/azure/azure_mysql_audit_log_events_connection_manual/test/positive1.tf @@ -0,0 +1,8 @@ +resource "azurerm_mssql_server" "fail_single" { + name = "mysql-server-connection-fail" + resource_group_name = "rg-test" + location = "West Europe" + administrator_login = "mysqladmin" + administrator_login_password = "Password1234!" + version = "5.7" +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_mysql_audit_log_events_connection_manual/test/positive2.tf b/assets/queries/terraform/azure/azure_mysql_audit_log_events_connection_manual/test/positive2.tf new file mode 100644 index 00000000000..f9529c2d915 --- /dev/null +++ b/assets/queries/terraform/azure/azure_mysql_audit_log_events_connection_manual/test/positive2.tf @@ -0,0 +1,7 @@ +resource "azurerm_mysql_flexible_server" "fail_flexible" { + name = "mysql-flex-server-connection-fail" + resource_group_name = "rg-test" + location = "West Europe" + administrator_login = "mysqladmin" + administrator_password = "Password1234!" +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_mysql_audit_log_events_connection_manual/test/positive_expected_result.json b/assets/queries/terraform/azure/azure_mysql_audit_log_events_connection_manual/test/positive_expected_result.json new file mode 100644 index 00000000000..2b4dd87ee4a --- /dev/null +++ b/assets/queries/terraform/azure/azure_mysql_audit_log_events_connection_manual/test/positive_expected_result.json @@ -0,0 +1,14 @@ +[ + { + "queryName": "MySQL Audit Log Events Connection (Manual)", + "severity": "INFO", + "line": 1, + "fileName": "positive1.tf" + }, + { + "queryName": "MySQL Audit Log Events Connection (Manual)", + "severity": "INFO", + "line": 1, + "fileName": "positive2.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_netapp_account_cmk_encryption_disabled/README.md b/assets/queries/terraform/azure/azure_netapp_account_cmk_encryption_disabled/README.md new file mode 100644 index 00000000000..fbdc28464c5 --- /dev/null +++ b/assets/queries/terraform/azure/azure_netapp_account_cmk_encryption_disabled/README.md @@ -0,0 +1,65 @@ +# Regla KICS: NetApp Account CMK Encryption Disabled + +## Descripción General + +Esta regla de KICS verifica que las cuentas de **Azure NetApp Files** estén configuradas para utilizar claves gestionadas por el cliente (**Customer-Managed Keys - CMK**) para el cifrado de datos en reposo. + +Por defecto, Azure NetApp Files cifra los datos utilizando claves gestionadas por la plataforma Microsoft. Sin embargo, para cumplir con requisitos de cumplimiento normativo y soberanía de datos, se recomienda el uso de CMK. Esto permite a las organizaciones tener el control total sobre el ciclo de vida de las claves, incluyendo la rotación y la revocación de acceso, asegurando que los volúmenes de almacenamiento de alto rendimiento estén protegidos por claves bajo el control directo del cliente en Azure Key Vault. + +## Lógica de la Regla + +La política analiza la configuración de Terraform buscando dos métodos válidos de implementación de CMK: +1. **Configuración Inline:** Verifica si el recurso `azurerm_netapp_account` tiene un bloque `encryption` donde el atributo `key_source` sea explícitamente `Microsoft.KeyVault`. +2. **Recurso Independiente:** Verifica si existe un recurso `azurerm_netapp_account_encryption` vinculado al ID de la cuenta NetApp analizada. + +Si no se detecta ninguno de estos dos métodos, se genera una alerta indicando que la cuenta está operando con el cifrado predeterminado de la plataforma. + +## Caso de Fallo Detectado + +A continuación se describe el escenario que esta política detectará. + +--- + +### Caso Único: Configuración de Cifrado por Defecto (Platform Keys) + +* **Descripción:** Se define la cuenta de NetApp sin habilitar el uso de Key Vault para el cifrado, dejando los datos protegidos únicamente por las claves de Microsoft. +* **Ejemplo de Código Terraform Problemático:** + ```terraform + resource "azurerm_netapp_account" "fail" { + name = "insecure-netapp-account" + resource_group_name = azurerm_resource_group.example.name + location = azurerm_resource_group.example.location + + # Falta la configuración de CMK (inline o recurso separado) + } + ``` +* **Ubicación de la Alerta:** Sobre el recurso raíz `azurerm_netapp_account`. + +## Recursos Involucrados + +* `azurerm_netapp_account` +* `azurerm_netapp_account_encryption` + +## Solución + +Para solucionar este riesgo, asigne una identidad gestionada a la cuenta y utilice el recurso `azurerm_netapp_account_encryption` para vincular la clave del Key Vault. + +```terraform +resource "azurerm_netapp_account" "secure" { + name = "secure-netapp-account" + resource_group_name = azurerm_resource_group.example.name + location = azurerm_resource_group.example.location + + # 1. Asignar identidad + identity { + type = "UserAssigned" + identity_ids = [azurerm_user_assigned_identity.example.id] + } +} + +# 2. Configurar el cifrado CMK mediante el recurso dedicado +resource "azurerm_netapp_account_encryption" "secure_encryption" { + netapp_account_id = azurerm_netapp_account.secure.id + user_assigned_identity_id = azurerm_user_assigned_identity.example.id + encryption_key = azurerm_key_vault_key.example.versionless_id +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_netapp_account_cmk_encryption_disabled/metadata.json b/assets/queries/terraform/azure/azure_netapp_account_cmk_encryption_disabled/metadata.json new file mode 100644 index 00000000000..e98c7036089 --- /dev/null +++ b/assets/queries/terraform/azure/azure_netapp_account_cmk_encryption_disabled/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "8bed44b5-bcd6-42f4-b7b6-f59fd4edc94a", + "queryName": "NetApp Account CMK Encryption Disabled", + "severity": "MEDIUM", + "category": "Encryption", + "descriptionText": "Ensures that Azure NetApp Files accounts are configured to use Customer-Managed Keys (CMK) for encryption, instead of the default platform-managed keys.", + "descriptionUrl": "https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/netapp_account_encryption", + "platform": "Terraform", + "cloudProvider": "azure", + "cwe": "CWE-326", + "descriptionID": "8bed44b5", + "riskScore": 5.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_netapp_account_cmk_encryption_disabled/query.rego b/assets/queries/terraform/azure/azure_netapp_account_cmk_encryption_disabled/query.rego new file mode 100644 index 00000000000..aaef399b3a3 --- /dev/null +++ b/assets/queries/terraform/azure/azure_netapp_account_cmk_encryption_disabled/query.rego @@ -0,0 +1,36 @@ +package Cx + +has_encryption_resource(doc, account_id) { + enc := doc.resource.azurerm_netapp_account_encryption[_] + check_id(enc.netapp_account_id, account_id) +} + +check_id(current, target) { + current == target +} + +check_id(current, target) { + current == sprintf("${%s}", [target]) +} + +has_inline_encryption(account) { + account.encryption.key_source == "Microsoft.KeyVault" +} + +# REGLA 1: La cuenta NetApp utiliza Platform-Managed Keys (falta configuración CMK). +CxPolicy[result] { + doc := input.document[i] + account := doc.resource.azurerm_netapp_account[name] + account_id := sprintf("azurerm_netapp_account.%s.id", [name]) + + not has_inline_encryption(account) + not has_encryption_resource(doc, account_id) + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.azurerm_netapp_account.%s", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": sprintf("'azurerm_netapp_account.%s' should have 'encryption.key_source' set to 'Microsoft.KeyVault' or have an 'azurerm_netapp_account_encryption' resource associated", [name]), + "keyActualValue": sprintf("'azurerm_netapp_account.%s' is using Platform-Managed Keys (default)", [name]), + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_netapp_account_cmk_encryption_disabled/test/negative1.tf b/assets/queries/terraform/azure/azure_netapp_account_cmk_encryption_disabled/test/negative1.tf new file mode 100644 index 00000000000..610c7e965bf --- /dev/null +++ b/assets/queries/terraform/azure/azure_netapp_account_cmk_encryption_disabled/test/negative1.tf @@ -0,0 +1,16 @@ +resource "azurerm_netapp_account" "pass" { + name = "pass-netapp" + resource_group_name = "rg-example" + location = "West Europe" + + identity { + type = "UserAssigned" + identity_ids = ["/subscriptions/000/resourceGroups/rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/id"] + } +} + +resource "azurerm_netapp_account_encryption" "pass_encryption" { + netapp_account_id = azurerm_netapp_account.pass.id + user_assigned_identity_id = "/subscriptions/000/resourceGroups/rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/id" + encryption_key = "https://kv.vault.azure.net/keys/key/v1" +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_netapp_account_cmk_encryption_disabled/test/positive1.tf b/assets/queries/terraform/azure/azure_netapp_account_cmk_encryption_disabled/test/positive1.tf new file mode 100644 index 00000000000..76a7edb055f --- /dev/null +++ b/assets/queries/terraform/azure/azure_netapp_account_cmk_encryption_disabled/test/positive1.tf @@ -0,0 +1,5 @@ +resource "azurerm_netapp_account" "fail" { + name = "fail-netapp" + resource_group_name = "rg-example" + location = "West Europe" +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_netapp_account_cmk_encryption_disabled/test/positive_expected_result.json b/assets/queries/terraform/azure/azure_netapp_account_cmk_encryption_disabled/test/positive_expected_result.json new file mode 100644 index 00000000000..40945f981f6 --- /dev/null +++ b/assets/queries/terraform/azure/azure_netapp_account_cmk_encryption_disabled/test/positive_expected_result.json @@ -0,0 +1,8 @@ +[ + { + "queryName": "NetApp Account CMK Encryption Disabled", + "severity": "MEDIUM", + "line": 1, + "fileName": "positive1.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_paas_private_endpoint_missing/README.md b/assets/queries/terraform/azure/azure_paas_private_endpoint_missing/README.md new file mode 100644 index 00000000000..4c089125f9b --- /dev/null +++ b/assets/queries/terraform/azure/azure_paas_private_endpoint_missing/README.md @@ -0,0 +1,67 @@ +# Regla KICS: Azure PaaS Services Private Endpoint Disabled (Master) + +## Descripción General + +Esta es una regla consolidada (**Regla Maestra**) diseñada para asegurar que los servicios PaaS críticos de Azure estén protegidos mediante el uso de **Private Endpoints**. + +Los Private Endpoints (Puntos de Conexión Privados) son un componente esencial de la seguridad perimetral en Azure. Permiten que los servicios (como bases de datos, almacenamiento o bóvedas de claves) se integren directamente en una Red Virtual (VNet). Al asignar una dirección IP privada del espacio de la VNet al servicio, se elimina la necesidad de exponer dichos recursos a la internet pública, reduciendo drásticamente la superficie de ataque y previniendo la exfiltración de datos. + +## Lógica de la Regla + +La política analiza de forma exhaustiva los recursos definidos en Terraform buscando una vinculación válida: +1. **Recursos Objetivo:** La regla monitorea una lista extensa de servicios, incluyendo `azurerm_storage_account`, `azurerm_mssql_server`, `azurerm_cosmosdb_account`, `azurerm_key_vault` y otros servicios de datos y mensajería. +2. **Validación de Vínculo:** Para cada recurso detectado, busca la existencia de un recurso `azurerm_private_endpoint` que lo referencia a través del atributo `private_connection_resource_id`. +3. **Resultado:** Si el recurso PaaS existe pero no hay un endpoint privado asociado en el mismo contexto de código, se genera una alerta. + +## Limitaciones del Análisis Estático + +Es importante notar que esta regla valida la configuración **dentro del mismo archivo o estado de Terraform**. Debido a la naturaleza del análisis estático: +* No puede validar conexiones si el recurso se crea en una suscripción distinta o se gestiona en un estado de Terraform separado. +* Podría generar falsos positivos si el endpoint se define mediante módulos externos cuyas variables no son resueltas por el motor de escaneo. + +## Caso de Fallo Detectado + +A continuación se describe el escenario principal que esta política detectará. + +--- + +### Caso Único: Recurso PaaS Aislado (Sin Endpoint Privado) + +* **Descripción:** Se ha definido un servicio crítico (ej. Azure SQL o Storage Account) pero no se ha aprovisionado el endpoint privado que garantice que el tráfico sea interno a la VNet. +* **Ejemplo de Código Terraform Problemático:** + ```terraform + resource "azurerm_storage_account" "example_insecure" { + name = "stinsecuredata" + resource_group_name = azurerm_resource_group.example.name + location = azurerm_resource_group.example.location + account_tier = "Standard" + account_replication_type = "LRS" + + # El recurso existe pero carece de un azurerm_private_endpoint vinculado + } + ``` +* **Ubicación de la Alerta:** Sobre el recurso PaaS identificado (Storage, SQL, Key Vault, etc.). + +## Recursos Involucrados + +* `azurerm_private_endpoint` +* Servicios PaaS (Storage, SQL, Cosmos, KeyVault, ACR, ServiceBus, etc.) + +## Solución + +Para solucionar este hallazgo, debe crear un recurso `azurerm_private_endpoint` y vincularlo al ID del recurso PaaS mediante el bloque `private_service_connection`. + +```terraform +resource "azurerm_private_endpoint" "example_secure" { + name = "pe-storage" + location = azurerm_resource_group.example.location + resource_group_name = azurerm_resource_group.example.name + subnet_id = azurerm_subnet.example.id + + private_service_connection { + name = "psc-storage" + private_connection_resource_id = azurerm_storage_account.example_insecure.id + is_manual_connection = false + subresource_names = ["blob"] + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_paas_private_endpoint_missing/metadata.json b/assets/queries/terraform/azure/azure_paas_private_endpoint_missing/metadata.json new file mode 100644 index 00000000000..a17522eb843 --- /dev/null +++ b/assets/queries/terraform/azure/azure_paas_private_endpoint_missing/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "dda11a20-c7c2-43f8-bd52-a4c029ad15e4", + "queryName": "Ensure Private Endpoints Are Used Where Possible", + "severity": "MEDIUM", + "category": "Networking and Firewall", + "descriptionText": "Ensures that key Azure PaaS services (Cosmos DB, Storage, SQL, KeyVault, ACR, etc.) are accessed via Private Endpoints. While static analysis cannot resolve IDs across different state files, this rule validates local resource linkages.", + "descriptionUrl": "https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/private_endpoint", + "platform": "Terraform", + "cloudProvider": "azure", + "cwe": "CWE-200", + "descriptionID": "dda11a20", + "riskScore": 5.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_paas_private_endpoint_missing/query.rego b/assets/queries/terraform/azure/azure_paas_private_endpoint_missing/query.rego new file mode 100644 index 00000000000..fc1b7a752a0 --- /dev/null +++ b/assets/queries/terraform/azure/azure_paas_private_endpoint_missing/query.rego @@ -0,0 +1,54 @@ +package Cx + +targets := { + "azurerm_cosmosdb_account", + "azurerm_storage_account", + "azurerm_mssql_server", + "azurerm_key_vault", + "azurerm_container_registry", + "azurerm_servicebus_namespace", + "azurerm_mariadb_server", + "azurerm_postgresql_server", + "azurerm_mysql_server", + "azurerm_redis_cache", + "azurerm_eventhub_namespace", + "azurerm_automation_account", + "azurerm_data_factory", + "azurerm_synapse_workspace", + "azurerm_search_service" +} + +# REGLA MAESTRA: Verifica si los recursos de la lista targets tienen un Private Endpoint vinculado. +CxPolicy[result] { + doc := input.document[i] + + resource_type := targets[t] + resource_instances := doc.resource[resource_type] + resource_instance := resource_instances[name] + + target_id := sprintf("%s.%s.id", [resource_type, name]) + + private_endpoints := [pe | + pe := doc.resource.azurerm_private_endpoint[_] + conn := pe.private_service_connection + check_resource_id(conn.private_connection_resource_id, target_id) + ] + + count(private_endpoints) == 0 + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.%s.%s", [resource_type, name]), + "issueType": "MissingAttribute", + "keyExpectedValue": sprintf("'%s.%s' should be linked to an 'azurerm_private_endpoint'", [resource_type, name]), + "keyActualValue": sprintf("'%s.%s' is not linked to any 'azurerm_private_endpoint' in this file", [resource_type, name]), + } +} + +check_resource_id(current, target) { + current == target +} + +check_resource_id(current, target) { + current == sprintf("${%s}", [target]) +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_paas_private_endpoint_missing/test/negative1.tf b/assets/queries/terraform/azure/azure_paas_private_endpoint_missing/test/negative1.tf new file mode 100644 index 00000000000..cfd1a9fb026 --- /dev/null +++ b/assets/queries/terraform/azure/azure_paas_private_endpoint_missing/test/negative1.tf @@ -0,0 +1,22 @@ +resource "azurerm_mssql_server" "pass_sql" { + name = "sqlpassserver" + resource_group_name = "rg" + location = "West Europe" + version = "12.0" + administrator_login = "admin" + administrator_login_password = "Password123!" +} + +resource "azurerm_private_endpoint" "pass_pe" { + name = "pe-sql" + location = "West Europe" + resource_group_name = "rg" + subnet_id = "subnet-id" + + private_service_connection { + name = "psc-sql" + private_connection_resource_id = azurerm_mssql_server.pass_sql.id + is_manual_connection = false + subresource_names = ["sqlServer"] + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_paas_private_endpoint_missing/test/positive1.tf b/assets/queries/terraform/azure/azure_paas_private_endpoint_missing/test/positive1.tf new file mode 100644 index 00000000000..0d675c14d24 --- /dev/null +++ b/assets/queries/terraform/azure/azure_paas_private_endpoint_missing/test/positive1.tf @@ -0,0 +1,7 @@ +resource "azurerm_storage_account" "fail_storage" { + name = "storagefail" + resource_group_name = "rg" + location = "West Europe" + account_tier = "Standard" + account_replication_type = "LRS" +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_paas_private_endpoint_missing/test/positive2.tf b/assets/queries/terraform/azure/azure_paas_private_endpoint_missing/test/positive2.tf new file mode 100644 index 00000000000..f65468b751e --- /dev/null +++ b/assets/queries/terraform/azure/azure_paas_private_endpoint_missing/test/positive2.tf @@ -0,0 +1,7 @@ +resource "azurerm_key_vault" "fail_kv" { + name = "kvfail" + location = "West Europe" + resource_group_name = "rg" + tenant_id = "00000000-0000-0000-0000-000000000000" + sku_name = "standard" +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_paas_private_endpoint_missing/test/positive_expected_result.json b/assets/queries/terraform/azure/azure_paas_private_endpoint_missing/test/positive_expected_result.json new file mode 100644 index 00000000000..e92aa602481 --- /dev/null +++ b/assets/queries/terraform/azure/azure_paas_private_endpoint_missing/test/positive_expected_result.json @@ -0,0 +1,14 @@ +[ + { + "queryName": "Ensure Private Endpoints Are Used Where Possible", + "severity": "MEDIUM", + "line": 1, + "fileName": "positive1.tf" + }, + { + "queryName": "Ensure Private Endpoints Are Used Where Possible", + "severity": "MEDIUM", + "line": 1, + "fileName": "positive2.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_production_workload_basic_consumption_sku/README.md b/assets/queries/terraform/azure/azure_production_workload_basic_consumption_sku/README.md new file mode 100644 index 00000000000..f5ae5d05d63 --- /dev/null +++ b/assets/queries/terraform/azure/azure_production_workload_basic_consumption_sku/README.md @@ -0,0 +1,69 @@ +# Regla KICS: Production Workload using Basic or Consumption SKU + +## Descripción General + +Esta regla identifica recursos de Azure configurados con niveles de precios (SKUs) de tipo **Basic**, **Free** o **Consumption**. + +Aunque estos niveles son ideales para entornos de desarrollo, aprendizaje o pruebas de concepto (PoC), carecen de características fundamentales necesarias para entornos de producción. El uso de estos SKUs en producción compromete la fiabilidad del servicio debido a la ausencia de Acuerdos de Nivel de Servicio (SLA) garantizados, falta de soporte para integración con redes virtuales (VNet), ausencia de slots de implementación y latencias imprevistas ("cold starts") en modelos de consumo serverless. + +## Lógica de la Regla + +La política audita los siguientes recursos en la configuración de Terraform: +1. **Service Plans (`azurerm_service_plan`):** Alerta si el atributo `sku_name` se establece en niveles no productivos como B1-B3, F1, FREE o Y1. +2. **API Management (`azurerm_api_management`):** Alerta si el atributo `sku_name` coincide con patrones de tipo "Basic" o "Consumption". + +## Casos de Fallo Detectados + +A continuación se describen los escenarios que esta política detectará. + +--- + +### Caso 1: Service Plan en Nivel No-Productivo + +* **Descripción:** Se detecta un plan de App Service configurado en nivel Basic o Free, lo cual limita la disponibilidad y las capacidades de red del servicio. +* **Ejemplo de Código Terraform Problemático:** + ```terraform + resource "azurerm_service_plan" "fail_plan" { + name = "example-basic-plan" + resource_group_name = "rg-production" + location = "West Europe" + os_type = "Linux" + sku_name = "B1" # <-- FALLO: Nivel Basic + } + ``` +* **Ubicación de la Alerta:** Atributo `sku_name`. + +--- + +### Caso 2: API Management en Modo Consumo + +* **Descripción:** Se detecta una instancia de APIM en nivel Consumption (Serverless), lo que puede afectar al rendimiento y carece de aislamiento de red. +* **Ejemplo de Código Terraform Problemático:** + ```terraform + resource "azurerm_api_management" "fail_apim" { + name = "example-apim" + sku_name = "Consumption_0" # <-- FALLO: Nivel Consumption + # ... resto de la configuración ... + } + ``` +* **Ubicación de la Alerta:** Atributo `sku_name`. + +## Recurso Involucrado + +* `azurerm_service_plan` +* `azurerm_api_management` + +## Solución + +Actualice el SKU del recurso a un nivel orientado a producción, como **Standard (S)** o **Premium (P)**, para garantizar el SLA y las funciones de red necesarias. + +```terraform +resource "azurerm_service_plan" "secure_production" { + name = "prod-service-plan" + resource_group_name = "rg-production" + location = "West Europe" + os_type = "Linux" + + # SOLUCIÓN: Usar un nivel Standard o superior + sku_name = "S1" +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_production_workload_basic_consumption_sku/metadata.json b/assets/queries/terraform/azure/azure_production_workload_basic_consumption_sku/metadata.json new file mode 100644 index 00000000000..4ff2bbda593 --- /dev/null +++ b/assets/queries/terraform/azure/azure_production_workload_basic_consumption_sku/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "bd3a6fc3-fadb-472b-b06c-54c7d7645f15", + "queryName": "Production Workload using Basic or Consumption SKU", + "severity": "LOW", + "category": "Resource Management", + "descriptionText": "Detects the use of Basic, Free, or Consumption SKUs in Azure resources. These SKUs are often unsuitable for production workloads due to lack of SLAs, VNet integration support, or 'cold start' issues.", + "descriptionUrl": "https://azure.microsoft.com/en-us/pricing/details/app-service/linux/", + "platform": "Terraform", + "cloudProvider": "azure", + "cwe": "CWE-1038", + "descriptionID": "bd3a6fc3", + "riskScore": 2.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_production_workload_basic_consumption_sku/query.rego b/assets/queries/terraform/azure/azure_production_workload_basic_consumption_sku/query.rego new file mode 100644 index 00000000000..4433742de6c --- /dev/null +++ b/assets/queries/terraform/azure/azure_production_workload_basic_consumption_sku/query.rego @@ -0,0 +1,34 @@ +package Cx + +# REGLA 1: Azure Service Plan usando SKU Basic (B), Free (F) o Consumption (Y1). +CxPolicy[result] { + doc := input.document[i] + plan := doc.resource.azurerm_service_plan[name] + + invalid_skus := ["B1", "B2", "B3", "F1", "FREE", "Y1"] + plan.sku_name == invalid_skus[_] + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.azurerm_service_plan.%s.sku_name", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": sprintf("'%s.sku_name' should be Standard (S), Premium (P) or Isolated (I) for production", [name]), + "keyActualValue": sprintf("'%s.sku_name' is set to '%s' (Basic/Free/Consumption)", [name, plan.sku_name]), + } +} + +# REGLA 2: Azure API Management usando SKU Basic o Consumption. +CxPolicy[result] { + doc := input.document[i] + apim := doc.resource.azurerm_api_management[name] + + regex.match("(Basic|Consumption).*", apim.sku_name) + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.azurerm_api_management.%s.sku_name", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": sprintf("'%s.sku_name' should be Standard or Premium for production features", [name]), + "keyActualValue": sprintf("'%s.sku_name' is set to '%s'", [name, apim.sku_name]), + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_production_workload_basic_consumption_sku/test/negative1.tf b/assets/queries/terraform/azure/azure_production_workload_basic_consumption_sku/test/negative1.tf new file mode 100644 index 00000000000..78a4362db6a --- /dev/null +++ b/assets/queries/terraform/azure/azure_production_workload_basic_consumption_sku/test/negative1.tf @@ -0,0 +1,7 @@ +resource "azurerm_service_plan" "pass_sp" { + name = "pass-sp" + resource_group_name = "rg" + location = "West Europe" + os_type = "Linux" + sku_name = "S1" +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_production_workload_basic_consumption_sku/test/negative2.tf b/assets/queries/terraform/azure/azure_production_workload_basic_consumption_sku/test/negative2.tf new file mode 100644 index 00000000000..af8313b8440 --- /dev/null +++ b/assets/queries/terraform/azure/azure_production_workload_basic_consumption_sku/test/negative2.tf @@ -0,0 +1,8 @@ +resource "azurerm_api_management" "pass_apim" { + name = "pass-apim" + location = "West Europe" + resource_group_name = "rg" + publisher_name = "Company" + publisher_email = "email@test.com" + sku_name = "Standard_1" +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_production_workload_basic_consumption_sku/test/positive1.tf b/assets/queries/terraform/azure/azure_production_workload_basic_consumption_sku/test/positive1.tf new file mode 100644 index 00000000000..97884022dc3 --- /dev/null +++ b/assets/queries/terraform/azure/azure_production_workload_basic_consumption_sku/test/positive1.tf @@ -0,0 +1,7 @@ +resource "azurerm_service_plan" "fail_sp" { + name = "fail-sp" + resource_group_name = "rg" + location = "West Europe" + os_type = "Linux" + sku_name = "B1" +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_production_workload_basic_consumption_sku/test/positive2.tf b/assets/queries/terraform/azure/azure_production_workload_basic_consumption_sku/test/positive2.tf new file mode 100644 index 00000000000..fe09c001e18 --- /dev/null +++ b/assets/queries/terraform/azure/azure_production_workload_basic_consumption_sku/test/positive2.tf @@ -0,0 +1,8 @@ +resource "azurerm_api_management" "fail_apim" { + name = "fail-apim" + location = "West Europe" + resource_group_name = "rg" + publisher_name = "Company" + publisher_email = "email@test.com" + sku_name = "Consumption_0" +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_production_workload_basic_consumption_sku/test/positive_expected_result.json b/assets/queries/terraform/azure/azure_production_workload_basic_consumption_sku/test/positive_expected_result.json new file mode 100644 index 00000000000..0e52a4b0071 --- /dev/null +++ b/assets/queries/terraform/azure/azure_production_workload_basic_consumption_sku/test/positive_expected_result.json @@ -0,0 +1,14 @@ +[ + { + "queryName": "Production Workload using Basic or Consumption SKU", + "severity": "LOW", + "line": 6, + "fileName": "positive1.tf" + }, + { + "queryName": "Production Workload using Basic or Consumption SKU", + "severity": "LOW", + "line": 7, + "fileName": "positive2.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_recovery_services_vault_cmk_encryption_disabled/README.md b/assets/queries/terraform/azure/azure_recovery_services_vault_cmk_encryption_disabled/README.md new file mode 100644 index 00000000000..e4cf7cd3af5 --- /dev/null +++ b/assets/queries/terraform/azure/azure_recovery_services_vault_cmk_encryption_disabled/README.md @@ -0,0 +1,61 @@ +# Regla KICS: Recovery Services Vault CMK Encryption Disabled + +## Descripción General + +Esta regla de KICS verifica que los almacenes de **Recovery Services Vault** (utilizados para Azure Backup y Azure Site Recovery) estén configurados para utilizar claves gestionadas por el cliente (**Customer-Managed Keys - CMK**) para el cifrado de datos en reposo. + +Por defecto, los datos respaldados se cifran mediante claves gestionadas por la plataforma de Microsoft. Sin embargo, para cumplir con requisitos de cumplimiento normativo y garantizar la soberanía de los datos, las organizaciones deben utilizar sus propias claves almacenadas en Azure Key Vault. Esto permite un control total sobre la rotación de claves, las políticas de acceso y la capacidad de revocar el acceso a los datos de respaldo en caso de necesidad. + +## Lógica de la Regla + +La política analiza el recurso `azurerm_recovery_services_vault` validando la siguiente condición técnica: +1. **Presencia del Bloque de Cifrado:** Se comprueba la existencia del bloque `encryption`. La ausencia de este bloque implica que el almacén utiliza el cifrado predeterminado gestionado por la plataforma, lo cual no cumple con los requisitos de CMK. + +## Caso de Fallo Detectado + +A continuación se describe el escenario que esta política detectará. + +--- + +### Caso Único: Uso de Claves de la Plataforma (Cifrado por Defecto) + +* **Descripción:** Se define el Recovery Services Vault sin el bloque de configuración de cifrado por cliente, delegando la protección de los datos a las claves automáticas de Azure. +* **Ejemplo de Código Terraform Problemático:** + ```terraform + resource "azurerm_recovery_services_vault" "fail_vault" { + name = "insecure-vault" + location = "West Europe" + resource_group_name = "rg-production" + sku = "Standard" + + # El recurso carece del bloque encryption {} + } + ``` +* **Ubicación de la Alerta:** Sobre el recurso raíz `azurerm_recovery_services_vault`. + +## Recurso Involucrado + +* `azurerm_recovery_services_vault` + +## Solución + +Para solucionar este hallazgo, debe habilitar una identidad gestionada en el Vault y configurar el bloque `encryption` proporcionando obligatoriamente el `key_id` de Azure Key Vault. + +```terraform +resource "azurerm_recovery_services_vault" "secure_vault" { + name = "secure-recovery-vault" + location = azurerm_resource_group.example.location + resource_group_name = azurerm_resource_group.example.name + sku = "Standard" + + # 1. Habilitar identidad para acceso al Key Vault + identity { + type = "SystemAssigned" + } + + # 2. Configurar el cifrado CMK (key_id es obligatorio en este bloque) + encryption { + key_id = azurerm_key_vault_key.example.id + use_system_assigned_identity = true + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_recovery_services_vault_cmk_encryption_disabled/metadata.json b/assets/queries/terraform/azure/azure_recovery_services_vault_cmk_encryption_disabled/metadata.json new file mode 100644 index 00000000000..588fbca70b7 --- /dev/null +++ b/assets/queries/terraform/azure/azure_recovery_services_vault_cmk_encryption_disabled/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "9436d439-99f6-4f81-bef2-e7f50d24d453", + "queryName": "Recovery Services Vault CMK Encryption Disabled", + "severity": "MEDIUM", + "category": "Encryption", + "descriptionText": "Ensures that Azure Recovery Services Vaults are encrypted using Customer-Managed Keys (CMK) stored in Azure Key Vault. Using CMK provides control over key lifecycle and access policies, unlike the default platform-managed keys.", + "descriptionUrl": "https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/recovery_services_vault#encryption", + "platform": "Terraform", + "cloudProvider": "azure", + "cwe": "CWE-326", + "descriptionID": "9436d439", + "riskScore": 5.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_recovery_services_vault_cmk_encryption_disabled/query.rego b/assets/queries/terraform/azure/azure_recovery_services_vault_cmk_encryption_disabled/query.rego new file mode 100644 index 00000000000..3926d795315 --- /dev/null +++ b/assets/queries/terraform/azure/azure_recovery_services_vault_cmk_encryption_disabled/query.rego @@ -0,0 +1,17 @@ +package Cx + +# REGLA 1: El bloque de encriptación no está definido. +CxPolicy[result] { + doc := input.document[i] + vault := doc.resource.azurerm_recovery_services_vault[name] + + not vault.encryption + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.azurerm_recovery_services_vault.%s", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": sprintf("'azurerm_recovery_services_vault.%s' should have an 'encryption' block defined with a valid 'key_id'", [name]), + "keyActualValue": sprintf("'azurerm_recovery_services_vault.%s' is missing the 'encryption' block (Platform-Managed Keys by default)", [name]), + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_recovery_services_vault_cmk_encryption_disabled/test/negative1.tf b/assets/queries/terraform/azure/azure_recovery_services_vault_cmk_encryption_disabled/test/negative1.tf new file mode 100644 index 00000000000..3469d90f314 --- /dev/null +++ b/assets/queries/terraform/azure/azure_recovery_services_vault_cmk_encryption_disabled/test/negative1.tf @@ -0,0 +1,16 @@ +resource "azurerm_recovery_services_vault" "pass" { + name = "vault-pass" + location = "West Europe" + resource_group_name = "rg-test" + sku = "Standard" + + identity { + type = "SystemAssigned" + } + + encryption { + key_id = "https://kv.vault.azure.net/keys/key/v1" + infrastructure_encryption_enabled = true + use_system_assigned_identity = true + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_recovery_services_vault_cmk_encryption_disabled/test/positive1.tf b/assets/queries/terraform/azure/azure_recovery_services_vault_cmk_encryption_disabled/test/positive1.tf new file mode 100644 index 00000000000..8f2bb000720 --- /dev/null +++ b/assets/queries/terraform/azure/azure_recovery_services_vault_cmk_encryption_disabled/test/positive1.tf @@ -0,0 +1,7 @@ +resource "azurerm_recovery_services_vault" "fail" { + name = "vault-fail" + location = "West Europe" + resource_group_name = "rg-test" + sku = "Standard" + # Falla por ausencia de bloque encryption +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_recovery_services_vault_cmk_encryption_disabled/test/positive_expected_result.json b/assets/queries/terraform/azure/azure_recovery_services_vault_cmk_encryption_disabled/test/positive_expected_result.json new file mode 100644 index 00000000000..ff3d72812a2 --- /dev/null +++ b/assets/queries/terraform/azure/azure_recovery_services_vault_cmk_encryption_disabled/test/positive_expected_result.json @@ -0,0 +1,8 @@ +[ + { + "queryName": "Recovery Services Vault CMK Encryption Disabled", + "severity": "MEDIUM", + "line": 1, + "fileName": "positive1.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_recovery_services_vault_cross_region_restore_disabled/README.md b/assets/queries/terraform/azure/azure_recovery_services_vault_cross_region_restore_disabled/README.md new file mode 100644 index 00000000000..59e612fbeec --- /dev/null +++ b/assets/queries/terraform/azure/azure_recovery_services_vault_cross_region_restore_disabled/README.md @@ -0,0 +1,72 @@ +# Regla KICS: Recovery Services Vault Cross Region Restore Disabled + +## Descripción General + +Esta regla de KICS verifica que la funcionalidad de **Restauración entre Regiones** (`cross_region_restore_enabled`) esté habilitada en los almacenes de **Recovery Services Vault**. + +Habilitar la Restauración entre Regiones (CRR) es un componente crítico de una estrategia de continuidad de negocio y recuperación ante desastres (DRP). Esta característica permite realizar restauraciones de datos en una región secundaria emparejada de Azure en cualquier momento, garantizando el acceso a los backups incluso si la región principal sufre una interrupción total o desastre regional. + +**Nota técnica:** Para que la restauración entre regiones sea efectiva, el almacén debe tener configurado el tipo de almacenamiento como `GeoRedundant`. + +## Lógica de la Regla + +La política analiza el recurso `azurerm_recovery_services_vault` evaluando dos condiciones: +1. **Atributo Ausente:** Si no se define explícitamente `cross_region_restore_enabled`, se considera no conforme, ya que la configuración por defecto no garantiza la disponibilidad del dato en la región secundaria. +2. **Configuración Incorrecta:** Si el atributo se establece explícitamente como `false`, se genera una alerta indicando la falta de redundancia operativa para restauraciones. + +## Casos de Fallo Detectados + +A continuación se describen los escenarios que esta política detectará. + +--- + +### Caso 1: Configuración de CRR Ausente + +* **Descripción:** El almacén se define sin especificar la política de restauración regional, lo que resulta en la desactivación por defecto de esta medida de resiliencia. +* **Ejemplo de Código Terraform Problemático:** + ```terraform + resource "azurerm_recovery_services_vault" "fail_missing" { + name = "vault-insecure" + location = "West Europe" + resource_group_name = azurerm_resource_group.example.name + sku = "Standard" + storage_mode_type = "GeoRedundant" + # Falta cross_region_restore_enabled = true + } + ``` +* **Ubicación de la Alerta:** Sobre el recurso raíz `azurerm_recovery_services_vault`. + +--- + +### Caso 2: CRR Deshabilitado Explícitamente + +* **Descripción:** Se ha configurado el atributo `cross_region_restore_enabled` con el valor `false`, impidiendo la recuperación de desastres en regiones emparejadas. +* **Ejemplo de Código Terraform Problemático:** + ```terraform + resource "azurerm_recovery_services_vault" "fail_false" { + name = "vault-disabled" + storage_mode_type = "GeoRedundant" + cross_region_restore_enabled = false # <-- Problema detectado + } + ``` +* **Ubicación de la Alerta:** Atributo `cross_region_restore_enabled`. + +## Recurso Involucrado + +* `azurerm_recovery_services_vault` + +## Solución + +Establezca `cross_region_restore_enabled` en `true` y asegúrese de que el `storage_mode_type` sea `GeoRedundant`. + +```terraform +resource "azurerm_recovery_services_vault" "secure_vault" { + name = "vault-resilient" + resource_group_name = azurerm_resource_group.example.name + location = azurerm_resource_group.example.location + sku = "Standard" + storage_mode_type = "GeoRedundant" + + # SOLUCIÓN: Habilitar restauración entre regiones + cross_region_restore_enabled = true +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_recovery_services_vault_cross_region_restore_disabled/metadata.json b/assets/queries/terraform/azure/azure_recovery_services_vault_cross_region_restore_disabled/metadata.json new file mode 100644 index 00000000000..5714b398bc0 --- /dev/null +++ b/assets/queries/terraform/azure/azure_recovery_services_vault_cross_region_restore_disabled/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "b7b0bc16-aedc-405e-a27e-cf159c200f1e", + "queryName": "Recovery Services Vault Cross Region Restore Disabled", + "severity": "MEDIUM", + "category": "Backup", + "descriptionText": "Ensures that 'Cross Region Restore' is enabled for Azure Recovery Services Vaults. This feature allows you to restore data in the secondary region, which is essential for business continuity during a primary region disaster.", + "descriptionUrl": "https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/recovery_services_vault#cross_region_restore_enabled", + "platform": "Terraform", + "cloudProvider": "azure", + "cwe": "CWE-668", + "descriptionID": "b7b0bc16", + "riskScore": 5.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_recovery_services_vault_cross_region_restore_disabled/query.rego b/assets/queries/terraform/azure/azure_recovery_services_vault_cross_region_restore_disabled/query.rego new file mode 100644 index 00000000000..bb16333f81d --- /dev/null +++ b/assets/queries/terraform/azure/azure_recovery_services_vault_cross_region_restore_disabled/query.rego @@ -0,0 +1,33 @@ +package Cx + +# REGLA 1: Atributo 'cross_region_restore_enabled' ausente. +CxPolicy[result] { + doc := input.document[i] + vault := doc.resource.azurerm_recovery_services_vault[name] + + object.get(vault, "cross_region_restore_enabled", "undefined") == "undefined" + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.azurerm_recovery_services_vault.%s", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": sprintf("'azurerm_recovery_services_vault.%s' should have 'cross_region_restore_enabled' set to true", [name]), + "keyActualValue": sprintf("'azurerm_recovery_services_vault.%s' is missing 'cross_region_restore_enabled'", [name]), + } +} + +# REGLA 2: Atributo 'cross_region_restore_enabled' establecido en false. +CxPolicy[result] { + doc := input.document[i] + vault := doc.resource.azurerm_recovery_services_vault[name] + + vault.cross_region_restore_enabled == false + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.azurerm_recovery_services_vault.%s.cross_region_restore_enabled", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "'cross_region_restore_enabled' should be set to true", + "keyActualValue": "'cross_region_restore_enabled' is set to false", + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_recovery_services_vault_cross_region_restore_disabled/test/negative1.tf b/assets/queries/terraform/azure/azure_recovery_services_vault_cross_region_restore_disabled/test/negative1.tf new file mode 100644 index 00000000000..93b14ef30fd --- /dev/null +++ b/assets/queries/terraform/azure/azure_recovery_services_vault_cross_region_restore_disabled/test/negative1.tf @@ -0,0 +1,8 @@ +resource "azurerm_recovery_services_vault" "pass" { + name = "pass-vault" + location = "West Europe" + resource_group_name = "rg-test" + sku = "Standard" + storage_mode_type = "GeoRedundant" + cross_region_restore_enabled = true +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_recovery_services_vault_cross_region_restore_disabled/test/positive1.tf b/assets/queries/terraform/azure/azure_recovery_services_vault_cross_region_restore_disabled/test/positive1.tf new file mode 100644 index 00000000000..dc1ac3cdc8c --- /dev/null +++ b/assets/queries/terraform/azure/azure_recovery_services_vault_cross_region_restore_disabled/test/positive1.tf @@ -0,0 +1,7 @@ +resource "azurerm_recovery_services_vault" "fail_omission" { + name = "fail-vault-omission" + location = "West Europe" + resource_group_name = "rg-test" + sku = "Standard" + storage_mode_type = "GeoRedundant" +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_recovery_services_vault_cross_region_restore_disabled/test/positive2.tf b/assets/queries/terraform/azure/azure_recovery_services_vault_cross_region_restore_disabled/test/positive2.tf new file mode 100644 index 00000000000..21220fbc5f4 --- /dev/null +++ b/assets/queries/terraform/azure/azure_recovery_services_vault_cross_region_restore_disabled/test/positive2.tf @@ -0,0 +1,8 @@ +resource "azurerm_recovery_services_vault" "fail_explicit" { + name = "fail-vault-explicit" + location = "West Europe" + resource_group_name = "rg-test" + sku = "Standard" + storage_mode_type = "GeoRedundant" + cross_region_restore_enabled = false +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_recovery_services_vault_cross_region_restore_disabled/test/positive_expected_result.json b/assets/queries/terraform/azure/azure_recovery_services_vault_cross_region_restore_disabled/test/positive_expected_result.json new file mode 100644 index 00000000000..ad96799ed4d --- /dev/null +++ b/assets/queries/terraform/azure/azure_recovery_services_vault_cross_region_restore_disabled/test/positive_expected_result.json @@ -0,0 +1,14 @@ +[ + { + "queryName": "Recovery Services Vault Cross Region Restore Disabled", + "severity": "MEDIUM", + "line": 1, + "fileName": "positive1.tf" + }, + { + "queryName": "Recovery Services Vault Cross Region Restore Disabled", + "severity": "MEDIUM", + "line": 7, + "fileName": "positive2.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_recovery_services_vault_infrastructure_encryption_disabled/README.md b/assets/queries/terraform/azure/azure_recovery_services_vault_infrastructure_encryption_disabled/README.md new file mode 100644 index 00000000000..bed1a30d845 --- /dev/null +++ b/assets/queries/terraform/azure/azure_recovery_services_vault_infrastructure_encryption_disabled/README.md @@ -0,0 +1,79 @@ +# Regla KICS: Recovery Services Vault Infrastructure Encryption Disabled + +## Descripción General + +Esta regla de KICS verifica que el **Cifrado de Infraestructura** (`infrastructure_encryption_enabled`) esté habilitado en los almacenes de **Recovery Services Vault**. + +El cifrado de infraestructura proporciona una capa adicional de protección mediante el uso de un segundo algoritmo de cifrado (**Double Encryption**). Mientras que todos los datos en Azure ya están cifrados en reposo, habilitar esta opción asegura que los datos se cifren dos veces utilizando dos algoritmos independientes. Esta configuración es fundamental para organizaciones con requisitos de cumplimiento altamente estrictos que buscan mitigar riesgos ante posibles vulnerabilidades en un único estándar criptográfico. + +## Lógica de la Regla + +La política analiza el recurso `azurerm_recovery_services_vault` validando dos aspectos técnicos: +1. **Bloque de Cifrado:** Verifica la existencia del bloque `encryption`. Si no existe, se asume que no hay doble cifrado. +2. **Cifrado de Infraestructura:** Verifica que el atributo `infrastructure_encryption_enabled` esté explícitamente establecido en `true`. + +## Casos de Fallo Detectados + +A continuación se describen los escenarios que esta política detectará. + +--- + +### Caso 1: Bloque de Cifrado Ausente + +* **Descripción:** El almacén no tiene definido el bloque `encryption`, por lo que utiliza únicamente el cifrado predeterminado de Azure sin capas adicionales. +* **Ejemplo de Código Terraform Problemático:** + ```terraform + resource "azurerm_recovery_services_vault" "fail_missing" { + name = "insecure-vault" + resource_group_name = "rg-prod" + location = "West Europe" + sku = "Standard" + # Falta bloque encryption {} + } + ``` +* **Ubicación de la Alerta:** Sobre el recurso raíz `azurerm_recovery_services_vault`. + +--- + +### Caso 2: Cifrado de Infraestructura Deshabilitado + +* **Descripción:** El bloque `encryption` existe pero el atributo `infrastructure_encryption_enabled` no está configurado o se encuentra en `false`. +* **Ejemplo de Código Terraform Problemático:** + ```terraform + resource "azurerm_recovery_services_vault" "fail_disabled" { + name = "vault-vulnerable" + # ... + encryption { + key_id = azurerm_key_vault_key.example.id + infrastructure_encryption_enabled = false + use_system_assigned_identity = true + } + } + ``` +* **Ubicación de la Alerta:** Atributo `infrastructure_encryption_enabled`. + +## Recurso Involucrado + +* `azurerm_recovery_services_vault` + +## Solución + +Para mitigar este riesgo, asegúrese de declarar el bloque `encryption` estableciendo `infrastructure_encryption_enabled` en `true`. + +```terraform +resource "azurerm_recovery_services_vault" "secure_vault" { + name = "secure-recovery-vault" + resource_group_name = azurerm_resource_group.example.name + location = azurerm_resource_group.example.location + sku = "Standard" + + identity { + type = "SystemAssigned" + } + + encryption { + key_id = azurerm_key_vault_key.example.id + infrastructure_encryption_enabled = true + use_system_assigned_identity = true + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_recovery_services_vault_infrastructure_encryption_disabled/metadata.json b/assets/queries/terraform/azure/azure_recovery_services_vault_infrastructure_encryption_disabled/metadata.json new file mode 100644 index 00000000000..44dd19a744d --- /dev/null +++ b/assets/queries/terraform/azure/azure_recovery_services_vault_infrastructure_encryption_disabled/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "95e32d9c-77b5-423a-8746-35ab59a85148", + "queryName": "Recovery Services Vault Infrastructure Encryption Disabled", + "severity": "MEDIUM", + "category": "Encryption", + "descriptionText": "Ensures that 'Infrastructure Encryption' (Double Encryption) is enabled for Azure Recovery Services Vaults. This provides a second layer of encryption for data at rest using platform-managed keys.", + "descriptionUrl": "https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/recovery_services_vault#infrastructure_encryption_enabled", + "platform": "Terraform", + "cloudProvider": "azure", + "cwe": "CWE-312", + "descriptionID": "95e32d9c", + "riskScore": 5.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_recovery_services_vault_infrastructure_encryption_disabled/query.rego b/assets/queries/terraform/azure/azure_recovery_services_vault_infrastructure_encryption_disabled/query.rego new file mode 100644 index 00000000000..bbdfc81beaf --- /dev/null +++ b/assets/queries/terraform/azure/azure_recovery_services_vault_infrastructure_encryption_disabled/query.rego @@ -0,0 +1,34 @@ +package Cx + +# REGLA 1: El bloque 'encryption' no está definido. +CxPolicy[result] { + doc := input.document[i] + vault := doc.resource.azurerm_recovery_services_vault[name] + + not vault.encryption + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.azurerm_recovery_services_vault.%s", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": sprintf("'azurerm_recovery_services_vault.%s' should have an 'encryption' block defined", [name]), + "keyActualValue": sprintf("'azurerm_recovery_services_vault.%s' is missing the 'encryption' block", [name]), + } +} + +# REGLA 2: El atributo 'infrastructure_encryption_enabled' no está en true. +CxPolicy[result] { + doc := input.document[i] + vault := doc.resource.azurerm_recovery_services_vault[name] + + vault.encryption + object.get(vault.encryption, "infrastructure_encryption_enabled", false) != true + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.azurerm_recovery_services_vault.%s.encryption.infrastructure_encryption_enabled", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "encryption.infrastructure_encryption_enabled should be set to true", + "keyActualValue": sprintf("encryption.infrastructure_encryption_enabled is set to %v", [object.get(vault.encryption, "infrastructure_encryption_enabled", false)]), + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_recovery_services_vault_infrastructure_encryption_disabled/test/negative1.tf b/assets/queries/terraform/azure/azure_recovery_services_vault_infrastructure_encryption_disabled/test/negative1.tf new file mode 100644 index 00000000000..1e10cf5603c --- /dev/null +++ b/assets/queries/terraform/azure/azure_recovery_services_vault_infrastructure_encryption_disabled/test/negative1.tf @@ -0,0 +1,16 @@ +resource "azurerm_recovery_services_vault" "pass" { + name = "vault-pass" + resource_group_name = "rg" + location = "West Europe" + sku = "Standard" + + identity { + type = "SystemAssigned" + } + + encryption { + key_id = "https://kv.vault.azure.net/keys/key/v1" + infrastructure_encryption_enabled = true + use_system_assigned_identity = true + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_recovery_services_vault_infrastructure_encryption_disabled/test/positive1.tf b/assets/queries/terraform/azure/azure_recovery_services_vault_infrastructure_encryption_disabled/test/positive1.tf new file mode 100644 index 00000000000..51f73d0b5e7 --- /dev/null +++ b/assets/queries/terraform/azure/azure_recovery_services_vault_infrastructure_encryption_disabled/test/positive1.tf @@ -0,0 +1,6 @@ +resource "azurerm_recovery_services_vault" "fail_1" { + name = "fail-missing-encryption" + resource_group_name = "rg" + location = "West Europe" + sku = "Standard" +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_recovery_services_vault_infrastructure_encryption_disabled/test/positive2.tf b/assets/queries/terraform/azure/azure_recovery_services_vault_infrastructure_encryption_disabled/test/positive2.tf new file mode 100644 index 00000000000..2ee8fe240fc --- /dev/null +++ b/assets/queries/terraform/azure/azure_recovery_services_vault_infrastructure_encryption_disabled/test/positive2.tf @@ -0,0 +1,12 @@ +resource "azurerm_recovery_services_vault" "fail_2" { + name = "fail-disabled-infrastructure" + resource_group_name = "rg" + location = "West Europe" + sku = "Standard" + + encryption { + key_id = "key-id" + infrastructure_encryption_enabled = false + use_system_assigned_identity = true + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_recovery_services_vault_infrastructure_encryption_disabled/test/positive_expected_result.json b/assets/queries/terraform/azure/azure_recovery_services_vault_infrastructure_encryption_disabled/test/positive_expected_result.json new file mode 100644 index 00000000000..098d8633fae --- /dev/null +++ b/assets/queries/terraform/azure/azure_recovery_services_vault_infrastructure_encryption_disabled/test/positive_expected_result.json @@ -0,0 +1,14 @@ +[ + { + "queryName": "Recovery Services Vault Infrastructure Encryption Disabled", + "severity": "MEDIUM", + "line": 1, + "fileName": "positive1.tf" + }, + { + "queryName": "Recovery Services Vault Infrastructure Encryption Disabled", + "severity": "MEDIUM", + "line": 9, + "fileName": "positive2.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_sql_server_tde_cmk_disabled/README.md b/assets/queries/terraform/azure/azure_sql_server_tde_cmk_disabled/README.md new file mode 100644 index 00000000000..d243e68a8e5 --- /dev/null +++ b/assets/queries/terraform/azure/azure_sql_server_tde_cmk_disabled/README.md @@ -0,0 +1,56 @@ +# Regla KICS: SQL Server TDE Not Encrypted with CMK + +## Descripción General + +Esta regla de KICS asegura que la característica **Transparent Data Encryption (TDE)** de Azure SQL Server esté configurada para utilizar una **clave gestionada por el cliente (Customer-Managed Key - CMK)** almacenada en Azure Key Vault. + +Por defecto, Azure SQL cifra los datos en reposo utilizando una clave gestionada por el servicio (Microsoft). Aunque este método proporciona seguridad base, el uso de CMK ofrece un nivel de control superior indispensable para cumplir con normativas estrictas de seguridad corporativa. Con CMK, la organización asume el control total sobre el ciclo de vida de las claves, permitiendo la rotación, revocación de acceso y auditoría de uso de forma soberana a través de Azure Key Vault. + +## Lógica de la Regla + +La política audita la configuración de Terraform siguiendo estos criterios: +1. **Identificación:** Localiza todos los recursos de tipo `azurerm_mssql_server`. +2. **Vinculación:** Busca si existe un recurso independiente del tipo `azurerm_mssql_server_transparent_data_encryption` vinculado al servidor mediante el atributo `server_id`. +3. **Validación de Clave:** Verifica que dicho recurso tenga definido explícitamente el atributo `key_vault_key_id`. +4. **Alerta:** Si no se encuentra el recurso de cifrado o si falta la referencia a la clave de Key Vault, se genera un hallazgo. + +## Caso de Fallo Detectado + +A continuación se describe el escenario que esta política detectará. + +--- + +### Caso Único: Uso de Clave Gestionada por el Servicio (Default) + +* **Descripción:** Se define el servidor SQL pero no se añade el recurso adicional necesario para habilitar el cifrado TDE mediante llaves del cliente, manteniendo el cifrado por defecto de Azure. +* **Ejemplo de Código Terraform Problemático:** + ```terraform + resource "azurerm_mssql_server" "fail_server" { + name = "insecure-sql-server" + resource_group_name = azurerm_resource_group.example.name + location = azurerm_resource_group.example.location + version = "12.0" + administrator_login = "sqladmin" + administrator_password = "P@ssword123!" + + # Falta el recurso azurerm_mssql_server_transparent_data_encryption + } + ``` +* **Ubicación de la Alerta:** Sobre el recurso raíz `azurerm_mssql_server`. + +## Recursos Involucrados + +* `azurerm_mssql_server` +* `azurerm_mssql_server_transparent_data_encryption` + +## Solución + +Para mitigar este riesgo, cree un recurso `azurerm_mssql_server_transparent_data_encryption`, vincúlelo al servidor SQL y proporcione la URI de la clave de Azure Key Vault. + +```terraform +resource "azurerm_mssql_server_transparent_data_encryption" "secure_tde" { + server_id = azurerm_mssql_server.example.id + + # SOLUCIÓN: Usar una clave gestionada por el cliente + key_vault_key_id = azurerm_key_vault_key.example.id +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_sql_server_tde_cmk_disabled/metadata.json b/assets/queries/terraform/azure/azure_sql_server_tde_cmk_disabled/metadata.json new file mode 100644 index 00000000000..8047f2f55df --- /dev/null +++ b/assets/queries/terraform/azure/azure_sql_server_tde_cmk_disabled/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "745e82b3-edf5-4606-a5c2-25d8453e7de6", + "queryName": "SQL Server TDE Not Encrypted with CMK", + "severity": "MEDIUM", + "category": "Encryption", + "descriptionText": "Ensures that Azure SQL Server Transparent Data Encryption (TDE) is configured with a Customer-Managed Key (CMK) stored in Azure Key Vault, rather than the default Service-Managed Key.", + "descriptionUrl": "https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/mssql_server_transparent_data_encryption", + "platform": "Terraform", + "cloudProvider": "azure", + "cwe": "CWE-326", + "descriptionID": "745e82b3", + "riskScore": 5.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_sql_server_tde_cmk_disabled/query.rego b/assets/queries/terraform/azure/azure_sql_server_tde_cmk_disabled/query.rego new file mode 100644 index 00000000000..22c9b17da63 --- /dev/null +++ b/assets/queries/terraform/azure/azure_sql_server_tde_cmk_disabled/query.rego @@ -0,0 +1,32 @@ +package Cx + +has_cmk_tde(doc, server_id) { + tde := doc.resource.azurerm_mssql_server_transparent_data_encryption[_] + check_id(tde.server_id, server_id) + tde.key_vault_key_id +} + +check_id(current, target) { + current == target +} + +check_id(current, target) { + current == sprintf("${%s}", [target]) +} + +# REGLA 1: SQL Server sin configuración de TDE explícita o sin CMK. +CxPolicy[result] { + doc := input.document[i] + server := doc.resource.azurerm_mssql_server[name] + server_id := sprintf("azurerm_mssql_server.%s.id", [name]) + + not has_cmk_tde(doc, server_id) + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.azurerm_mssql_server.%s", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": sprintf("'azurerm_mssql_server.%s' should have an associated 'azurerm_mssql_server_transparent_data_encryption' resource with 'key_vault_key_id' set", [name]), + "keyActualValue": sprintf("'azurerm_mssql_server.%s' is using Service-Managed Key (default) or lacks TDE resource", [name]), + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_sql_server_tde_cmk_disabled/test/negative1.tf b/assets/queries/terraform/azure/azure_sql_server_tde_cmk_disabled/test/negative1.tf new file mode 100644 index 00000000000..9d1a96c6668 --- /dev/null +++ b/assets/queries/terraform/azure/azure_sql_server_tde_cmk_disabled/test/negative1.tf @@ -0,0 +1,13 @@ +resource "azurerm_mssql_server" "pass" { + name = "sql-server-pass" + resource_group_name = "rg-test" + location = "West Europe" + version = "12.0" + administrator_login = "sqladmin" + administrator_login_password = "Password123!" +} + +resource "azurerm_mssql_server_transparent_data_encryption" "pass_tde" { + server_id = azurerm_mssql_server.pass.id + key_vault_key_id = "https://kv.vault.azure.net/keys/key/v1" +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_sql_server_tde_cmk_disabled/test/positive1.tf b/assets/queries/terraform/azure/azure_sql_server_tde_cmk_disabled/test/positive1.tf new file mode 100644 index 00000000000..7e5a913fb3e --- /dev/null +++ b/assets/queries/terraform/azure/azure_sql_server_tde_cmk_disabled/test/positive1.tf @@ -0,0 +1,8 @@ +resource "azurerm_mssql_server" "fail" { + name = "sql-server-fail" + resource_group_name = "rg-test" + location = "West Europe" + version = "12.0" + administrator_login = "sqladmin" + administrator_login_password = "Password123!" +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_sql_server_tde_cmk_disabled/test/positive_expected_result.json b/assets/queries/terraform/azure/azure_sql_server_tde_cmk_disabled/test/positive_expected_result.json new file mode 100644 index 00000000000..c6f6f5d4124 --- /dev/null +++ b/assets/queries/terraform/azure/azure_sql_server_tde_cmk_disabled/test/positive_expected_result.json @@ -0,0 +1,8 @@ +[ + { + "queryName": "SQL Server TDE Not Encrypted with CMK", + "severity": "MEDIUM", + "line": 1, + "fileName": "positive1.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_account_geo_redundancy_disabled/README.md b/assets/queries/terraform/azure/azure_storage_account_geo_redundancy_disabled/README.md new file mode 100644 index 00000000000..6dfd2710310 --- /dev/null +++ b/assets/queries/terraform/azure/azure_storage_account_geo_redundancy_disabled/README.md @@ -0,0 +1,55 @@ +# Regla KICS: Storage Account Geo-Redundancy Disabled + +## Descripción General + +Esta regla de KICS verifica que las cuentas de almacenamiento de Azure (`azurerm_storage_account`) estén configuradas con **Redundancia Geográfica**. + +La redundancia geográfica es una estrategia de continuidad del negocio fundamental. Al habilitarla, Azure copia los datos de forma asíncrona en una región secundaria situada a cientos de kilómetros de la región principal. Esto garantiza que, ante un desastre regional catastrófico (fallos masivos de red, desastres naturales o cortes de energía en toda una región), los datos permanezcan duraderos y disponibles para su recuperación, minimizando el RPO (objetivo de punto de recuperación) y el RTO (objetivo de tiempo de recuperación). + +## Lógica de la Regla + +La política audita el atributo `account_replication_type` del recurso `azurerm_storage_account`: +1. **Validación de Tipo:** Comprueba si el valor configurado pertenece al grupo de alta disponibilidad regional: `GRS`, `RAGRS`, `GZRS` o `RAGZRS`. +2. **Detección de Riesgo:** Si el valor es `LRS` (Localmente redundante) o `ZRS` (Redundancia de zona), se genera una alerta, ya que estos niveles solo protegen contra fallos de hardware dentro de un centro de datos o zona, pero no contra la pérdida de una región completa. + +## Caso de Fallo Detectado + +A continuación se describe el escenario que esta política detectará. + +--- + +### Caso Único: Replicación Local o Zonal (LRS/ZRS) + +* **Descripción:** La cuenta de almacenamiento utiliza un esquema de replicación que no se extiende fuera de la región principal, dejando los datos vulnerables ante desastres regionales. +* **Ejemplo de Código Terraform Problemático:** + ```terraform + resource "azurerm_storage_account" "fail_storage" { + name = "insecurestorage" + resource_group_name = azurerm_resource_group.example.name + location = azurerm_resource_group.example.location + account_tier = "Standard" + + # FALLO: Replicación limitada a la región local + account_replication_type = "LRS" + } + ``` +* **Ubicación de la Alerta:** Atributo `account_replication_type`. + +## Recurso Involucrado + +* `azurerm_storage_account` + +## Solución + +Para mitigar este riesgo en entornos de producción, cambie el tipo de replicación a uno que soporte redundancia geográfica, como **GRS**. + +```terraform +resource "azurerm_storage_account" "secure_storage" { + name = "securestorage" + resource_group_name = azurerm_resource_group.example.name + location = azurerm_resource_group.example.location + account_tier = "Standard" + + # SOLUCIÓN: Habilitar redundancia geográfica + account_replication_type = "GRS" +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_account_geo_redundancy_disabled/metadata.json b/assets/queries/terraform/azure/azure_storage_account_geo_redundancy_disabled/metadata.json new file mode 100644 index 00000000000..7c5d7efb00d --- /dev/null +++ b/assets/queries/terraform/azure/azure_storage_account_geo_redundancy_disabled/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "56c748b8-9ec8-4f3e-84ac-c9a6992e1b20", + "queryName": "Storage Account Geo-Redundancy Disabled", + "severity": "MEDIUM", + "category": "Availability", + "descriptionText": "Ensures that Azure Storage Accounts use Geo-Redundant Storage (GRS, RAGRS, GZRS, or RAGZRS). This is critical for disaster recovery, ensuring data is durable even in the event of a complete regional outage.", + "descriptionUrl": "https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/storage_account#account_replication_type", + "platform": "Terraform", + "cloudProvider": "azure", + "cwe": "CWE-668", + "descriptionID": "56c748b8", + "riskScore": 5.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_account_geo_redundancy_disabled/query.rego b/assets/queries/terraform/azure/azure_storage_account_geo_redundancy_disabled/query.rego new file mode 100644 index 00000000000..daa11248e5c --- /dev/null +++ b/assets/queries/terraform/azure/azure_storage_account_geo_redundancy_disabled/query.rego @@ -0,0 +1,21 @@ +package Cx + +geo_redundant_types := {"GRS", "RAGRS", "GZRS", "RAGZRS"} + +# REGLA 1: El tipo de replicación no es Geo-Redundante (ej. es LRS o ZRS). +CxPolicy[result] { + doc := input.document[i] + sa := doc.resource.azurerm_storage_account[name] + + current_type := sa.account_replication_type + + not geo_redundant_types[current_type] + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.azurerm_storage_account.%s.account_replication_type", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "'account_replication_type' should be 'GRS', 'RAGRS', 'GZRS', or 'RAGZRS'", + "keyActualValue": sprintf("'account_replication_type' is set to '%s'", [current_type]), + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_account_geo_redundancy_disabled/test/negative1.tf b/assets/queries/terraform/azure/azure_storage_account_geo_redundancy_disabled/test/negative1.tf new file mode 100644 index 00000000000..01fd017e155 --- /dev/null +++ b/assets/queries/terraform/azure/azure_storage_account_geo_redundancy_disabled/test/negative1.tf @@ -0,0 +1,7 @@ +resource "azurerm_storage_account" "pass" { + name = "storagepass" + resource_group_name = "rg-test" + location = "West Europe" + account_tier = "Standard" + account_replication_type = "GRS" +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_account_geo_redundancy_disabled/test/positive1.tf b/assets/queries/terraform/azure/azure_storage_account_geo_redundancy_disabled/test/positive1.tf new file mode 100644 index 00000000000..40dd592b5df --- /dev/null +++ b/assets/queries/terraform/azure/azure_storage_account_geo_redundancy_disabled/test/positive1.tf @@ -0,0 +1,7 @@ +resource "azurerm_storage_account" "fail" { + name = "storagefail" + resource_group_name = "rg-test" + location = "West Europe" + account_tier = "Standard" + account_replication_type = "LRS" +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_account_geo_redundancy_disabled/test/positive_expected_result.json b/assets/queries/terraform/azure/azure_storage_account_geo_redundancy_disabled/test/positive_expected_result.json new file mode 100644 index 00000000000..a6c97123f83 --- /dev/null +++ b/assets/queries/terraform/azure/azure_storage_account_geo_redundancy_disabled/test/positive_expected_result.json @@ -0,0 +1,8 @@ +[ + { + "queryName": "Storage Account Geo-Redundancy Disabled", + "severity": "MEDIUM", + "line": 6, + "fileName": "positive1.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_account_infrastructure_encryption_disabled/README.md b/assets/queries/terraform/azure/azure_storage_account_infrastructure_encryption_disabled/README.md new file mode 100644 index 00000000000..5bc305813ee --- /dev/null +++ b/assets/queries/terraform/azure/azure_storage_account_infrastructure_encryption_disabled/README.md @@ -0,0 +1,72 @@ +# Regla KICS: Storage Account Infrastructure Encryption Disabled + +## Descripción General + +Esta regla de KICS verifica que el **Cifrado de Infraestructura** (`infrastructure_encryption_enabled`) esté habilitado en los recursos `azurerm_storage_account`. + +Por defecto, Azure Storage cifra todos los datos en reposo mediante el cifrado del lado del servidor (SSE) utilizando claves gestionadas por Microsoft. Sin embargo, habilitar el cifrado de infraestructura proporciona una capa de defensa en profundidad conocida como **Doble Cifrado** (Double Encryption). En este modelo, los datos se cifran dos veces: una a nivel del servicio de almacenamiento y otra a nivel de la infraestructura subyacente, utilizando dos algoritmos de cifrado independientes y claves distintas. Esto protege la información incluso en el caso improbable de que un algoritmo o clave individual se vea comprometido. + +**Nota técnica:** Esta configuración es **inmutable**. Solo se puede habilitar en el momento de la creación de la cuenta de almacenamiento; no es posible activarla posteriormente sin recrear el recurso. + +## Lógica de la Regla + +La política audita el recurso `azurerm_storage_account` evaluando dos escenarios: +1. **Atributo Ausente:** Si no se define explícitamente `infrastructure_encryption_enabled`, KICS asume el valor por defecto (`false`) y genera una alerta sobre el recurso. +2. **Deshabilitado Explícitamente:** Si el atributo está configurado como `false`, se genera una alerta indicando que la protección de doble cifrado no está activa. + +## Casos de Fallo Detectados + +A continuación se describen los escenarios que esta política detectará. + +--- + +### Caso 1: Configuración de Doble Cifrado Ausente + +* **Descripción:** Se define la cuenta de almacenamiento sin incluir el parámetro de cifrado de infraestructura, delegando la seguridad únicamente al cifrado simple predeterminado. +* **Ejemplo de Código Terraform Problemático:** + ```terraform + resource "azurerm_storage_account" "fail_missing" { + name = "storageinsecure" + resource_group_name = azurerm_resource_group.example.name + location = azurerm_resource_group.example.location + account_tier = "Standard" + account_replication_type = "LRS" + # Falta infrastructure_encryption_enabled = true + } + ``` +* **Ubicación de la Alerta:** Sobre el recurso raíz `azurerm_storage_account`. + +--- + +### Caso 2: Cifrado de Infraestructura Deshabilitado Explícitamente + +* **Descripción:** El atributo `infrastructure_encryption_enabled` se ha configurado como `false`, desactivando la capa de seguridad adicional requerida. +* **Ejemplo de Código Terraform Problemático:** + ```terraform + resource "azurerm_storage_account" "fail_explicit" { + name = "storagedisabled" + # ... + infrastructure_encryption_enabled = false # <-- Problema detectado + } + ``` +* **Ubicación de la Alerta:** Atributo `infrastructure_encryption_enabled`. + +## Recurso Involucrado + +* `azurerm_storage_account` + +## Solución + +Para mitigar este riesgo, establezca `infrastructure_encryption_enabled` en `true` durante la definición inicial de la cuenta de almacenamiento. + +```terraform +resource "azurerm_storage_account" "secure_storage" { + name = "storage-secure" + resource_group_name = azurerm_resource_group.example.name + location = azurerm_resource_group.example.location + account_tier = "Standard" + account_replication_type = "GRS" + + # SOLUCIÓN: Habilitar el doble cifrado de infraestructura + infrastructure_encryption_enabled = true +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_account_infrastructure_encryption_disabled/metadata.json b/assets/queries/terraform/azure/azure_storage_account_infrastructure_encryption_disabled/metadata.json new file mode 100644 index 00000000000..4b988f9c413 --- /dev/null +++ b/assets/queries/terraform/azure/azure_storage_account_infrastructure_encryption_disabled/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "d02793e5-b3b5-4cfa-b379-7a81d5bfe170", + "queryName": "Storage Account Infrastructure Encryption Disabled", + "severity": "MEDIUM", + "category": "Encryption", + "descriptionText": "Ensures that 'Infrastructure Encryption' is enabled for Azure Storage Accounts. This provides a second layer of encryption (double encryption) for data at rest, protecting against the compromise of any single encryption algorithm or key.", + "descriptionUrl": "https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/storage_account#infrastructure_encryption_enabled", + "platform": "Terraform", + "cloudProvider": "azure", + "cwe": "CWE-312", + "descriptionID": "d02793e5", + "riskScore": 5.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_account_infrastructure_encryption_disabled/query.rego b/assets/queries/terraform/azure/azure_storage_account_infrastructure_encryption_disabled/query.rego new file mode 100644 index 00000000000..149bbed7e9f --- /dev/null +++ b/assets/queries/terraform/azure/azure_storage_account_infrastructure_encryption_disabled/query.rego @@ -0,0 +1,33 @@ +package Cx + +# REGLA 1: Atributo 'infrastructure_encryption_enabled' ausente. +CxPolicy[result] { + doc := input.document[i] + sa := doc.resource.azurerm_storage_account[name] + + object.get(sa, "infrastructure_encryption_enabled", "undefined") == "undefined" + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.azurerm_storage_account.%s", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": sprintf("'azurerm_storage_account.%s' should have 'infrastructure_encryption_enabled' set to true", [name]), + "keyActualValue": sprintf("'azurerm_storage_account.%s' is missing 'infrastructure_encryption_enabled'", [name]), + } +} + +# REGLA 2: Atributo 'infrastructure_encryption_enabled' establecido en false. +CxPolicy[result] { + doc := input.document[i] + sa := doc.resource.azurerm_storage_account[name] + + sa.infrastructure_encryption_enabled == false + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.azurerm_storage_account.%s.infrastructure_encryption_enabled", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "'infrastructure_encryption_enabled' should be set to true", + "keyActualValue": "'infrastructure_encryption_enabled' is set to false", + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_account_infrastructure_encryption_disabled/test/negative1.tf b/assets/queries/terraform/azure/azure_storage_account_infrastructure_encryption_disabled/test/negative1.tf new file mode 100644 index 00000000000..1c168bb3be0 --- /dev/null +++ b/assets/queries/terraform/azure/azure_storage_account_infrastructure_encryption_disabled/test/negative1.tf @@ -0,0 +1,8 @@ +resource "azurerm_storage_account" "pass" { + name = "stpass" + resource_group_name = "rg-test" + location = "West Europe" + account_tier = "Standard" + account_replication_type = "LRS" + infrastructure_encryption_enabled = true +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_account_infrastructure_encryption_disabled/test/positive1.tf b/assets/queries/terraform/azure/azure_storage_account_infrastructure_encryption_disabled/test/positive1.tf new file mode 100644 index 00000000000..acc79de247b --- /dev/null +++ b/assets/queries/terraform/azure/azure_storage_account_infrastructure_encryption_disabled/test/positive1.tf @@ -0,0 +1,7 @@ +resource "azurerm_storage_account" "fail_omission" { + name = "stfailomission" + resource_group_name = "rg-test" + location = "West Europe" + account_tier = "Standard" + account_replication_type = "LRS" +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_account_infrastructure_encryption_disabled/test/positive2.tf b/assets/queries/terraform/azure/azure_storage_account_infrastructure_encryption_disabled/test/positive2.tf new file mode 100644 index 00000000000..4be506a4672 --- /dev/null +++ b/assets/queries/terraform/azure/azure_storage_account_infrastructure_encryption_disabled/test/positive2.tf @@ -0,0 +1,8 @@ +resource "azurerm_storage_account" "fail_explicit" { + name = "stfailexplicit" + resource_group_name = "rg-test" + location = "West Europe" + account_tier = "Standard" + account_replication_type = "LRS" + infrastructure_encryption_enabled = false +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_account_infrastructure_encryption_disabled/test/positive_expected_result.json b/assets/queries/terraform/azure/azure_storage_account_infrastructure_encryption_disabled/test/positive_expected_result.json new file mode 100644 index 00000000000..4c58ec8ff3a --- /dev/null +++ b/assets/queries/terraform/azure/azure_storage_account_infrastructure_encryption_disabled/test/positive_expected_result.json @@ -0,0 +1,14 @@ +[ + { + "queryName": "Storage Account Infrastructure Encryption Disabled", + "severity": "MEDIUM", + "line": 1, + "fileName": "positive1.tf" + }, + { + "queryName": "Storage Account Infrastructure Encryption Disabled", + "severity": "MEDIUM", + "line": 7, + "fileName": "positive2.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_account_read_only_lock_missing/README.md b/assets/queries/terraform/azure/azure_storage_account_read_only_lock_missing/README.md new file mode 100644 index 00000000000..bf0b5dab876 --- /dev/null +++ b/assets/queries/terraform/azure/azure_storage_account_read_only_lock_missing/README.md @@ -0,0 +1,63 @@ +# Regla KICS: Storage Account ReadOnly Lock Missing + +## Descripción General + +Esta regla de KICS verifica que las cuentas de almacenamiento de Azure (`azurerm_storage_account`) tengan aplicado un **bloqueo de recursos** (`azurerm_management_lock`) configurado específicamente en el nivel **`ReadOnly`**. + +Los bloqueos de Azure Resource Manager (ARM) proporcionan una capa de seguridad adicional que trasciende los permisos de RBAC. Aplicar un bloqueo de tipo `ReadOnly` a una cuenta de almacenamiento garantiza que la configuración del recurso (como las reglas de firewall, los niveles de acceso o la configuración de replicación) no pueda ser modificada ni el recurso eliminado accidentalmente, incluso por administradores. Es una práctica esencial para activos de datos críticos en entornos de producción. + +## Lógica de la Regla + +La política realiza un análisis cruzado entre los recursos de la cuenta de almacenamiento y los bloqueos definidos: +1. **Identificación:** Localiza todas las instancias de `azurerm_storage_account`. +2. **Vinculación:** Busca recursos `azurerm_management_lock` cuyo atributo `scope` referencie al ID del Storage Account. +3. **Validación de Nivel:** Verifica que el atributo `lock_level` sea estrictamente `"ReadOnly"`. +4. **Generación de Hallazgo:** Si el recurso no tiene ningún bloqueo o si los bloqueos existentes tienen un nivel inferior (como `CanNotDelete`), se genera una alerta. + +## Casos de Fallo Detectados + +### Caso 1: Storage Account sin Bloqueo + +* **Descripción:** La cuenta de almacenamiento se ha desplegado sin ninguna restricción de gestión, permitiendo modificaciones accidentales. +* **Ubicación de la Alerta:** Sobre el recurso raíz `azurerm_storage_account`. + +--- + +### Caso 2: Bloqueo con Nivel Incorrecto + +* **Descripción:** Existe un bloqueo asociado al recurso, pero su nivel es `"CanNotDelete"`, lo cual permite modificaciones en la configuración que la regla busca restringir mediante `"ReadOnly"`. +* **Ejemplo de Código Terraform Problemático:** + ```terraform + resource "azurerm_management_lock" "fail_lock" { + name = "prevent-deletion" + scope = azurerm_storage_account.example.id + lock_level = "CanNotDelete" # <-- FALLO: Se requiere 'ReadOnly' + } + ``` +* **Ubicación de la Alerta:** Atributo `lock_level` del recurso de bloqueo. + +## Recursos Involucrados + +* `azurerm_storage_account` +* `azurerm_management_lock` + +## Solución + +Añada un recurso `azurerm_management_lock` apuntando al ID de la cuenta de almacenamiento con el nivel `ReadOnly`. + +```terraform +resource "azurerm_storage_account" "secure_sa" { + name = "storage-prod-critical" + resource_group_name = azurerm_resource_group.example.name + location = azurerm_resource_group.example.location + account_tier = "Standard" + account_replication_type = "GRS" +} + +# SOLUCIÓN: Aplicar bloqueo ReadOnly +resource "azurerm_management_lock" "sa_readonly_lock" { + name = "critical-storage-lock" + scope = azurerm_storage_account.secure_sa.id + lock_level = "ReadOnly" + notes = "Bloqueo de seguridad para cumplimiento de política de gobernanza" +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_account_read_only_lock_missing/metadata.json b/assets/queries/terraform/azure/azure_storage_account_read_only_lock_missing/metadata.json new file mode 100644 index 00000000000..99fa66216c4 --- /dev/null +++ b/assets/queries/terraform/azure/azure_storage_account_read_only_lock_missing/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "3a716202-a630-4f23-b8ba-2dcbc40e2fa7", + "queryName": "Storage Account ReadOnly Lock Missing", + "severity": "LOW", + "category": "Resource Management", + "descriptionText": "Ensures that Azure Storage Accounts have a Resource Manager lock with 'ReadOnly' level applied. This prevents accidental modification or deletion of the storage account configuration.", + "descriptionUrl": "https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/management_lock", + "platform": "Terraform", + "cloudProvider": "azure", + "cwe": "CWE-400", + "descriptionID": "3a716202", + "riskScore": 2.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_account_read_only_lock_missing/query.rego b/assets/queries/terraform/azure/azure_storage_account_read_only_lock_missing/query.rego new file mode 100644 index 00000000000..560db7a509c --- /dev/null +++ b/assets/queries/terraform/azure/azure_storage_account_read_only_lock_missing/query.rego @@ -0,0 +1,58 @@ +package Cx + +has_readonly_lock(doc, sa_id) { + l := doc.resource.azurerm_management_lock[_] + check_lock_scope(l.scope, sa_id) + l.lock_level == "ReadOnly" +} + +check_lock_scope(current, target) { + current == target +} + +check_lock_scope(current, target) { + current == sprintf("${%s}", [target]) +} + +# CASO 1: Storage Account totalmente desprotegido (sin bloqueos asociados) +CxPolicy[result] { + doc := input.document[i] + sa := doc.resource.azurerm_storage_account[sa_name] + sa_id := sprintf("azurerm_storage_account.%s.id", [sa_name]) + + associated_locks := [l | + l := doc.resource.azurerm_management_lock[_] + check_lock_scope(l.scope, sa_id) + ] + count(associated_locks) == 0 + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.azurerm_storage_account.%s", [sa_name]), + "issueType": "MissingAttribute", + "keyExpectedValue": sprintf("'azurerm_storage_account.%s' should have a 'ReadOnly' lock associated", [sa_name]), + "keyActualValue": sprintf("'azurerm_storage_account.%s' has no locks associated", [sa_name]), + } +} + +# CASO 2: Storage Account con bloqueo incorrecto (nivel distinto a ReadOnly) +CxPolicy[result] { + doc := input.document[i] + lock := doc.resource.azurerm_management_lock[lock_name] + + lock.lock_level != "ReadOnly" + + sa := doc.resource.azurerm_storage_account[sa_name] + sa_id := sprintf("azurerm_storage_account.%s.id", [sa_name]) + check_lock_scope(lock.scope, sa_id) + + not has_readonly_lock(doc, sa_id) + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.azurerm_management_lock.%s.lock_level", [lock_name]), + "issueType": "IncorrectValue", + "keyExpectedValue": sprintf("'azurerm_management_lock.%s.lock_level' should be 'ReadOnly'", [lock_name]), + "keyActualValue": sprintf("'azurerm_management_lock.%s.lock_level' is '%s'", [lock_name, lock.lock_level]), + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_account_read_only_lock_missing/test/negative1.tf b/assets/queries/terraform/azure/azure_storage_account_read_only_lock_missing/test/negative1.tf new file mode 100644 index 00000000000..d1f2a9b1aae --- /dev/null +++ b/assets/queries/terraform/azure/azure_storage_account_read_only_lock_missing/test/negative1.tf @@ -0,0 +1,13 @@ +resource "azurerm_storage_account" "pass" { + name = "stpass" + resource_group_name = "rg-test" + location = "West Europe" + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_management_lock" "pass_lock" { + name = "readonly-lock" + scope = azurerm_storage_account.pass.id + lock_level = "ReadOnly" +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_account_read_only_lock_missing/test/positive1.tf b/assets/queries/terraform/azure/azure_storage_account_read_only_lock_missing/test/positive1.tf new file mode 100644 index 00000000000..22d586ce54a --- /dev/null +++ b/assets/queries/terraform/azure/azure_storage_account_read_only_lock_missing/test/positive1.tf @@ -0,0 +1,7 @@ +resource "azurerm_storage_account" "fail_1" { + name = "stfailnoblocking" + resource_group_name = "rg-test" + location = "West Europe" + account_tier = "Standard" + account_replication_type = "LRS" +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_account_read_only_lock_missing/test/positive2.tf b/assets/queries/terraform/azure/azure_storage_account_read_only_lock_missing/test/positive2.tf new file mode 100644 index 00000000000..60837ce3bc1 --- /dev/null +++ b/assets/queries/terraform/azure/azure_storage_account_read_only_lock_missing/test/positive2.tf @@ -0,0 +1,13 @@ +resource "azurerm_storage_account" "sa_fail_2" { + name = "stfailwronglevel" + resource_group_name = "rg-test" + location = "West Europe" + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_management_lock" "fail_lock_level" { + name = "not-readonly" + scope = azurerm_storage_account.sa_fail_2.id + lock_level = "CanNotDelete" +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_account_read_only_lock_missing/test/positive_expected_result.json b/assets/queries/terraform/azure/azure_storage_account_read_only_lock_missing/test/positive_expected_result.json new file mode 100644 index 00000000000..97fd3418dd5 --- /dev/null +++ b/assets/queries/terraform/azure/azure_storage_account_read_only_lock_missing/test/positive_expected_result.json @@ -0,0 +1,14 @@ +[ + { + "queryName": "Storage Account ReadOnly Lock Missing", + "severity": "LOW", + "line": 1, + "fileName": "positive1.tf" + }, + { + "queryName": "Storage Account ReadOnly Lock Missing", + "severity": "LOW", + "line": 12, + "fileName": "positive2.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_account_versioning_disabled/README.md b/assets/queries/terraform/azure/azure_storage_account_versioning_disabled/README.md new file mode 100644 index 00000000000..9b9b41c70e4 --- /dev/null +++ b/assets/queries/terraform/azure/azure_storage_account_versioning_disabled/README.md @@ -0,0 +1,76 @@ +# Regla KICS: Storage Account Blob Versioning Disabled + +## Descripción General + +Esta regla verifica que la característica de **Versioning** (control de versiones) esté habilitada en los recursos `azurerm_storage_account`. + +El control de versiones de Blobs permite mantener automáticamente versiones anteriores de un objeto cuando este se modifica o elimina. Es una capa de protección fundamental para escenarios de recuperación de datos ante errores humanos, sobreescrituras accidentales o ataques de ransomware, permitiendo restaurar un estado anterior del archivo sin necesidad de recurrir a copias de seguridad externas complejas. + +## Lógica de la Regla + +La política evalúa la configuración en tres niveles de granularidad: +1. **Bloque Ausente:** Si el recurso no tiene definido el bloque `blob_properties`. +2. **Atributo Ausente:** Si existe el bloque pero no se define el parámetro `versioning_enabled`. +3. **Valor Incorrecto:** Si `versioning_enabled` se ha configurado explícitamente como `false`. + +## Casos de Fallo Detectados + +A continuación se describen los escenarios que esta política detectará. + +--- + +### Caso 1: Configuración de Bloque Ausente + +* **Descripción:** El recurso no define el bloque `blob_properties`. Por defecto, Azure deshabilita el versionado si no se especifica. +* **Ejemplo de Código Terraform Problemático:** + ```terraform + resource "azurerm_storage_account" "fail_1" { + name = "storage-insecure-1" + resource_group_name = "rg-prod" + location = "West Europe" + account_tier = "Standard" + account_replication_type = "LRS" + + # El bloque blob_properties es inexistente + } + ``` +* **Ubicación de la Alerta:** Sobre el recurso raíz `azurerm_storage_account`. + +--- + +### Caso 2: Versionado Deshabilitado Explícitamente + +* **Descripción:** Se ha incluido el bloque de propiedades pero el versionado está apagado, lo que impide la recuperación de archivos modificados. +* **Ejemplo de Código Terraform Problemático:** + ```terraform + resource "azurerm_storage_account" "fail_2" { + name = "storage-insecure-2" + # ... + blob_properties { + versioning_enabled = false # <-- PROBLEMA: Deshabilitado. + } + } + ``` +* **Ubicación de la Alerta:** Línea `versioning_enabled = false`. + +## Recurso Involucrado + +* `azurerm_storage_account` + +## Solución + +Para solucionar este riesgo, asegúrese de incluir el bloque `blob_properties` con el atributo `versioning_enabled` establecido en `true`. + +```terraform +resource "azurerm_storage_account" "secure_storage" { + name = "storage-secure" + resource_group_name = azurerm_resource_group.example.name + location = azurerm_resource_group.example.location + account_tier = "Standard" + account_replication_type = "GRS" + + # SOLUCIÓN: Habilitar el control de versiones de blobs + blob_properties { + versioning_enabled = true + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_account_versioning_disabled/metadata.json b/assets/queries/terraform/azure/azure_storage_account_versioning_disabled/metadata.json new file mode 100644 index 00000000000..88efc35cafb --- /dev/null +++ b/assets/queries/terraform/azure/azure_storage_account_versioning_disabled/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "0f437572-8be4-4e05-9f76-dd266d7ad90b", + "queryName": "Storage Account Blob Versioning Disabled", + "severity": "MEDIUM", + "category": "Backup", + "descriptionText": "Ensures that 'Versioning' is enabled for Azure Storage Account Blob services. Blob versioning enables automatic retention of previous versions of an object.", + "descriptionUrl": "https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/storage_account#versioning_enabled", + "platform": "Terraform", + "cloudProvider": "azure", + "cwe": "CWE-226", + "descriptionID": "0f437572", + "riskScore": 5.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_account_versioning_disabled/query.rego b/assets/queries/terraform/azure/azure_storage_account_versioning_disabled/query.rego new file mode 100644 index 00000000000..70bc0f27b04 --- /dev/null +++ b/assets/queries/terraform/azure/azure_storage_account_versioning_disabled/query.rego @@ -0,0 +1,51 @@ +package Cx + +# CASO 1: Falta el bloque 'blob_properties' completo. +CxPolicy[result] { + doc := input.document[i] + sa := doc.resource.azurerm_storage_account[name] + + not sa.blob_properties + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.azurerm_storage_account.%s", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": sprintf("'azurerm_storage_account.%s' should have 'blob_properties' defined", [name]), + "keyActualValue": sprintf("'azurerm_storage_account.%s' is missing 'blob_properties'", [name]), + } +} + +# CASO 2: Existe 'blob_properties' pero falta el atributo 'versioning_enabled'. +CxPolicy[result] { + doc := input.document[i] + sa := doc.resource.azurerm_storage_account[name] + + sa.blob_properties + + object.get(sa.blob_properties, "versioning_enabled", "undefined") == "undefined" + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.azurerm_storage_account.%s.blob_properties", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "blob_properties.versioning_enabled should be defined and set to true", + "keyActualValue": "blob_properties.versioning_enabled is missing", + } +} + +# CASO 3: 'versioning_enabled' existe pero está explícitamente a false. +CxPolicy[result] { + doc := input.document[i] + sa := doc.resource.azurerm_storage_account[name] + + sa.blob_properties.versioning_enabled == false + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.azurerm_storage_account.%s.blob_properties.versioning_enabled", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "blob_properties.versioning_enabled should be set to true", + "keyActualValue": "blob_properties.versioning_enabled is set to false", + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_account_versioning_disabled/test/negative1.tf b/assets/queries/terraform/azure/azure_storage_account_versioning_disabled/test/negative1.tf new file mode 100644 index 00000000000..efc6cad605c --- /dev/null +++ b/assets/queries/terraform/azure/azure_storage_account_versioning_disabled/test/negative1.tf @@ -0,0 +1,11 @@ +resource "azurerm_storage_account" "pass" { + name = "stpass" + resource_group_name = "rg-test" + location = "West Europe" + account_tier = "Standard" + account_replication_type = "LRS" + + blob_properties { + versioning_enabled = true + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_account_versioning_disabled/test/positive1.tf b/assets/queries/terraform/azure/azure_storage_account_versioning_disabled/test/positive1.tf new file mode 100644 index 00000000000..219f4827eb9 --- /dev/null +++ b/assets/queries/terraform/azure/azure_storage_account_versioning_disabled/test/positive1.tf @@ -0,0 +1,7 @@ +resource "azurerm_storage_account" "fail_missing_block" { + name = "stfailblock" + resource_group_name = "rg-test" + location = "West Europe" + account_tier = "Standard" + account_replication_type = "LRS" +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_account_versioning_disabled/test/positive2.tf b/assets/queries/terraform/azure/azure_storage_account_versioning_disabled/test/positive2.tf new file mode 100644 index 00000000000..e5d343549ae --- /dev/null +++ b/assets/queries/terraform/azure/azure_storage_account_versioning_disabled/test/positive2.tf @@ -0,0 +1,11 @@ +resource "azurerm_storage_account" "fail_explicit_false" { + name = "stfailexplicit" + resource_group_name = "rg-test" + location = "West Europe" + account_tier = "Standard" + account_replication_type = "LRS" + + blob_properties { + change_feed_enabled = false + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_account_versioning_disabled/test/positive3.tf b/assets/queries/terraform/azure/azure_storage_account_versioning_disabled/test/positive3.tf new file mode 100644 index 00000000000..ebf179fbecd --- /dev/null +++ b/assets/queries/terraform/azure/azure_storage_account_versioning_disabled/test/positive3.tf @@ -0,0 +1,11 @@ +resource "azurerm_storage_account" "fail_explicit_false" { + name = "stfailexplicit" + resource_group_name = "rg-test" + location = "West Europe" + account_tier = "Standard" + account_replication_type = "LRS" + + blob_properties { + versioning_enabled = false + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_account_versioning_disabled/test/positive_expected_result.json b/assets/queries/terraform/azure/azure_storage_account_versioning_disabled/test/positive_expected_result.json new file mode 100644 index 00000000000..19db728ea31 --- /dev/null +++ b/assets/queries/terraform/azure/azure_storage_account_versioning_disabled/test/positive_expected_result.json @@ -0,0 +1,20 @@ +[ + { + "queryName": "Storage Account Blob Versioning Disabled", + "severity": "MEDIUM", + "line": 1, + "fileName": "positive1.tf" + }, + { + "queryName": "Storage Account Blob Versioning Disabled", + "severity": "MEDIUM", + "line": 8, + "fileName": "positive2.tf" + }, + { + "queryName": "Storage Account Blob Versioning Disabled", + "severity": "MEDIUM", + "line": 9, + "fileName": "positive3.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_blob_logging_disabled/README.md b/assets/queries/terraform/azure/azure_storage_blob_logging_disabled/README.md new file mode 100644 index 00000000000..d239d1dd6b5 --- /dev/null +++ b/assets/queries/terraform/azure/azure_storage_blob_logging_disabled/README.md @@ -0,0 +1,46 @@ +# Regla KICS: Storage Blob Service Logging Disabled + +## Descripción General + +Esta regla verifica que el registro de diagnóstico (**Diagnostic Settings**) esté habilitado y configurado íntegramente para el servicio de **Blobs** en las cuentas de almacenamiento de Azure. + +Para garantizar una auditoría completa del plano de datos, es imperativo registrar las tres operaciones fundamentales que permiten rastrear el ciclo de vida de los objetos: +* **StorageRead:** Auditoría de lectura de datos de blobs y metadatos de contenedores. +* **StorageWrite:** Auditoría de subidas, creaciones y modificaciones de blobs. +* **StorageDelete:** Auditoría de eliminaciones de objetos y contenedores. + +## Lógica de la Regla + +La política audita el código Terraform evaluando tres niveles de cumplimiento: +1. **Existencia de Recurso:** Verifica que cada `azurerm_storage_account` tenga un recurso `azurerm_monitor_diagnostic_setting` vinculado a su endpoint de blobs (`/blobServices/default`). +2. **Presencia de Logs:** Alerta si el recurso de diagnóstico existe pero no contiene definiciones de registros (`enabled_log`). +3. **Integridad de Categorías:** Analiza que las categorías `StorageRead`, `StorageWrite` y `StorageDelete` estén presentes. Si el conjunto está incompleto, la alerta apunta directamente al bloque `enabled_log`. + +## Casos de Fallo Detectados + +### Caso 1: Servicio de Blobs sin Diagnostic Settings +* **Ubicación:** `azurerm_storage_account`. + +### Caso 2: Diagnostic Setting sin bloques de Log +* **Ubicación:** `azurerm_monitor_diagnostic_setting`. + +### Caso 3: Auditoría de Blobs Incompleta +* **Descripción:** El bloque de registros no contiene el set completo de categorías (Read, Write y Delete). +* **Ubicación:** Bloque `enabled_log` dentro de `azurerm_monitor_diagnostic_setting`. + +## Recursos Involucrados +* `azurerm_storage_account` +* `azurerm_monitor_diagnostic_setting` + +## Solución + +```terraform +resource "azurerm_monitor_diagnostic_setting" "secure_blob_logging" { + name = "blob-audit-complete" + target_resource_id = "${azurerm_storage_account.example.id}/blobServices/default" + storage_account_id = azurerm_storage_account.log_destination.id + + enabled_log { category = "StorageRead" } + enabled_log { category = "StorageWrite" } + enabled_log { category = "StorageDelete" } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_blob_logging_disabled/metadata.json b/assets/queries/terraform/azure/azure_storage_blob_logging_disabled/metadata.json new file mode 100644 index 00000000000..6433e6ca03d --- /dev/null +++ b/assets/queries/terraform/azure/azure_storage_blob_logging_disabled/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "11fa88c0-2064-48ee-8431-953c7d961c71", + "queryName": "Storage Blob Service Logging Disabled", + "severity": "LOW", + "category": "Observability", + "descriptionText": "Ensures that Storage Logging is enabled for the Blob service for 'Read', 'Write', and 'Delete' requests. This provides an audit trail of operations performed on blobs and containers.", + "descriptionUrl": "https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/storage_account#logging", + "platform": "Terraform", + "cloudProvider": "azure", + "cwe": "CWE-778", + "descriptionID": "11fa88c0", + "riskScore": 2.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_blob_logging_disabled/query.rego b/assets/queries/terraform/azure/azure_storage_blob_logging_disabled/query.rego new file mode 100644 index 00000000000..1580109acde --- /dev/null +++ b/assets/queries/terraform/azure/azure_storage_blob_logging_disabled/query.rego @@ -0,0 +1,76 @@ +package Cx + +is_target_linked(target, sa_name) { + ref := sprintf("azurerm_storage_account.%s.id", [sa_name]) + contains(target, ref) + contains(target, "blobServices/default") +} + +is_target_linked(target, sa_name) { + ref := sprintf("${azurerm_storage_account.%s.id}", [sa_name]) + contains(target, ref) + contains(target, "blobServices/default") +} + +# CASO 1: La cuenta de almacenamiento no tiene ningún Diagnostic Setting para Blobs. +CxPolicy[result] { + doc := input.document[i] + sa := doc.resource.azurerm_storage_account[name] + + not diag_exists_for_sa(doc, name) + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.azurerm_storage_account.%s", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": sprintf("'azurerm_storage_account.%s' should have an 'azurerm_monitor_diagnostic_setting' for its blob service", [name]), + "keyActualValue": sprintf("'azurerm_storage_account.%s' does not have diagnostic logging enabled for blobs", [name]), + } +} + +diag_exists_for_sa(doc, sa_name) { + diag := doc.resource.azurerm_monitor_diagnostic_setting[_] + is_target_linked(diag.target_resource_id, sa_name) +} + +# CASO 2: El Diagnostic Setting existe pero no tiene ningún bloque 'enabled_log'. +CxPolicy[result] { + doc := input.document[i] + diag := doc.resource.azurerm_monitor_diagnostic_setting[diag_name] + + contains(diag.target_resource_id, "blobServices/default") + not diag.enabled_log + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.azurerm_monitor_diagnostic_setting.%s", [diag_name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "Diagnostic Setting should have 'enabled_log' blocks defined", + "keyActualValue": "Diagnostic Setting has no 'enabled_log' blocks", + } +} + +# CASO 3: El Diagnostic Setting tiene bloques 'enabled_log' pero el conjunto está incompleto. +CxPolicy[result] { + doc := input.document[i] + diag := doc.resource.azurerm_monitor_diagnostic_setting[diag_name] + + contains(diag.target_resource_id, "blobServices/default") + diag.enabled_log + + required_categories := {"StorageRead", "StorageWrite", "StorageDelete"} + present_categories := {cat | + log := diag.enabled_log[_] + cat := log.category + } + + not count(required_categories - present_categories) == 0 + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.azurerm_monitor_diagnostic_setting.%s.enabled_log", [diag_name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "All required log categories (StorageRead, StorageWrite, StorageDelete) should be present", + "keyActualValue": "One or more required log categories are missing in the 'enabled_log' configuration", + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_blob_logging_disabled/test/negative1.tf b/assets/queries/terraform/azure/azure_storage_blob_logging_disabled/test/negative1.tf new file mode 100644 index 00000000000..13a3e1790f3 --- /dev/null +++ b/assets/queries/terraform/azure/azure_storage_blob_logging_disabled/test/negative1.tf @@ -0,0 +1,17 @@ +resource "azurerm_storage_account" "pass" { + name = "st-pass" + resource_group_name = "rg" + location = "West Europe" + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_monitor_diagnostic_setting" "pass_diag" { + name = "complete-diag" + target_resource_id = "${azurerm_storage_account.pass.id}/blobServices/default" + storage_account_id = "target" + + enabled_log { category = "StorageRead" } + enabled_log { category = "StorageWrite" } + enabled_log { category = "StorageDelete" } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_blob_logging_disabled/test/positive1.tf b/assets/queries/terraform/azure/azure_storage_blob_logging_disabled/test/positive1.tf new file mode 100644 index 00000000000..ea0d8402fd3 --- /dev/null +++ b/assets/queries/terraform/azure/azure_storage_blob_logging_disabled/test/positive1.tf @@ -0,0 +1,7 @@ +resource "azurerm_storage_account" "fail_none" { + name = "st-no-blob-diag" + resource_group_name = "rg" + location = "West Europe" + account_tier = "Standard" + account_replication_type = "LRS" +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_blob_logging_disabled/test/positive2.tf b/assets/queries/terraform/azure/azure_storage_blob_logging_disabled/test/positive2.tf new file mode 100644 index 00000000000..7c2838bfa7d --- /dev/null +++ b/assets/queries/terraform/azure/azure_storage_blob_logging_disabled/test/positive2.tf @@ -0,0 +1,6 @@ +resource "azurerm_monitor_diagnostic_setting" "fail_no_logs" { + name = "empty-diag" + target_resource_id = "${azurerm_storage_account.example.id}/blobServices/default" + storage_account_id = "id" + # enabled_log bloque ausente +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_blob_logging_disabled/test/positive3.tf b/assets/queries/terraform/azure/azure_storage_blob_logging_disabled/test/positive3.tf new file mode 100644 index 00000000000..d595f8bf0b2 --- /dev/null +++ b/assets/queries/terraform/azure/azure_storage_blob_logging_disabled/test/positive3.tf @@ -0,0 +1,10 @@ +resource "azurerm_monitor_diagnostic_setting" "fail_partial" { + name = "partial-diag" + target_resource_id = "${azurerm_storage_account.example.id}/blobServices/default" + storage_account_id = "id" + + enabled_log { + category = "StorageRead" + } + # Faltan Write y Delete. Solo debe saltar 1 vez. +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_blob_logging_disabled/test/positive_expected_result.json b/assets/queries/terraform/azure/azure_storage_blob_logging_disabled/test/positive_expected_result.json new file mode 100644 index 00000000000..77b0bab39ad --- /dev/null +++ b/assets/queries/terraform/azure/azure_storage_blob_logging_disabled/test/positive_expected_result.json @@ -0,0 +1,20 @@ +[ + { + "queryName": "Storage Blob Service Logging Disabled", + "severity": "LOW", + "line": 1, + "fileName": "positive1.tf" + }, + { + "queryName": "Storage Blob Service Logging Disabled", + "severity": "LOW", + "line": 1, + "fileName": "positive2.tf" + }, + { + "queryName": "Storage Blob Service Logging Disabled", + "severity": "LOW", + "line": 6, + "fileName": "positive3.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_container_immutability_not_locked/README.md b/assets/queries/terraform/azure/azure_storage_container_immutability_not_locked/README.md new file mode 100644 index 00000000000..c09ad4074f5 --- /dev/null +++ b/assets/queries/terraform/azure/azure_storage_container_immutability_not_locked/README.md @@ -0,0 +1,65 @@ +# Regla KICS: Storage Immutability Policy Not Locked + +## Descripción General + +Esta regla verifica que las políticas de inmutabilidad (`azurerm_storage_container_immutability_policy`) aplicadas a los contenedores de Azure Storage estén configuradas en estado **Bloqueado** (`locked = true`). + +Las políticas de inmutabilidad permiten que los datos se almacenen en un formato **WORM** (Write Once, Read Many). En el contexto de control de acceso y protección de datos, existen dos estados críticos: +* **Unlocked (Mutable):** La política protege los datos contra borrado o modificación, pero la política en sí puede ser eliminada o modificada por usuarios con altos privilegios. Es un estado transitorio y no garantiza la inalterabilidad a largo plazo. +* **Locked (Inmutable):** Una vez bloqueada, la política se vuelve irreversible; no puede ser eliminada y el periodo de retención solo puede aumentarse. Este estado es un control de integridad estricto que garantiza que ni siquiera un administrador pueda eliminar los datos antes de tiempo. + +## Lógica de la Regla + +La regla audita los recursos `azurerm_storage_container_immutability_policy` evaluando dos condiciones de control: +1. **Omisión del Atributo:** Si no se define el atributo `locked`, Azure asume por defecto el estado "Unlocked", permitiendo que la protección sea removida. +2. **Valor Incorrecto:** Si el atributo `locked` se establece explícitamente en `false`. + +## Casos de Fallo Detectados + +### Caso 1: Atributo de Bloqueo Ausente + +* **Descripción:** Se define una política de retención pero no se especifica si debe estar bloqueada. Por defecto, Azure la crea en estado "Unlocked". +* **Ejemplo de Código Terraform Problemático:** + ```terraform + resource "azurerm_storage_container_immutability_policy" "fail_missing" { + storage_container_resource_manager_id = azurerm_storage_container.example.id + retention_period_in_days = 365 + # Falta locked = true + } + ``` +* **Ubicación de la Alerta:** Sobre el recurso raíz `azurerm_storage_container_immutability_policy`. + +--- + +### Caso 2: Política Mutable (Unlocked) + +* **Descripción:** El atributo `locked` se establece explícitamente en `false`, lo que permite que la política de inmutabilidad sea eliminable por usuarios autorizados. +* **Ejemplo de Código Terraform Problemático:** + ```terraform + resource "azurerm_storage_container_immutability_policy" "fail_explicit" { + storage_container_resource_manager_id = azurerm_storage_container.example.id + retention_period_in_days = 730 + locked = false # <-- PROBLEMA DETECTADO + } + ``` +* **Ubicación de la Alerta:** Atributo `locked`. + +## Recurso Involucrado + +* `azurerm_storage_container_immutability_policy` + +## Solución + +Establezca el atributo `locked` en `true` para asegurar que el control de integridad de los datos sea permanente y cumpla con los requisitos WORM. + +> [!WARNING] +> **Advertencia Crítica:** Una vez que una política se bloquea en Azure, **no puede eliminarse**. Solo podrá borrar el contenedor una vez que el periodo de retención de todos los objetos haya expirado. Valide cuidadosamente los días de retención antes de aplicar este cambio en producción. + +```terraform +resource "azurerm_storage_container_immutability_policy" "secure_policy" { + storage_container_resource_manager_id = azurerm_storage_container.example.id + retention_period_in_days = 365 + + # SOLUCIÓN: Bloqueo de política habilitado para integridad WORM + locked = true +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_container_immutability_not_locked/metadata.json b/assets/queries/terraform/azure/azure_storage_container_immutability_not_locked/metadata.json new file mode 100644 index 00000000000..eaf61fafe48 --- /dev/null +++ b/assets/queries/terraform/azure/azure_storage_container_immutability_not_locked/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "9b959753-3a9b-41e7-82b2-28bef91e6b53", + "queryName": "Storage Immutability Policy Not Locked", + "severity": "MEDIUM", + "category": "Access Control", + "descriptionText": "Ensures that the Immutability Policy for Azure Storage Containers is set to 'Locked'. An unlocked policy can be removed or modified, failing to provide true WORM (Write Once, Read Many) compliance for business-critical data.", + "descriptionUrl": "https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/storage_container_immutability_policy#locked", + "platform": "Terraform", + "cloudProvider": "azure", + "cwe": "CWE-284", + "descriptionID": "9b959753", + "riskScore": 5.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_container_immutability_not_locked/query.rego b/assets/queries/terraform/azure/azure_storage_container_immutability_not_locked/query.rego new file mode 100644 index 00000000000..233dc06fe1c --- /dev/null +++ b/assets/queries/terraform/azure/azure_storage_container_immutability_not_locked/query.rego @@ -0,0 +1,33 @@ +package Cx + +# REGLA 1: El atributo 'locked' no está definido (Default es false/unlocked). +CxPolicy[result] { + doc := input.document[i] + policy := doc.resource.azurerm_storage_container_immutability_policy[name] + + object.get(policy, "locked", "undefined") == "undefined" + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.azurerm_storage_container_immutability_policy.%s", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": sprintf("'azurerm_storage_container_immutability_policy.%s' should have 'locked' set to true", [name]), + "keyActualValue": sprintf("'azurerm_storage_container_immutability_policy.%s' is missing 'locked' attribute (default is false)", [name]), + } +} + +# REGLA 2: El atributo 'locked' está explícitamente a false. +CxPolicy[result] { + doc := input.document[i] + policy := doc.resource.azurerm_storage_container_immutability_policy[name] + + policy.locked == false + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.azurerm_storage_container_immutability_policy.%s.locked", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "'locked' should be set to true", + "keyActualValue": "'locked' is set to false", + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_container_immutability_not_locked/test/negative1.tf b/assets/queries/terraform/azure/azure_storage_container_immutability_not_locked/test/negative1.tf new file mode 100644 index 00000000000..24a49a6b0a9 --- /dev/null +++ b/assets/queries/terraform/azure/azure_storage_container_immutability_not_locked/test/negative1.tf @@ -0,0 +1,5 @@ +resource "azurerm_storage_container_immutability_policy" "pass" { + storage_container_resource_manager_id = "some-resource-id" + immutability_period_in_days = 90 + locked = true +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_container_immutability_not_locked/test/positive1.tf b/assets/queries/terraform/azure/azure_storage_container_immutability_not_locked/test/positive1.tf new file mode 100644 index 00000000000..f245a916668 --- /dev/null +++ b/assets/queries/terraform/azure/azure_storage_container_immutability_not_locked/test/positive1.tf @@ -0,0 +1,4 @@ +resource "azurerm_storage_container_immutability_policy" "fail_omission" { + storage_container_resource_manager_id = "some-resource-id" + immutability_period_in_days = 90 +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_container_immutability_not_locked/test/positive2.tf b/assets/queries/terraform/azure/azure_storage_container_immutability_not_locked/test/positive2.tf new file mode 100644 index 00000000000..65f0fe13e6e --- /dev/null +++ b/assets/queries/terraform/azure/azure_storage_container_immutability_not_locked/test/positive2.tf @@ -0,0 +1,5 @@ +resource "azurerm_storage_container_immutability_policy" "fail_explicit" { + storage_container_resource_manager_id = "some-resource-id" + immutability_period_in_days = 90 + locked = false +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_container_immutability_not_locked/test/positive_expected_result.json b/assets/queries/terraform/azure/azure_storage_container_immutability_not_locked/test/positive_expected_result.json new file mode 100644 index 00000000000..3559388eb05 --- /dev/null +++ b/assets/queries/terraform/azure/azure_storage_container_immutability_not_locked/test/positive_expected_result.json @@ -0,0 +1,14 @@ +[ + { + "queryName": "Storage Immutability Policy Not Locked", + "severity": "MEDIUM", + "line": 1, + "fileName": "positive1.tf" + }, + { + "queryName": "Storage Immutability Policy Not Locked", + "severity": "MEDIUM", + "line": 4, + "fileName": "positive2.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_critical_data_cmk_manual/README.md b/assets/queries/terraform/azure/azure_storage_critical_data_cmk_manual/README.md new file mode 100644 index 00000000000..1304966919e --- /dev/null +++ b/assets/queries/terraform/azure/azure_storage_critical_data_cmk_manual/README.md @@ -0,0 +1,62 @@ +# Regla KICS: Critical Data Storage CMK (Manual) + +## Descripción General + +Esta regla identifica cuentas de almacenamiento de Azure (`azurerm_storage_account`) que utilizan cifrado gestionado por Microsoft (**Platform-Managed Keys**). + +El cifrado en reposo es automático en Azure, pero el uso de claves gestionadas por la plataforma no siempre es suficiente para cumplir con normativas de soberanía de datos o requisitos de seguridad de datos críticos. Esta regla actúa como un control de **auditoría manual**: identifica recursos sin **Customer-Managed Keys (CMK)** para que el equipo de seguridad valide si los datos contenidos (financieros, PII, secretos industriales) requieren que la organización posea y gestione las claves de cifrado en su propio Key Vault. + +## Lógica de la Regla + +La política audita el recurso `azurerm_storage_account` evaluando dos escenarios: +1. **Ausencia de Configuración CMK:** Si el bloque `customer_managed_key` no está presente, la cuenta utiliza las claves por defecto de Azure. +2. **Configuración Incompleta:** Si el bloque `customer_managed_key` existe pero no define el atributo `key_vault_key_id`, el cifrado CMK no se está aplicando efectivamente. + +## Casos de Fallo Detectados + +### Caso 1: Revisión de Sensibilidad de Datos Requerida (Cifrado Default) + +* **Descripción:** La cuenta utiliza el cifrado base de Azure. Se requiere validación manual para determinar si la criticidad de los datos exige el paso a CMK. +* **Ubicación de la Alerta:** Recurso `azurerm_storage_account`. + +### Caso 2: Bloque CMK sin Clave Asociada + +* **Descripción:** Se ha declarado el bloque de claves gestionadas por el cliente pero se ha omitido el identificador de la clave criptográfica. +* **Ejemplo de Código Terraform Problemático:** + ```terraform + resource "azurerm_storage_account" "fail_incomplete" { + name = "stincomplete" + # ... + customer_managed_key { + user_assigned_identity_id = azurerm_user_assigned_identity.example.id + # key_vault_key_id es opcional en el esquema pero necesario para CMK + } + } + ``` +* **Ubicación de la Alerta:** Bloque `customer_managed_key`. + +## Recurso Involucrado + +* `azurerm_storage_account` + +## Solución + +Si tras la revisión manual se confirma que los datos son críticos, implemente CMK vinculando una clave de Azure Key Vault. + +```terraform +resource "azurerm_storage_account" "secure_critical" { + name = "stcriticaldata" + resource_group_name = azurerm_resource_group.example.name + location = azurerm_resource_group.example.location + account_tier = "Standard" + account_replication_type = "GRS" + + identity { + type = "SystemAssigned" + } + + customer_managed_key { + # SOLUCIÓN: Definir explícitamente la clave de Key Vault + key_vault_key_id = azurerm_key_vault_key.example.id + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_critical_data_cmk_manual/metadata.json b/assets/queries/terraform/azure/azure_storage_critical_data_cmk_manual/metadata.json new file mode 100644 index 00000000000..0c21a11ee0b --- /dev/null +++ b/assets/queries/terraform/azure/azure_storage_critical_data_cmk_manual/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "20fcb3ef-4524-4d45-baa9-60b0eb229beb", + "queryName": "Critical Data Storage CMK (Manual)", + "severity": "INFO", + "category": "Encryption", + "descriptionText": "Identifies Storage Accounts using default Platform-Managed Keys. If these accounts store business-critical or sensitive data, they must be encrypted using Customer-Managed Keys (CMK). Manual verification of data sensitivity is required.", + "descriptionUrl": "https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/storage_account#customer_managed_key", + "platform": "Terraform", + "cloudProvider": "azure", + "cwe": "CWE-312", + "descriptionID": "20fcb3ef", + "riskScore": 0.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_critical_data_cmk_manual/query.rego b/assets/queries/terraform/azure/azure_storage_critical_data_cmk_manual/query.rego new file mode 100644 index 00000000000..e888549ca7b --- /dev/null +++ b/assets/queries/terraform/azure/azure_storage_critical_data_cmk_manual/query.rego @@ -0,0 +1,34 @@ +package Cx + +# REGLA 1: El bloque 'customer_managed_key' no existe. +CxPolicy[result] { + doc := input.document[i] + sa := doc.resource.azurerm_storage_account[name] + + not sa.customer_managed_key + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.azurerm_storage_account.%s", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": sprintf("'azurerm_storage_account.%s' should use CMK if hosting critical data (Manual Verification)", [name]), + "keyActualValue": sprintf("'azurerm_storage_account.%s' is using Platform-Managed Keys", [name]), + } +} + +# REGLA 2: El bloque existe pero el atributo 'key_vault_key_id' no está definido. +CxPolicy[result] { + doc := input.document[i] + sa := doc.resource.azurerm_storage_account[name] + + sa.customer_managed_key + object.get(sa.customer_managed_key, "key_vault_key_id", "undefined") == "undefined" + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.azurerm_storage_account.%s.customer_managed_key", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "If 'customer_managed_key' block is defined, it should include 'key_vault_key_id' for CMK encryption", + "keyActualValue": "'key_vault_key_id' is not defined within the 'customer_managed_key' block", + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_critical_data_cmk_manual/test/negative1.tf b/assets/queries/terraform/azure/azure_storage_critical_data_cmk_manual/test/negative1.tf new file mode 100644 index 00000000000..39bd87c0f6e --- /dev/null +++ b/assets/queries/terraform/azure/azure_storage_critical_data_cmk_manual/test/negative1.tf @@ -0,0 +1,16 @@ +resource "azurerm_storage_account" "pass" { + name = "st-pass-cmk" + resource_group_name = "rg" + location = "West Europe" + account_tier = "Standard" + account_replication_type = "LRS" + + identity { + type = "SystemAssigned" + } + + customer_managed_key { + key_vault_key_id = "https://kv.vault.azure.net/keys/key/v1" + user_assigned_identity_id = "some-id" + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_critical_data_cmk_manual/test/positive1.tf b/assets/queries/terraform/azure/azure_storage_critical_data_cmk_manual/test/positive1.tf new file mode 100644 index 00000000000..471c97fbe07 --- /dev/null +++ b/assets/queries/terraform/azure/azure_storage_critical_data_cmk_manual/test/positive1.tf @@ -0,0 +1,7 @@ +resource "azurerm_storage_account" "fail_1" { + name = "st-no-cmk-block" + resource_group_name = "rg" + location = "West Europe" + account_tier = "Standard" + account_replication_type = "LRS" +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_critical_data_cmk_manual/test/positive2.tf b/assets/queries/terraform/azure/azure_storage_critical_data_cmk_manual/test/positive2.tf new file mode 100644 index 00000000000..b774263629d --- /dev/null +++ b/assets/queries/terraform/azure/azure_storage_critical_data_cmk_manual/test/positive2.tf @@ -0,0 +1,11 @@ +resource "azurerm_storage_account" "fail_2" { + name = "st-block-no-id" + resource_group_name = "rg" + location = "West Europe" + account_tier = "Standard" + account_replication_type = "LRS" + + customer_managed_key { + user_assigned_identity_id = "some-id" + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_critical_data_cmk_manual/test/positive_expected_result.json b/assets/queries/terraform/azure/azure_storage_critical_data_cmk_manual/test/positive_expected_result.json new file mode 100644 index 00000000000..3c78101dd5c --- /dev/null +++ b/assets/queries/terraform/azure/azure_storage_critical_data_cmk_manual/test/positive_expected_result.json @@ -0,0 +1,14 @@ +[ + { + "queryName": "Critical Data Storage CMK (Manual)", + "severity": "INFO", + "line": 1, + "fileName": "positive1.tf" + }, + { + "queryName": "Critical Data Storage CMK (Manual)", + "severity": "INFO", + "line": 8, + "fileName": "positive2.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_queue_logging_disabled/README.md b/assets/queries/terraform/azure/azure_storage_queue_logging_disabled/README.md new file mode 100644 index 00000000000..3fc500b38a8 --- /dev/null +++ b/assets/queries/terraform/azure/azure_storage_queue_logging_disabled/README.md @@ -0,0 +1,93 @@ +# Regla KICS: Storage Queue Service Logging Disabled + +## Descripción General + +Esta regla verifica que el **Logging de Almacenamiento** (Storage Analytics Logging) esté habilitado para el servicio de **Colas (Queue Service)** en las cuentas de almacenamiento de Azure. + +El registro de auditoría es un componente fundamental de la observabilidad. Permite rastrear la actividad detallada en el plano de datos, capturando quién y cuándo interactuó con los mensajes de la cola. La política valida que se registren las siguientes operaciones: +* **Read (Lectura):** Operaciones como la visualización o extracción de mensajes. +* **Write (Escritura):** Operaciones de inserción o actualización de mensajes. +* **Delete (Borrado):** Operaciones de eliminación de mensajes o vaciado de colas. + +Sin esta configuración, las organizaciones pierden la trazabilidad necesaria para investigar comportamientos anómalos o fugas de información a través del servicio de mensajería. + +## Lógica de la Regla + +La política audita tanto la configuración embebida en la cuenta de almacenamiento como el recurso específico de propiedades: +1. **Identificación de Atributos:** Busca la presencia de `queue_properties` en el recurso principal o instancias del recurso independiente. +2. **Validación de Auditoría:** Asegura la existencia del bloque `logging`. +3. **Verificación de Acciones:** Comprueba que `read`, `write` y `delete` estén configurados explícitamente como `true`. + +## Casos de Fallo Detectados + +A continuación se describen los escenarios que esta política detectará. + +--- + +### Caso 1: Logging Embebido Ausente + +* **Descripción:** Se definen propiedades de cola en la cuenta de almacenamiento pero no se incluye la configuración de registro. +* **Ubicación de la Alerta:** Bloque `queue_properties` del recurso `azurerm_storage_account`. + +--- + +### Caso 2: Configuración Embebida Incorrecta + +* **Descripción:** El bloque de registro existe en la cuenta de almacenamiento pero alguna de las acciones críticas está desactivada. +* **Ejemplo de Código Terraform Problemático:** + ```terraform + resource "azurerm_storage_account" "fail_logging" { + # ... + queue_properties { + logging { + read = false # <-- PROBLEMA + write = true + delete = true + version = "1.0" + } + } + } + ``` +* **Ubicación de la Alerta:** Atributo `logging` del recurso `azurerm_storage_account`. + +--- + +### Caso 3: Recurso Standalone sin Logging + +* **Descripción:** Se utiliza el recurso `azurerm_storage_account_queue_properties` pero se omite completamente el bloque de registro. +* **Ubicación de la Alerta:** Recurso `azurerm_storage_account_queue_properties`. + +--- + +### Caso 4: Recurso Standalone con Configuración Incorrecta + +* **Descripción:** El recurso independiente de propiedades tiene el bloque de registro, pero con acciones deshabilitadas. +* **Ubicación de la Alerta:** Atributo `logging` del recurso `azurerm_storage_account_queue_properties`. + +## Recursos Involucrados + +* `azurerm_storage_account` +* `azurerm_storage_account_queue_properties` + +## Solución + +Habilite el registro para todas las operaciones (`read`, `write`, `delete`) dentro de la configuración del servicio de colas. + +```terraform +resource "azurerm_storage_account" "secure_queue" { + name = "stsecurequeue" + resource_group_name = azurerm_resource_group.example.name + location = azurerm_resource_group.example.location + account_tier = "Standard" + account_replication_type = "LRS" + + queue_properties { + logging { + read = true + write = true + delete = true + version = "1.0" + retention_policy_days = 30 + } + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_queue_logging_disabled/metadata.json b/assets/queries/terraform/azure/azure_storage_queue_logging_disabled/metadata.json new file mode 100644 index 00000000000..24c682371b3 --- /dev/null +++ b/assets/queries/terraform/azure/azure_storage_queue_logging_disabled/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "cddbdd74-3736-4aec-ba57-43b4db315bc2", + "queryName": "Storage Queue Service Logging Disabled", + "severity": "LOW", + "category": "Observability", + "descriptionText": "Ensures that Storage Logging is enabled for the Queue service for 'Read', 'Write', and 'Delete' requests. This provides an audit trail of operations performed on the queues.", + "descriptionUrl": "https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/storage_account#logging", + "platform": "Terraform", + "cloudProvider": "azure", + "cwe": "CWE-778", + "descriptionID": "cddbdd74", + "riskScore": 2.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_queue_logging_disabled/query.rego b/assets/queries/terraform/azure/azure_storage_queue_logging_disabled/query.rego new file mode 100644 index 00000000000..0c5fbe8a745 --- /dev/null +++ b/assets/queries/terraform/azure/azure_storage_queue_logging_disabled/query.rego @@ -0,0 +1,74 @@ +package Cx + +is_logging_valid(logging) { + logging.read == true + logging.write == true + logging.delete == true +} + +# CASO 1: Bloque 'logging' ausente en 'queue_properties' de azurerm_storage_account. +CxPolicy[result] { + doc := input.document[i] + sa := doc.resource.azurerm_storage_account[name] + + sa.queue_properties + not sa.queue_properties.logging + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.azurerm_storage_account.%s.queue_properties", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "queue_properties.logging should be defined with read, write, and delete enabled", + "keyActualValue": "queue_properties.logging is missing", + } +} + +# CASO 2: Configuración de 'logging' incorrecta en azurerm_storage_account. +CxPolicy[result] { + doc := input.document[i] + sa := doc.resource.azurerm_storage_account[name] + + logging := sa.queue_properties.logging + not is_logging_valid(logging) + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.azurerm_storage_account.%s.queue_properties.logging", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "logging should have read, write, and delete set to true", + "keyActualValue": "logging has one or more required actions (read, write, delete) disabled", + } +} + +# CASO 3: Bloque 'logging' ausente en el recurso azurerm_storage_account_queue_properties. +CxPolicy[result] { + doc := input.document[i] + props := doc.resource.azurerm_storage_account_queue_properties[name] + + not props.logging + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.azurerm_storage_account_queue_properties.%s", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "logging block should be defined in queue properties", + "keyActualValue": "logging block is missing", + } +} + +# CASO 4: Configuración de 'logging' incorrecta en azurerm_storage_account_queue_properties. +CxPolicy[result] { + doc := input.document[i] + props := doc.resource.azurerm_storage_account_queue_properties[name] + + logging := props.logging + not is_logging_valid(logging) + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.azurerm_storage_account_queue_properties.%s.logging", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "logging should have read, write, and delete set to true", + "keyActualValue": "logging is missing one or more required actions", + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_queue_logging_disabled/test/negative1.tf b/assets/queries/terraform/azure/azure_storage_queue_logging_disabled/test/negative1.tf new file mode 100644 index 00000000000..ee304ada58b --- /dev/null +++ b/assets/queries/terraform/azure/azure_storage_queue_logging_disabled/test/negative1.tf @@ -0,0 +1,17 @@ +resource "azurerm_storage_account" "pass_inline" { + name = "stpassinline" + resource_group_name = "rg" + location = "West Europe" + account_tier = "Standard" + account_replication_type = "LRS" + + queue_properties { + logging { + read = true + write = true + delete = true + version = "1.0" + retention_policy_days = 7 + } + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_queue_logging_disabled/test/negative2.tf b/assets/queries/terraform/azure/azure_storage_queue_logging_disabled/test/negative2.tf new file mode 100644 index 00000000000..d479376aa5a --- /dev/null +++ b/assets/queries/terraform/azure/azure_storage_queue_logging_disabled/test/negative2.tf @@ -0,0 +1,19 @@ +resource "azurerm_storage_account" "base" { + name = "stbase" + resource_group_name = "rg" + location = "West Europe" + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_storage_account_queue_properties" "pass_standalone" { + storage_account_id = azurerm_storage_account.base.id + + logging { + read = true + write = true + delete = true + version = "1.0" + retention_policy_days = 10 + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_queue_logging_disabled/test/positive1.tf b/assets/queries/terraform/azure/azure_storage_queue_logging_disabled/test/positive1.tf new file mode 100644 index 00000000000..aa4a19e1192 --- /dev/null +++ b/assets/queries/terraform/azure/azure_storage_queue_logging_disabled/test/positive1.tf @@ -0,0 +1,11 @@ +resource "azurerm_storage_account" "fail_1" { + name = "stfail1" + resource_group_name = "rg" + location = "West Europe" + account_tier = "Standard" + account_replication_type = "LRS" + + queue_properties { + # Falta bloque logging + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_queue_logging_disabled/test/positive2.tf b/assets/queries/terraform/azure/azure_storage_queue_logging_disabled/test/positive2.tf new file mode 100644 index 00000000000..61cd3bf4219 --- /dev/null +++ b/assets/queries/terraform/azure/azure_storage_queue_logging_disabled/test/positive2.tf @@ -0,0 +1,16 @@ +resource "azurerm_storage_account" "fail_2" { + name = "stfail2" + resource_group_name = "rg" + location = "West Europe" + account_tier = "Standard" + account_replication_type = "LRS" + + queue_properties { + logging { + read = true + write = false + delete = true + version = "1.0" + } + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_queue_logging_disabled/test/positive3.tf b/assets/queries/terraform/azure/azure_storage_queue_logging_disabled/test/positive3.tf new file mode 100644 index 00000000000..97b7aed369b --- /dev/null +++ b/assets/queries/terraform/azure/azure_storage_queue_logging_disabled/test/positive3.tf @@ -0,0 +1,3 @@ +resource "azurerm_storage_account_queue_properties" "fail_3" { + storage_account_id = "some-id" +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_queue_logging_disabled/test/positive4.tf b/assets/queries/terraform/azure/azure_storage_queue_logging_disabled/test/positive4.tf new file mode 100644 index 00000000000..218bdef5585 --- /dev/null +++ b/assets/queries/terraform/azure/azure_storage_queue_logging_disabled/test/positive4.tf @@ -0,0 +1,9 @@ +resource "azurerm_storage_account_queue_properties" "fail_4" { + storage_account_id = "some-id" + logging { + read = false + write = true + delete = true + version = "1.0" + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_queue_logging_disabled/test/positive_expected_result.json b/assets/queries/terraform/azure/azure_storage_queue_logging_disabled/test/positive_expected_result.json new file mode 100644 index 00000000000..253c872e665 --- /dev/null +++ b/assets/queries/terraform/azure/azure_storage_queue_logging_disabled/test/positive_expected_result.json @@ -0,0 +1,26 @@ +[ + { + "queryName": "Storage Queue Service Logging Disabled", + "severity": "LOW", + "line": 8, + "fileName": "positive1.tf" + }, + { + "queryName": "Storage Queue Service Logging Disabled", + "severity": "LOW", + "line": 9, + "fileName": "positive2.tf" + }, + { + "queryName": "Storage Queue Service Logging Disabled", + "severity": "LOW", + "line": 1, + "fileName": "positive3.tf" + }, + { + "queryName": "Storage Queue Service Logging Disabled", + "severity": "LOW", + "line": 3, + "fileName": "positive4.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_table_logging_disabled/README.md b/assets/queries/terraform/azure/azure_storage_table_logging_disabled/README.md new file mode 100644 index 00000000000..ee83e7ad654 --- /dev/null +++ b/assets/queries/terraform/azure/azure_storage_table_logging_disabled/README.md @@ -0,0 +1,52 @@ +# Regla KICS: Storage Table Service Logging Disabled + +## Descripción General + +Esta regla verifica que el registro de diagnóstico (**Diagnostic Settings**) esté habilitado y configurado íntegramente para el servicio de **Tablas (Table Service)** en las cuentas de almacenamiento de Azure. + +La auditoría de las tablas NoSQL de Azure Storage permite capturar telemetría sobre quién accede y modifica los datos almacenados. La política asegura que se capturen las tres categorías de operaciones esenciales para una trazabilidad completa: +* **StorageRead:** Auditoría de consultas de entidades y lectura de metadatos de tablas. +* **StorageWrite:** Auditoría de inserciones, actualizaciones y "upserts" de datos. +* **StorageDelete:** Auditoría de eliminaciones de entidades o de la estructura de la tabla. + +Sin estos registros activos, las organizaciones carecen de la telemetría necesaria para identificar accesos no autorizados a datos sensibles o investigar errores en la manipulación de registros NoSQL. + +## Lógica de la Regla + +La política audita el código Terraform evaluando tres niveles de cumplimiento: +1. **Existencia de Recurso:** Verifica que cada `azurerm_storage_account` tenga un recurso `azurerm_monitor_diagnostic_setting` vinculado a su endpoint de tablas (`/tableServices/default`). +2. **Presencia de Logs:** Alerta si el recurso de diagnóstico existe pero el bloque `enabled_log` está ausente. +3. **Integridad de Categorías:** Analiza que las categorías `StorageRead`, `StorageWrite` y `StorageDelete` estén presentes simultáneamente. Si el conjunto está incompleto, la alerta apunta directamente al bloque `enabled_log`. + +## Casos de Fallo Detectados + +### Caso 1: Servicio de Tablas sin Diagnostic Settings +* **Descripción:** La cuenta de almacenamiento no tiene configurado ningún destino de registro para tablas. +* **Ubicación:** `azurerm_storage_account`. + +### Caso 2: Diagnostic Setting sin bloques de Log +* **Descripción:** El recurso de diagnóstico existe pero la configuración de registros está vacía. +* **Ubicación:** `azurerm_monitor_diagnostic_setting`. + +### Caso 3: Auditoría de Tablas Incompleta +* **Descripción:** Faltan una o más categorías críticas en la configuración de logs. +* **Ubicación:** Bloque `enabled_log` dentro de `azurerm_monitor_diagnostic_setting`. + +## Recursos Involucrados +* `azurerm_storage_account` +* `azurerm_monitor_diagnostic_setting` + +## Solución + +Configure un Diagnostic Setting completo para el servicio de tablas. + +```terraform +resource "azurerm_monitor_diagnostic_setting" "secure_table_logging" { + name = "table-audit-complete" + target_resource_id = "${azurerm_storage_account.example.id}/tableServices/default" + storage_account_id = azurerm_storage_account.logs.id + + enabled_log { category = "StorageRead" } + enabled_log { category = "StorageWrite" } + enabled_log { category = "StorageDelete" } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_table_logging_disabled/metadata.json b/assets/queries/terraform/azure/azure_storage_table_logging_disabled/metadata.json new file mode 100644 index 00000000000..7ab0ddb59aa --- /dev/null +++ b/assets/queries/terraform/azure/azure_storage_table_logging_disabled/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "0d29e3cf-0033-4ca6-9b97-fa3e6c4d25b8", + "queryName": "Storage Table Service Logging Disabled", + "severity": "LOW", + "category": "Observability", + "descriptionText": "Ensures that Storage Logging is enabled for the Table service for 'Read', 'Write', and 'Delete' requests. This provides an audit trail of operations performed on NoSQL tables within the storage account.", + "descriptionUrl": "https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/storage_account#logging", + "platform": "Terraform", + "cloudProvider": "azure", + "cwe": "CWE-778", + "descriptionID": "0d29e3cf", + "riskScore": 2.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_table_logging_disabled/query.rego b/assets/queries/terraform/azure/azure_storage_table_logging_disabled/query.rego new file mode 100644 index 00000000000..0def7c4add9 --- /dev/null +++ b/assets/queries/terraform/azure/azure_storage_table_logging_disabled/query.rego @@ -0,0 +1,76 @@ +package Cx + +is_target_linked(target, sa_name) { + ref := sprintf("azurerm_storage_account.%s.id", [sa_name]) + contains(target, ref) + contains(target, "tableServices/default") +} + +is_target_linked(target, sa_name) { + ref := sprintf("${azurerm_storage_account.%s.id}", [sa_name]) + contains(target, ref) + contains(target, "tableServices/default") +} + +# CASO 1: La cuenta de almacenamiento no tiene ningún Diagnostic Setting para Tablas. +CxPolicy[result] { + doc := input.document[i] + sa := doc.resource.azurerm_storage_account[name] + + not diag_exists_for_sa(doc, name) + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.azurerm_storage_account.%s", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": sprintf("'azurerm_storage_account.%s' should have an 'azurerm_monitor_diagnostic_setting' for its table service", [name]), + "keyActualValue": sprintf("'azurerm_storage_account.%s' does not have diagnostic logging enabled for tables", [name]), + } +} + +diag_exists_for_sa(doc, sa_name) { + diag := doc.resource.azurerm_monitor_diagnostic_setting[_] + is_target_linked(diag.target_resource_id, sa_name) +} + +# CASO 2: El Diagnostic Setting existe pero no tiene ningún bloque 'enabled_log'. +CxPolicy[result] { + doc := input.document[i] + diag := doc.resource.azurerm_monitor_diagnostic_setting[diag_name] + + contains(diag.target_resource_id, "tableServices/default") + not diag.enabled_log + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.azurerm_monitor_diagnostic_setting.%s", [diag_name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "Diagnostic Setting should have 'enabled_log' blocks defined", + "keyActualValue": "Diagnostic Setting has no 'enabled_log' blocks", + } +} + +# CASO 3: El Diagnostic Setting tiene bloques 'enabled_log' pero el conjunto está incompleto. +CxPolicy[result] { + doc := input.document[i] + diag := doc.resource.azurerm_monitor_diagnostic_setting[diag_name] + + contains(diag.target_resource_id, "tableServices/default") + diag.enabled_log + + required_categories := {"StorageRead", "StorageWrite", "StorageDelete"} + present_categories := {cat | + log := diag.enabled_log[_] + cat := log.category + } + + not count(required_categories - present_categories) == 0 + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.azurerm_monitor_diagnostic_setting.%s.enabled_log", [diag_name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "All required log categories (StorageRead, StorageWrite, StorageDelete) should be present", + "keyActualValue": "One or more required log categories are missing in the 'enabled_log' configuration for Table service", + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_table_logging_disabled/test/negative1.tf b/assets/queries/terraform/azure/azure_storage_table_logging_disabled/test/negative1.tf new file mode 100644 index 00000000000..f426550f82b --- /dev/null +++ b/assets/queries/terraform/azure/azure_storage_table_logging_disabled/test/negative1.tf @@ -0,0 +1,17 @@ +resource "azurerm_storage_account" "pass" { + name = "st-table-pass" + resource_group_name = "rg" + location = "West Europe" + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_monitor_diagnostic_setting" "pass_table_diag" { + name = "complete-table-diag" + target_resource_id = "${azurerm_storage_account.pass.id}/tableServices/default" + storage_account_id = "target" + + enabled_log { category = "StorageRead" } + enabled_log { category = "StorageWrite" } + enabled_log { category = "StorageDelete" } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_table_logging_disabled/test/positive1.tf b/assets/queries/terraform/azure/azure_storage_table_logging_disabled/test/positive1.tf new file mode 100644 index 00000000000..45c4a09dfc7 --- /dev/null +++ b/assets/queries/terraform/azure/azure_storage_table_logging_disabled/test/positive1.tf @@ -0,0 +1,7 @@ +resource "azurerm_storage_account" "fail_none" { + name = "st-no-table-diag" + resource_group_name = "rg" + location = "West Europe" + account_tier = "Standard" + account_replication_type = "LRS" +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_table_logging_disabled/test/positive2.tf b/assets/queries/terraform/azure/azure_storage_table_logging_disabled/test/positive2.tf new file mode 100644 index 00000000000..b7399c3f233 --- /dev/null +++ b/assets/queries/terraform/azure/azure_storage_table_logging_disabled/test/positive2.tf @@ -0,0 +1,6 @@ +resource "azurerm_monitor_diagnostic_setting" "fail_no_logs" { + name = "no-logs-diag" + target_resource_id = "${azurerm_storage_account.example.id}/tableServices/default" + storage_account_id = "id" + # No hay bloques enabled_log +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_table_logging_disabled/test/positive3.tf b/assets/queries/terraform/azure/azure_storage_table_logging_disabled/test/positive3.tf new file mode 100644 index 00000000000..ed9882e5258 --- /dev/null +++ b/assets/queries/terraform/azure/azure_storage_table_logging_disabled/test/positive3.tf @@ -0,0 +1,18 @@ +resource "azurerm_storage_account" "example" { + name = "st-table-partial" + resource_group_name = "rg" + location = "West Europe" + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_monitor_diagnostic_setting" "fail_partial" { + name = "partial-table-diag" + target_resource_id = "${azurerm_storage_account.example.id}/tableServices/default" + storage_account_id = "target" + + # Faltan categorías de log + enabled_log { + category = "StorageRead" + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_table_logging_disabled/test/positive_expected_result.json b/assets/queries/terraform/azure/azure_storage_table_logging_disabled/test/positive_expected_result.json new file mode 100644 index 00000000000..b853eeccf65 --- /dev/null +++ b/assets/queries/terraform/azure/azure_storage_table_logging_disabled/test/positive_expected_result.json @@ -0,0 +1,20 @@ +[ + { + "queryName": "Storage Table Service Logging Disabled", + "severity": "LOW", + "line": 1, + "fileName": "positive1.tf" + }, + { + "queryName": "Storage Table Service Logging Disabled", + "severity": "LOW", + "line": 1, + "fileName": "positive2.tf" + }, + { + "queryName": "Storage Table Service Logging Disabled", + "severity": "LOW", + "line": 15, + "fileName": "positive3.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_access_approval_disabled/README.md b/assets/queries/terraform/gcp/gcp_access_approval_disabled/README.md new file mode 100644 index 00000000000..dfa9087e47e --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_access_approval_disabled/README.md @@ -0,0 +1,50 @@ +# Regla KICS: GCP Access Approval Disabled + +## Descripción General + +Esta regla de severidad **MEDIA** audita el cumplimiento del control de acceso de terceros en **Google Cloud (GCP)** para los recursos `google_project`. + +**Access Approval** es un control de seguridad avanzado que permite a las organizaciones establecer un paso de aprobación explícito antes de que el personal de Google (Ingeniería o Soporte) pueda acceder a los datos de sus clientes. Mientras que Google cifra los datos por defecto y restringe el acceso mediante políticas internas, Access Approval otorga al cliente la soberanía final: cualquier intento de acceso genera una solicitud por correo electrónico o mediante Cloud Pub/Sub que el cliente debe aprobar manualmente. + +Sin esta configuración, se asume que el personal de Google puede acceder a los recursos para fines de soporte técnico bajo los términos estándar del contrato, lo que puede no ser suficiente para empresas bajo regulaciones estrictas. + +## Lógica de la Regla + +La política realiza dos validaciones en el código Terraform: +1. **Existencia de la Configuración:** Detecta si un `google_project` carece de un recurso `google_access_approval_project_settings` que lo gestione. +2. **Inscripción de Servicios:** Verifica que, de existir la configuración, esta incluya al menos un bloque `enrolled_services`. Un recurso de configuración sin servicios inscritos no protege activamente ningún producto de GCP. + +## Casos de Fallo Detectados + +A continuación se describen los escenarios que esta política detectará. + +--- + +### Caso 1: Proyecto sin Access Approval +* **Descripción:** Se provisiona un proyecto en GCP pero no se implementa el flujo de aprobación de acceso de Google. +* **Ubicación de la Alerta:** Bloque del recurso `google_project`. + +### Caso 2: Configuración de Servicios Ausente +* **Descripción:** Se define el recurso de configuración de Access Approval pero se deja vacío el bloque de servicios protegidos. +* **Ubicación de la Alerta:** Recurso `google_access_approval_project_settings`. + +## Recursos Involucrados + +* `google_project` +* `google_access_approval_project_settings` + +## Solución + +Defina el recurso de configuración y asegúrese de inscribir los servicios deseados (o `all` para una cobertura total). + +```terraform +resource "google_access_approval_project_settings" "compliant_settings" { + project_id = google_project.my_secure_project.project_id + + enrolled_services { + cloud_product = "all" # Protege todos los productos compatibles + enrollment_level = "BLOCK_ALL" + } + + notification_emails = ["security-team@tu-empresa.com"] +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_access_approval_disabled/metadata.json b/assets/queries/terraform/gcp/gcp_access_approval_disabled/metadata.json new file mode 100644 index 00000000000..64cbcb48dff --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_access_approval_disabled/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "d7df9989-f87a-45cb-80ea-d4d20e3d4530", + "queryName": "GCP Access Approval Disabled", + "severity": "MEDIUM", + "category": "Access Control", + "descriptionText": "Ensures that Access Approval is enabled for the GCP Project. Access Approval allows you to approve or deny access to your data by Google support and engineering personnel.", + "descriptionUrl": "https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/access_approval_project_settings", + "platform": "Terraform", + "descriptionID": "d7df9989", + "cloudProvider": "gcp", + "cwe": "CWE-284", + "riskScore": 5.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_access_approval_disabled/query.rego b/assets/queries/terraform/gcp/gcp_access_approval_disabled/query.rego new file mode 100644 index 00000000000..f6a7dfce92f --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_access_approval_disabled/query.rego @@ -0,0 +1,38 @@ +package Cx + +# CASO 1: Proyecto sin configuración de Access Approval. +CxPolicy[result] { + doc := input.document[i] + project := doc.resource.google_project[name] + + settings := [s | + s := doc.resource.google_access_approval_project_settings[_] + contains(s.project_id, name) + ] + + count(settings) == 0 + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.google_project.%s", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": sprintf("'google_project.%s' should have 'google_access_approval_project_settings' associated", [name]), + "keyActualValue": sprintf("'google_project.%s' does not have Access Approval configured", [name]), + } +} + +# CASO 2: Access Approval configurado pero sin servicios inscritos. +CxPolicy[result] { + doc := input.document[i] + settings := doc.resource.google_access_approval_project_settings[name] + + not settings.enrolled_services + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.google_access_approval_project_settings.%s", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "Must have at least one 'enrolled_services' block defined", + "keyActualValue": "'enrolled_services' is missing", + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_access_approval_disabled/test/negative1.tf b/assets/queries/terraform/gcp/gcp_access_approval_disabled/test/negative1.tf new file mode 100644 index 00000000000..d4f71adbe76 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_access_approval_disabled/test/negative1.tf @@ -0,0 +1,13 @@ +resource "google_project" "safe_project" { + name = "Safe Project" + project_id = "safe-123" + org_id = "12345" +} + +resource "google_access_approval_project_settings" "safe_settings" { + project_id = google_project.safe_project.project_id + + enrolled_services { + cloud_product = "all" + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_access_approval_disabled/test/positive1.tf b/assets/queries/terraform/gcp/gcp_access_approval_disabled/test/positive1.tf new file mode 100644 index 00000000000..f7c28f9789d --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_access_approval_disabled/test/positive1.tf @@ -0,0 +1,5 @@ +resource "google_project" "vulnerable_project" { + name = "Project Without Approval" + project_id = "vulnerable-123" + org_id = "12345" +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_access_approval_disabled/test/positive2.tf b/assets/queries/terraform/gcp/gcp_access_approval_disabled/test/positive2.tf new file mode 100644 index 00000000000..6e9ddf40f21 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_access_approval_disabled/test/positive2.tf @@ -0,0 +1,4 @@ +resource "google_access_approval_project_settings" "empty_settings" { + project_id = "some-project-id" + # FALLO: No tiene enrolled_services +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_access_approval_disabled/test/positive_expected_result.json b/assets/queries/terraform/gcp/gcp_access_approval_disabled/test/positive_expected_result.json new file mode 100644 index 00000000000..6099b814b74 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_access_approval_disabled/test/positive_expected_result.json @@ -0,0 +1,14 @@ +[ + { + "queryName": "GCP Access Approval Disabled", + "severity": "MEDIUM", + "line": 1, + "fileName": "positive1.tf" + }, + { + "queryName": "GCP Access Approval Disabled", + "severity": "MEDIUM", + "line": 1, + "fileName": "positive2.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_api_key_api_targets_missing/README.md b/assets/queries/terraform/gcp/gcp_api_key_api_targets_missing/README.md new file mode 100644 index 00000000000..c7b4f8dee59 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_api_key_api_targets_missing/README.md @@ -0,0 +1,57 @@ +# Regla KICS: Google API Key API Targets Missing + +## Descripción General + +Esta regla de severidad **MEDIA** audita la configuración de las **Google API Keys** (`google_apikeys_key`) para asegurar que el acceso esté limitado exclusivamente a los servicios necesarios. + +Cuando se genera una API Key en Google Cloud, por defecto tiene capacidad para invocar **cualquier API habilitada** en el proyecto. Esto representa un riesgo significativo de seguridad y costos; si una clave se ve comprometida (por ejemplo, al ser expuesta accidentalmente en el código fuente de una aplicación móvil o web), un atacante podría utilizarla para consumir servicios sensibles o de alto coste (como la API de Google Maps o modelos de IA) que no forman parte del propósito original de la aplicación. + +La mejor práctica de seguridad consiste en aplicar restricciones de "API Targets" para que la clave solo funcione con los servicios específicos para los que fue creada. + +## Lógica de la Regla + +La política evalúa dos escenarios de fallo en el código Terraform: +1. **Ausencia de Restricciones:** El recurso no define ningún bloque `restrictions`, dejando la clave totalmente desprotegida. +2. **Falta de Alcance de API:** El recurso define restricciones (como IPs o referrers), pero omite el bloque `api_targets`, permitiendo que esos orígenes autorizados consuman cualquier API del proyecto. + +## Casos de Fallo Detectados + +A continuación se describen los escenarios que esta política detectará. + +--- + +### Caso 1: Restricciones Ausentes +* **Descripción:** La clave de API se define sin ningún tipo de control perimetral o de servicio. +* **Ubicación de la Alerta:** Bloque del recurso `google_apikeys_key`. + +### Caso 2: API Targets no Definidos +* **Descripción:** Se aplican restricciones de cliente, pero se mantiene el acceso ilimitado a todos los servicios de Google habilitados en el proyecto. +* **Ubicación de la Alerta:** Atributo `restrictions`. + +## Recurso Involucrado + +* `google_apikeys_key` + +## Solución + +Añada el bloque `api_targets` dentro de la sección `restrictions` especificando los servicios necesarios. + +```terraform +resource "google_apikeys_key" "secure_key" { + name = "production-maps-key" + + restrictions { + # Restricción de origen (ejemplo para navegador) + browser_key_restrictions { + allowed_referrers = ["[https://app.tu-empresa.com/](https://app.tu-empresa.com/)*"] + } + + # Restricción de servicios (Solución) + api_targets { + service = "maps-backend.googleapis.com" + } + api_targets { + service = "places-backend.googleapis.com" + } + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_api_key_api_targets_missing/metadata.json b/assets/queries/terraform/gcp/gcp_api_key_api_targets_missing/metadata.json new file mode 100644 index 00000000000..e4a80048519 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_api_key_api_targets_missing/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "8097afd6-ef1a-4a58-b5f8-5d1b70ab4818", + "queryName": "Google API Key API Targets Missing", + "severity": "MEDIUM", + "category": "Access Control", + "descriptionText": "Ensures that Google Cloud API Keys are restricted to specific APIs. By default, an API key can be used to access any API enabled in the project, which increases the blast radius if the key is compromised.", + "descriptionUrl": "https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/apikeys_key#api_targets", + "platform": "Terraform", + "descriptionID": "8097afd6", + "cloudProvider": "gcp", + "cwe": "CWE-284", + "riskScore": 5.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_api_key_api_targets_missing/query.rego b/assets/queries/terraform/gcp/gcp_api_key_api_targets_missing/query.rego new file mode 100644 index 00000000000..ad76b2d3db5 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_api_key_api_targets_missing/query.rego @@ -0,0 +1,34 @@ +package Cx + +# CASO 1: No existe el bloque 'restrictions'. +CxPolicy[result] { + doc := input.document[i] + key := doc.resource.google_apikeys_key[name] + + not key.restrictions + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.google_apikeys_key.%s", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": sprintf("'google_apikeys_key.%s' should have 'restrictions.api_targets' defined", [name]), + "keyActualValue": sprintf("'google_apikeys_key.%s' is missing the 'restrictions' block", [name]), + } +} + +# CASO 2: Existe 'restrictions', pero falta 'api_targets'. +CxPolicy[result] { + doc := input.document[i] + key := doc.resource.google_apikeys_key[name] + + key.restrictions + not key.restrictions.api_targets + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.google_apikeys_key.%s.restrictions", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "restrictions.api_targets should be defined", + "keyActualValue": "restrictions.api_targets is missing", + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_api_key_api_targets_missing/test/negative1.tf b/assets/queries/terraform/gcp/gcp_api_key_api_targets_missing/test/negative1.tf new file mode 100644 index 00000000000..46aac8df6af --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_api_key_api_targets_missing/test/negative1.tf @@ -0,0 +1,15 @@ +resource "google_apikeys_key" "key_fully_secure" { + name = "fully-secure-key" + display_name = "Compliant Key" + + restrictions { + browser_key_restrictions { + allowed_referrers = ["https://example.com/*"] + } + + # CORRECTO: Acceso limitado a servicios específicos + api_targets { + service = "translate.googleapis.com" + } + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_api_key_api_targets_missing/test/positive1.tf b/assets/queries/terraform/gcp/gcp_api_key_api_targets_missing/test/positive1.tf new file mode 100644 index 00000000000..27bb02ac13f --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_api_key_api_targets_missing/test/positive1.tf @@ -0,0 +1,5 @@ +resource "google_apikeys_key" "key_no_restrictions" { + name = "unrestricted-key" + display_name = "Unrestricted Key" + project = "my-project" +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_api_key_api_targets_missing/test/positive2.tf b/assets/queries/terraform/gcp/gcp_api_key_api_targets_missing/test/positive2.tf new file mode 100644 index 00000000000..23ab9600179 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_api_key_api_targets_missing/test/positive2.tf @@ -0,0 +1,11 @@ +resource "google_apikeys_key" "key_no_targets" { + name = "partial-restricted-key" + display_name = "Key without API targets" + + restrictions { + server_key_restrictions { + allowed_ips = ["1.2.3.4"] + } + # FALLO: Falta el bloque api_targets + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_api_key_api_targets_missing/test/positive_expected_result.json b/assets/queries/terraform/gcp/gcp_api_key_api_targets_missing/test/positive_expected_result.json new file mode 100644 index 00000000000..7afa3f11d84 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_api_key_api_targets_missing/test/positive_expected_result.json @@ -0,0 +1,14 @@ +[ + { + "queryName": "Google API Key API Targets Missing", + "severity": "MEDIUM", + "line": 1, + "fileName": "positive1.tf" + }, + { + "queryName": "Google API Key API Targets Missing", + "severity": "MEDIUM", + "line": 5, + "fileName": "positive2.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_api_key_restrictions_manual/README.md b/assets/queries/terraform/gcp/gcp_api_key_restrictions_manual/README.md new file mode 100644 index 00000000000..348522b5c62 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_api_key_restrictions_manual/README.md @@ -0,0 +1,54 @@ +# Regla KICS: Google API Key Restrictions (Manual) + +## Descripción General + +Esta regla informativa (INFO) audita la seguridad perimetral de las **Google API Keys** (`google_apikeys_key`). + +A diferencia de las identidades de IAM, las API Keys no se autentican mediante un usuario, sino que se validan por su simple posesión. Por ello, es imperativo restringir su uso a direcciones IP, dominios web o aplicaciones móviles específicas. + +Esta regla garantiza que se aplique el principio de defensa en profundidad. Dado que una herramienta de análisis estático no puede determinar si una dirección IP o un dominio configurado es legítimo o excesivamente permisivo, la regla alerta tanto ante la ausencia de restricciones como ante su presencia, forzando una revisión manual de la lista de permitidos. + +## Lógica de la Regla + +La política evalúa el recurso en dos etapas: +1. **Validación de Presencia:** Si falta el bloque `restrictions`, la clave se marca como vulnerable (acceso público). +2. **Validación Manual:** Si el bloque `restrictions` existe, se genera una alerta informativa para que el auditor confirme que los valores (IPs, referrers, etc.) coinciden con los activos corporativos autorizados. + +## Casos de Fallo Detectados + +A continuación se describen los escenarios que esta política detectará. + +--- + +### Caso 1: Sin Restricciones (Vulnerable) +* **Descripción:** La clave de API carece de restricciones de cliente, permitiendo que cualquier persona con la clave pueda realizar llamadas desde cualquier lugar de Internet. +* **Ubicación de la Alerta:** Bloque del recurso `google_apikeys_key`. + +### Caso 2: Revisión de Restricciones (Manual Check) +* **Descripción:** La clave tiene restricciones configuradas. El auditor debe verificar que los valores definidos no sean genéricos o incorrectos. +* **Ubicación de la Alerta:** Atributo `restrictions`. + +## Recurso Involucrado + +* `google_apikeys_key` + +## Solución + +Implemente siempre el bloque `restrictions` utilizando el tipo de restricción que mejor se adapte al uso de la clave (Browser, Server, Android o iOS). + +```terraform +resource "google_apikeys_key" "secure_api_key" { + name = "frontend-maps-key" + + restrictions { + # Ejemplo: Clave restringida a un dominio específico + browser_key_restrictions { + allowed_referrers = ["[https://app.example.com/](https://app.example.com/)*"] + } + + # Recomendado: Combinar con restricción de API (API Targets) + api_targets { + service = "maps-backend.googleapis.com" + } + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_api_key_restrictions_manual/metadata.json b/assets/queries/terraform/gcp/gcp_api_key_restrictions_manual/metadata.json new file mode 100644 index 00000000000..a91205ab304 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_api_key_restrictions_manual/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "7b3ba800-c190-4b65-ae4d-fa1e5f7bc75e", + "queryName": "Google API Key Restrictions (Manual)", + "severity": "INFO", + "category": "Access Control", + "descriptionText": "Ensures that Google Cloud API Keys are restricted. If restrictions are present, they must be manually verified to ensure they list the correct IPs, websites, or apps.", + "descriptionUrl": "https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/apikeys_key", + "platform": "Terraform", + "descriptionID": "7b3ba800", + "cloudProvider": "gcp", + "cwe": "CWE-284", + "riskScore": 0.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_api_key_restrictions_manual/query.rego b/assets/queries/terraform/gcp/gcp_api_key_restrictions_manual/query.rego new file mode 100644 index 00000000000..c3edd6d961b --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_api_key_restrictions_manual/query.rego @@ -0,0 +1,33 @@ +package Cx + +# CASO 1: No hay restricciones definidas. +CxPolicy[result] { + doc := input.document[i] + key := doc.resource.google_apikeys_key[name] + + not key.restrictions + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.google_apikeys_key.%s", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": sprintf("'google_apikeys_key.%s' should have a 'restrictions' block defined", [name]), + "keyActualValue": sprintf("'google_apikeys_key.%s' is missing the 'restrictions' block", [name]), + } +} + +# CASO 2: Hay restricciones definidas. +CxPolicy[result] { + doc := input.document[i] + key := doc.resource.google_apikeys_key[name] + + key.restrictions + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.google_apikeys_key.%s.restrictions", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "Restrictions should be verified against allowed IPs/Referrers", + "keyActualValue": "Restrictions are present. Manual verification required.", + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_api_key_restrictions_manual/test/negative1.tf b/assets/queries/terraform/gcp/gcp_api_key_restrictions_manual/test/negative1.tf new file mode 100644 index 00000000000..c37bffc22a1 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_api_key_restrictions_manual/test/negative1.tf @@ -0,0 +1,4 @@ +# Caso negativo: No existen recursos de API Key, por lo que no hay riesgo que auditar. +resource "google_compute_network" "vpc" { + name = "secure-network" +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_api_key_restrictions_manual/test/positive1.tf b/assets/queries/terraform/gcp/gcp_api_key_restrictions_manual/test/positive1.tf new file mode 100644 index 00000000000..6a744bca5ef --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_api_key_restrictions_manual/test/positive1.tf @@ -0,0 +1,6 @@ +resource "google_apikeys_key" "key_public" { + name = "public-key" + display_name = "Public Key" + project = "my-project" + # FALLO: No tiene bloque restrictions +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_api_key_restrictions_manual/test/positive2.tf b/assets/queries/terraform/gcp/gcp_api_key_restrictions_manual/test/positive2.tf new file mode 100644 index 00000000000..d39236a798c --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_api_key_restrictions_manual/test/positive2.tf @@ -0,0 +1,11 @@ +resource "google_apikeys_key" "key_with_restrictions" { + name = "restricted-key" + display_name = "Restricted Key" + + restrictions { + # INFO: Esta sección requiere verificación manual de los valores + server_key_restrictions { + allowed_ips = ["192.168.1.1"] + } + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_api_key_restrictions_manual/test/positive_expected_result.json b/assets/queries/terraform/gcp/gcp_api_key_restrictions_manual/test/positive_expected_result.json new file mode 100644 index 00000000000..a6e13f9849c --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_api_key_restrictions_manual/test/positive_expected_result.json @@ -0,0 +1,14 @@ +[ + { + "queryName": "Google API Key Restrictions (Manual)", + "severity": "INFO", + "line": 1, + "fileName": "positive1.tf" + }, + { + "queryName": "Google API Key Restrictions (Manual)", + "severity": "INFO", + "line": 5, + "fileName": "positive2.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_app_engine_https_enforcement_manual/README.md b/assets/queries/terraform/gcp/gcp_app_engine_https_enforcement_manual/README.md new file mode 100644 index 00000000000..827f151e803 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_app_engine_https_enforcement_manual/README.md @@ -0,0 +1,57 @@ +# Regla KICS: App Engine HTTPS Enforcement (Manual) + +## Descripción General + +Esta regla de auditoría verifica que las aplicaciones desplegadas en **Google App Engine** (Standard Environment) fuercen el uso exclusivo de conexiones cifradas mediante **HTTPS**. + +Servir contenido a través de HTTP sin cifrar expone los datos confidenciales de los usuarios y las credenciales de sesión a intercepciones. Para mitigar este riesgo, App Engine permite configurar una redirección automática de HTTP a HTTPS. + +Dado que App Engine suele utilizar un archivo de configuración externo (`app.yaml`) para definir el comportamiento de red, esta regla actúa como un control de gobierno que alerta si la política no está explícitamente definida en la infraestructura como código (Terraform) o si requiere una inspección manual de los artefactos de despliegue. + +## Lógica de la Regla + +La política evalúa el recurso `google_app_engine_standard_app_version` bajo dos escenarios: +1. **Configuración en Terraform:** Si el bloque `handlers` está presente, se asegura de que el atributo `security_level` esté configurado como `SECURE_ALWAYS`. Cualquier otro valor (como `SECURE_OPTIONAL`) disparará una alerta. +2. **Configuración Externa:** Si no se definen `handlers` en Terraform, la regla genera una alerta informativa (**INFO**) indicando que el cumplimiento depende de la configuración dentro del archivo `app.yaml` de la aplicación. + +## Casos de Fallo Detectados + +A continuación se describen los escenarios que esta política detectará. + +--- + +### Caso 1: Nivel de Seguridad Inadecuado en Terraform +* **Descripción:** Los manejadores de URL permiten tráfico HTTP o no fuerzan la redirección segura. +* **Ubicación de la Alerta:** Atributo `handlers` dentro del recurso App Engine. + +### Caso 2: Verificación Manual de Archivos de Configuración +* **Descripción:** Terraform no gestiona la lógica de rutas. Se requiere validar el código fuente. +* **Acción Requerida:** Confirmar que en `app.yaml` todos los handlers críticos contengan: + ```yaml + secure: always + ``` +* **Ubicación de la Alerta:** Nivel de recurso `google_app_engine_standard_app_version`. + +## Recurso Involucrado + +* `google_app_engine_standard_app_version` + +## Solución + +Para forzar HTTPS desde Terraform, configure el `security_level` en cada handler: + +```terraform +resource "google_app_engine_standard_app_version" "secure_app" { + service = "api-service" + version_id = "v2" + runtime = "nodejs18" + + handlers { + url_regex = "/.*" + script { + script_path = "auto" + } + # Solución técnica + security_level = "SECURE_ALWAYS" + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_app_engine_https_enforcement_manual/metadata.json b/assets/queries/terraform/gcp/gcp_app_engine_https_enforcement_manual/metadata.json new file mode 100644 index 00000000000..38b32c311e5 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_app_engine_https_enforcement_manual/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "52b7bc15-032e-4dd7-b9c3-6dc9698aa131", + "queryName": "App Engine HTTPS Enforcement (Manual)", + "severity": "INFO", + "category": "Encryption", + "descriptionText": "Ensures that Google App Engine applications enforce HTTPS connections. This is typically configured in 'app.yaml' using 'secure: always' or in Terraform via the 'handlers' block with 'security_level'. Manual verification is often required.", + "descriptionUrl": "https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/app_engine_standard_app_version#security_level", + "platform": "Terraform", + "descriptionID": "52b7bc15", + "cloudProvider": "gcp", + "cwe": "CWE-319", + "riskScore": 0.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_app_engine_https_enforcement_manual/query.rego b/assets/queries/terraform/gcp/gcp_app_engine_https_enforcement_manual/query.rego new file mode 100644 index 00000000000..13dd18563ce --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_app_engine_https_enforcement_manual/query.rego @@ -0,0 +1,42 @@ +package Cx + +ensure_array(x) = x { is_array(x) } +ensure_array(x) = [x] { not is_array(x) } + +# CASO 1: Configuración Insegura en los handlers definidos. +CxPolicy[result] { + doc := input.document[i] + app := doc.resource.google_app_engine_standard_app_version[name] + + app.handlers + + handlers_list := ensure_array(app.handlers) + handler := handlers_list[_] + + sec_level := object.get(handler, "security_level", "UNSPECIFIED") + sec_level != "SECURE_ALWAYS" + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.google_app_engine_standard_app_version.%s.handlers", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "'handlers.security_level' should be set to 'SECURE_ALWAYS'", + "keyActualValue": sprintf("'handlers.security_level' is set to '%s'", [sec_level]), + } +} + +# CASO 2: Configuración Ausente en Terraform (Requiere revisión de app.yaml). +CxPolicy[result] { + doc := input.document[i] + app := doc.resource.google_app_engine_standard_app_version[name] + + not app.handlers + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.google_app_engine_standard_app_version.%s", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "If handlers are not defined in Terraform, verify 'app.yaml' contains 'secure: always'", + "keyActualValue": "Terraform does not define handlers. Manual verification of 'app.yaml' required.", + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_app_engine_https_enforcement_manual/test/negative1.tf b/assets/queries/terraform/gcp/gcp_app_engine_https_enforcement_manual/test/negative1.tf new file mode 100644 index 00000000000..c677e558ac9 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_app_engine_https_enforcement_manual/test/negative1.tf @@ -0,0 +1,10 @@ +resource "google_app_engine_standard_app_version" "app_secure" { + service = "frontend" + version_id = "v1" + runtime = "php81" + + handlers { + url_regex = "/.*" + security_level = "SECURE_ALWAYS" + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_app_engine_https_enforcement_manual/test/positive1.tf b/assets/queries/terraform/gcp/gcp_app_engine_https_enforcement_manual/test/positive1.tf new file mode 100644 index 00000000000..9e6e7822418 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_app_engine_https_enforcement_manual/test/positive1.tf @@ -0,0 +1,11 @@ +resource "google_app_engine_standard_app_version" "app_insecure" { + service = "default" + version_id = "v1" + runtime = "python39" + + handlers { + url_regex = "/.*" + # FALLO: security_level no es SECURE_ALWAYS + security_level = "SECURE_OPTIONAL" + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_app_engine_https_enforcement_manual/test/positive2.tf b/assets/queries/terraform/gcp/gcp_app_engine_https_enforcement_manual/test/positive2.tf new file mode 100644 index 00000000000..7ef1af37bdb --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_app_engine_https_enforcement_manual/test/positive2.tf @@ -0,0 +1,6 @@ +resource "google_app_engine_standard_app_version" "app_no_handlers" { + service = "backend" + version_id = "v1" + runtime = "go119" + # FALLO: Falta el bloque handlers, requiere revisión de app.yaml +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_app_engine_https_enforcement_manual/test/positive_expected_result.json b/assets/queries/terraform/gcp/gcp_app_engine_https_enforcement_manual/test/positive_expected_result.json new file mode 100644 index 00000000000..d4dae947860 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_app_engine_https_enforcement_manual/test/positive_expected_result.json @@ -0,0 +1,14 @@ +[ + { + "queryName": "App Engine HTTPS Enforcement (Manual)", + "severity": "INFO", + "line": 6, + "fileName": "positive1.tf" + }, + { + "queryName": "App Engine HTTPS Enforcement (Manual)", + "severity": "INFO", + "line": 1, + "fileName": "positive2.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_compute_logging_service_disabled/README.md b/assets/queries/terraform/gcp/gcp_compute_logging_service_disabled/README.md new file mode 100644 index 00000000000..133689f6385 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_compute_logging_service_disabled/README.md @@ -0,0 +1,49 @@ +# Regla KICS: GCP Compute Logging Service Disabled + +## Descripción General + +Esta regla de **Observabilidad** verifica que las instancias de **Google Compute Engine** (`google_compute_instance`) tengan habilitado el envío de registros al servicio de **Cloud Logging** mediante el metadato `google-logging-enabled`. + +La visibilidad es un componente crítico de la seguridad. El agente de Google Cloud (Ops Agent) utiliza este flag para determinar si debe transmitir logs del sistema operativo y de aplicaciones a la consola centralizada de Google Cloud. Sin estos registros, la detección de intrusiones, el análisis forense y la resolución de errores operativos se vuelven tareas inviables. + +## Lógica de la Regla + +La política audita el recurso evaluando tres estados posibles de fallo para maximizar la precisión del reporte: +1. **Bloque Ausente:** Si falta todo el bloque `metadata`. +2. **Clave Ausente:** Si el bloque `metadata` existe pero no define la clave requerida. +3. **Valor Incorrecto:** Si la clave existe pero se ha establecido explícitamente como `"false"`. + +## Casos de Fallo Detectados + +--- + +### Caso 1: Configuración de Metadatos Ausente +* **Descripción:** La instancia no define ningún metadato, omitiendo por tanto el servicio de logging. +* **Ubicación de la Alerta:** Nivel de recurso `google_compute_instance`. + +### Caso 2: Flag de Logging Faltante +* **Descripción:** Se usan metadatos pero se omite específicamente `google-logging-enabled`. +* **Ubicación de la Alerta:** Atributo `metadata`. + +### Caso 3: Logging Deshabilitado +* **Descripción:** Se ha configurado el valor `"false"`, bloqueando activamente la ingesta de logs. +* **Ubicación de la Alerta:** Atributo `google-logging-enabled`. + +## Recurso Involucrado + +* `google_compute_instance` + +## Solución + +Asegúrese de establecer el metadato en `"true"` para habilitar el servicio. + +```terraform +resource "google_compute_instance" "secure_vm" { + name = "prod-server" + machine_type = "e2-medium" + zone = "us-central1-a" + + metadata = { + "google-logging-enabled" = "true" + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_compute_logging_service_disabled/metadata.json b/assets/queries/terraform/gcp/gcp_compute_logging_service_disabled/metadata.json new file mode 100644 index 00000000000..c2038150816 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_compute_logging_service_disabled/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "ccefc589-0310-45a4-a2b6-d558e629e019", + "queryName": "GCP Compute Logging Service Disabled", + "severity": "MEDIUM", + "category": "Observability", + "descriptionText": "Ensures that the 'google-logging-enabled' metadata flag is set to 'true' for Google Compute Engine instances. This flag configures the instance to send logs to Cloud Logging.", + "descriptionUrl": "https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_instance#metadata", + "platform": "Terraform", + "descriptionID": "ccefc589", + "cloudProvider": "gcp", + "cwe": "CWE-778", + "riskScore": 5.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_compute_logging_service_disabled/query.rego b/assets/queries/terraform/gcp/gcp_compute_logging_service_disabled/query.rego new file mode 100644 index 00000000000..c0f0b0534a6 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_compute_logging_service_disabled/query.rego @@ -0,0 +1,51 @@ +package Cx + +# REGLA 1: El bloque 'metadata' no existe. +CxPolicy[result] { + doc := input.document[i] + instance := doc.resource.google_compute_instance[name] + + not instance.metadata + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.google_compute_instance.%s", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "'metadata' block should be defined and contain 'google-logging-enabled'", + "keyActualValue": "'metadata' block is missing", + } +} + +# REGLA 2: El bloque 'metadata' existe pero le falta la clave 'google-logging-enabled'. +CxPolicy[result] { + doc := input.document[i] + instance := doc.resource.google_compute_instance[name] + + instance.metadata + not instance.metadata["google-logging-enabled"] + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.google_compute_instance.%s.metadata", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "'google-logging-enabled' should be defined within metadata", + "keyActualValue": "'google-logging-enabled' is missing in metadata", + } +} + +# REGLA 3: La clave existe pero su valor es 'false'. +CxPolicy[result] { + doc := input.document[i] + instance := doc.resource.google_compute_instance[name] + + val := instance.metadata["google-logging-enabled"] + val == "false" + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.google_compute_instance.%s.metadata.google-logging-enabled", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "'google-logging-enabled' should be set to 'true'", + "keyActualValue": "'google-logging-enabled' is set to 'false'", + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_compute_logging_service_disabled/test/negative1.tf b/assets/queries/terraform/gcp/gcp_compute_logging_service_disabled/test/negative1.tf new file mode 100644 index 00000000000..c93e52f1de6 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_compute_logging_service_disabled/test/negative1.tf @@ -0,0 +1,6 @@ +resource "google_compute_instance" "vm_ok" { + name = "instance-compliant" + metadata = { + "google-logging-enabled" = "true" + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_compute_logging_service_disabled/test/positive1.tf b/assets/queries/terraform/gcp/gcp_compute_logging_service_disabled/test/positive1.tf new file mode 100644 index 00000000000..50d669dfeca --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_compute_logging_service_disabled/test/positive1.tf @@ -0,0 +1,4 @@ +resource "google_compute_instance" "vm_fail_1" { + name = "instance-no-metadata" + machine_type = "e2-medium" +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_compute_logging_service_disabled/test/positive2.tf b/assets/queries/terraform/gcp/gcp_compute_logging_service_disabled/test/positive2.tf new file mode 100644 index 00000000000..0ec28c90e91 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_compute_logging_service_disabled/test/positive2.tf @@ -0,0 +1,6 @@ +resource "google_compute_instance" "vm_fail_2" { + name = "instance-no-flag" + metadata = { + foo = "bar" + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_compute_logging_service_disabled/test/positive3.tf b/assets/queries/terraform/gcp/gcp_compute_logging_service_disabled/test/positive3.tf new file mode 100644 index 00000000000..d2d93df0e9c --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_compute_logging_service_disabled/test/positive3.tf @@ -0,0 +1,6 @@ +resource "google_compute_instance" "vm_fail_3" { + name = "instance-disabled" + metadata = { + "google-logging-enabled" = "false" + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_compute_logging_service_disabled/test/positive_expected_result.json b/assets/queries/terraform/gcp/gcp_compute_logging_service_disabled/test/positive_expected_result.json new file mode 100644 index 00000000000..a8f97e871bb --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_compute_logging_service_disabled/test/positive_expected_result.json @@ -0,0 +1,20 @@ +[ + { + "queryName": "GCP Compute Logging Service Disabled", + "severity": "MEDIUM", + "line": 1, + "fileName": "positive1.tf" + }, + { + "queryName": "GCP Compute Logging Service Disabled", + "severity": "MEDIUM", + "line": 3, + "fileName": "positive2.tf" + }, + { + "queryName": "GCP Compute Logging Service Disabled", + "severity": "MEDIUM", + "line": 4, + "fileName": "positive3.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_default_service_account_used/README.md b/assets/queries/terraform/gcp/gcp_gke_default_service_account_used/README.md new file mode 100644 index 00000000000..7e218cf2b69 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_gke_default_service_account_used/README.md @@ -0,0 +1,51 @@ +# Regla KICS: GKE Default Service Account Used + +## Descripción General + +Esta regla de severidad **ALTA** verifica que los clústeres y pools de nodos de **Google Kubernetes Engine (GKE)** no utilicen la cuenta de servicio predeterminada de Compute Engine. + +Por defecto, si no se especifica el atributo `service_account`, GKE utiliza la cuenta de servicio predeterminada del proyecto (`PROJECT_NUMBER-compute@developer.gserviceaccount.com`). Esta cuenta posee automáticamente el rol de **Editor**, lo que otorga a los nodos (y potencialmente a los Pods) permisos extensos para modificar recursos en GCP, como buckets de almacenamiento, redes VPC y otras instancias. Utilizar una cuenta de servicio dedicada con privilegios mínimos reduce drásticamente el "blast radius" en caso de un compromiso de seguridad en el clúster. + +## Lógica de la Regla + +La política audita los recursos `google_container_cluster` y `google_container_node_pool` bajo los siguientes criterios: +1. **Falta de Atributo:** Identifica si el bloque `node_config` carece de la clave `service_account`. +2. **Identificación de Riesgo:** La alerta se dispara al detectar que el clúster delegará su identidad a la cuenta de servicio más privilegiada del proyecto por defecto. + +## Casos de Fallo Detectados + +--- + +### Caso 1: Uso Implícito en el Clúster +* **Descripción:** Se define un clúster de GKE sin especificar una identidad para los nodos. +* **Ubicación de la Alerta:** Atributo `node_config` del recurso `google_container_cluster`. + +### Caso 2: Uso Implícito en el Node Pool +* **Descripción:** Se crea un pool de nodos adicional que hereda la cuenta de servicio por defecto. +* **Ubicación de la Alerta:** Atributo `node_config` del recurso `google_container_node_pool`. + +## Recurso Involucrado + +* `google_container_cluster` +* `google_container_node_pool` + +## Solución + +Cree una Service Account personalizada con los permisos mínimos necesarios (ej. roles de logging, monitoring y acceso a registry) y asígnela explícitamente. + +```terraform +resource "google_service_account" "gke_nodes_sa" { + account_id = "gke-nodes-identity" + display_name = "GKE Nodes Minimal Service Account" +} + +resource "google_container_cluster" "secure_cluster" { + name = "production-cluster" + location = "us-central1" + + node_config { + # Solución: Identidad dedicada + service_account = google_service_account.gke_nodes_sa.email + oauth_scopes = ["[https://www.googleapis.com/auth/cloud-platform](https://www.googleapis.com/auth/cloud-platform)"] + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_default_service_account_used/metadata.json b/assets/queries/terraform/gcp/gcp_gke_default_service_account_used/metadata.json new file mode 100644 index 00000000000..6fd64f265d2 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_gke_default_service_account_used/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "8ef3f681-6ef1-4bbd-94bb-f9fe6d0626da", + "queryName": "GKE Default Service Account Used", + "severity": "HIGH", + "category": "Access Control", + "descriptionText": "Ensures that GKE clusters do not use the default Compute Engine Service Account. The default account has the 'Editor' role, granting nodes excessive write permissions to the project.", + "descriptionUrl": "https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/container_cluster#service_account", + "platform": "Terraform", + "descriptionID": "8ef3f681", + "cloudProvider": "gcp", + "cwe": "CWE-276", + "riskScore": 9.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_default_service_account_used/query.rego b/assets/queries/terraform/gcp/gcp_gke_default_service_account_used/query.rego new file mode 100644 index 00000000000..ecb14c5bfcc --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_gke_default_service_account_used/query.rego @@ -0,0 +1,33 @@ +package Cx + +# REGLA 1: Service Account ausente en google_container_cluster. +CxPolicy[result] { + doc := input.document[i] + resource := doc.resource.google_container_cluster[name] + + not resource.node_config.service_account + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.google_container_cluster.%s.node_config", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "'service_account' should be explicitly defined in node_config", + "keyActualValue": "'service_account' is missing, defaulting to the Compute Engine default service account", + } +} + +# REGLA 2: Service Account ausente en google_container_node_pool. +CxPolicy[result] { + doc := input.document[i] + resource := doc.resource.google_container_node_pool[name] + + not resource.node_config.service_account + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.google_container_node_pool.%s.node_config", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "'service_account' should be explicitly defined in node_config", + "keyActualValue": "'service_account' is missing, defaulting to the Compute Engine default service account", + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_default_service_account_used/test/negative1.tf b/assets/queries/terraform/gcp/gcp_gke_default_service_account_used/test/negative1.tf new file mode 100644 index 00000000000..7454d8cb1c4 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_gke_default_service_account_used/test/negative1.tf @@ -0,0 +1,6 @@ +resource "google_container_cluster" "pass_cluster" { + name = "secure-cluster" + node_config { + service_account = "dedicated-sa@project.iam.gserviceaccount.com" + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_default_service_account_used/test/positive1.tf b/assets/queries/terraform/gcp/gcp_gke_default_service_account_used/test/positive1.tf new file mode 100644 index 00000000000..ee0134114dc --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_gke_default_service_account_used/test/positive1.tf @@ -0,0 +1,9 @@ +resource "google_container_cluster" "fail_cluster" { + name = "insecure-cluster" + location = "us-central1" + + node_config { + machine_type = "e2-medium" + # FALLO: Falta service_account + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_default_service_account_used/test/positive2.tf b/assets/queries/terraform/gcp/gcp_gke_default_service_account_used/test/positive2.tf new file mode 100644 index 00000000000..f7a9977968e --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_gke_default_service_account_used/test/positive2.tf @@ -0,0 +1,9 @@ +resource "google_container_node_pool" "fail_pool" { + name = "insecure-pool" + cluster = "some-cluster" + + node_config { + # FALLO: Falta service_account + preemptible = true + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_default_service_account_used/test/positive_expected_result.json b/assets/queries/terraform/gcp/gcp_gke_default_service_account_used/test/positive_expected_result.json new file mode 100644 index 00000000000..1fc03b54785 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_gke_default_service_account_used/test/positive_expected_result.json @@ -0,0 +1,14 @@ +[ + { + "queryName": "GKE Default Service Account Used", + "severity": "HIGH", + "line": 5, + "fileName": "positive1.tf" + }, + { + "queryName": "GKE Default Service Account Used", + "severity": "HIGH", + "line": 5, + "fileName": "positive2.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_image_vulnerability_scanning_disabled/README.md b/assets/queries/terraform/gcp/gcp_gke_image_vulnerability_scanning_disabled/README.md new file mode 100644 index 00000000000..8cc831522a7 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_gke_image_vulnerability_scanning_disabled/README.md @@ -0,0 +1,46 @@ +# Regla KICS: GKE Image Vulnerability Scanning Disabled + +## Descripción General + +Esta regla de severidad **ALTA** verifica que el escaneo automático de vulnerabilidades esté habilitado en los clústeres de **Google Kubernetes Engine (GKE)** a través del Security Posture Dashboard. + +Habilitar el escaneo de vulnerabilidades permite a GKE inspeccionar continuamente las imágenes de contenedor que se ejecutan en las cargas de trabajo del clúster. GKE compara los paquetes del sistema operativo del contenedor con bases de datos públicas de vulnerabilidades (CVEs). Sin esta función activa, la organización carece de visibilidad sobre si las aplicaciones desplegadas contienen fallos de seguridad conocidos que podrían ser explotados por atacantes. + +## Lógica de la Regla + +La política audita el recurso `google_container_cluster` evaluando tres estados de riesgo: +1. **Omisión de Configuración:** El clúster no tiene definido el bloque `security_posture_config`. +2. **Modo No Especificado:** El bloque existe pero no define el parámetro `vulnerability_mode`. +3. **Deshabilitación Explícita:** El parámetro `vulnerability_mode` está configurado como `VULNERABILITY_DISABLED`. + +## Casos de Fallo Detectados + +--- + +### Caso 1: Configuración de Postura de Seguridad Ausente +* **Descripción:** El clúster se aprovisiona con la configuración por defecto de Google, que no siempre incluye el escaneo avanzado activo. +* **Ubicación de la Alerta:** Nivel de recurso `google_container_cluster`. + +### Caso 2: Vulnerability Mode Faltante o Deshabilitado +* **Descripción:** Se define la configuración de seguridad pero se omite o se desactiva explícitamente el escaneo de imágenes. +* **Ubicación de la Alerta:** Atributo `vulnerability_mode` dentro de `security_posture_config`. + +## Recurso Involucrado + +* `google_container_cluster` + +## Solución + +Habilite el escaneo configurando el bloque `security_posture_config` con un modo de vulnerabilidad válido. + +```terraform +resource "google_container_cluster" "compliant_cluster" { + name = "secure-production-cluster" + location = "us-central1" + + # Solución recomendada + security_posture_config { + mode = "BASIC" + vulnerability_mode = "VULNERABILITY_BASIC" + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_image_vulnerability_scanning_disabled/metadata.json b/assets/queries/terraform/gcp/gcp_gke_image_vulnerability_scanning_disabled/metadata.json new file mode 100644 index 00000000000..129e38d16fe --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_gke_image_vulnerability_scanning_disabled/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "30b5b4bb-a4dd-41b5-9caf-32e9b017a35e", + "queryName": "GKE Image Vulnerability Scanning Disabled", + "severity": "HIGH", + "category": "Supply-Chain", + "descriptionText": "Ensures that Image Vulnerability Scanning is enabled in GKE clusters via the security posture configuration. This feature automatically scans workloads for known vulnerabilities.", + "descriptionUrl": "https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/container_cluster#vulnerability_mode", + "platform": "Terraform", + "descriptionID": "30b5b4bb", + "cloudProvider": "gcp", + "cwe": "CWE-1395", + "riskScore": 9.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_image_vulnerability_scanning_disabled/query.rego b/assets/queries/terraform/gcp/gcp_gke_image_vulnerability_scanning_disabled/query.rego new file mode 100644 index 00000000000..d3736459d35 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_gke_image_vulnerability_scanning_disabled/query.rego @@ -0,0 +1,51 @@ +package Cx + +# REGLA 1: Bloque 'security_posture_config' ausente. +CxPolicy[result] { + doc := input.document[i] + cluster := doc.resource.google_container_cluster[name] + + not cluster.security_posture_config + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.google_container_cluster.%s", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "'security_posture_config' block should be defined", + "keyActualValue": "'security_posture_config' block is missing", + } +} + +# REGLA 2: Atributo 'vulnerability_mode' ausente dentro del bloque. +CxPolicy[result] { + doc := input.document[i] + cluster := doc.resource.google_container_cluster[name] + + cluster.security_posture_config + not cluster.security_posture_config.vulnerability_mode + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.google_container_cluster.%s.security_posture_config", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "'vulnerability_mode' should be defined within security_posture_config", + "keyActualValue": "'vulnerability_mode' is missing", + } +} + +# REGLA 3: Atributo 'vulnerability_mode' configurado como 'VULNERABILITY_DISABLED'. +CxPolicy[result] { + doc := input.document[i] + cluster := doc.resource.google_container_cluster[name] + + mode := cluster.security_posture_config.vulnerability_mode + mode == "VULNERABILITY_DISABLED" + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.google_container_cluster.%s.security_posture_config.vulnerability_mode", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "'vulnerability_mode' should be 'VULNERABILITY_BASIC' or 'VULNERABILITY_ENTERPRISE'", + "keyActualValue": "'vulnerability_mode' is set to 'VULNERABILITY_DISABLED'", + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_image_vulnerability_scanning_disabled/test/negative1.tf b/assets/queries/terraform/gcp/gcp_gke_image_vulnerability_scanning_disabled/test/negative1.tf new file mode 100644 index 00000000000..59cb11a0bb0 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_gke_image_vulnerability_scanning_disabled/test/negative1.tf @@ -0,0 +1,8 @@ +resource "google_container_cluster" "pass_vulnerability_basic" { + name = "compliant-cluster-basic" + + security_posture_config { + mode = "BASIC" + vulnerability_mode = "VULNERABILITY_BASIC" + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_image_vulnerability_scanning_disabled/test/negative2.tf b/assets/queries/terraform/gcp/gcp_gke_image_vulnerability_scanning_disabled/test/negative2.tf new file mode 100644 index 00000000000..ed4e5fdb59b --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_gke_image_vulnerability_scanning_disabled/test/negative2.tf @@ -0,0 +1,8 @@ +resource "google_container_cluster" "pass_vulnerability_enterprise" { + name = "compliant-cluster-enterprise" + + security_posture_config { + mode = "ENTERPRISE" + vulnerability_mode = "VULNERABILITY_ENTERPRISE" + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_image_vulnerability_scanning_disabled/test/positive1.tf b/assets/queries/terraform/gcp/gcp_gke_image_vulnerability_scanning_disabled/test/positive1.tf new file mode 100644 index 00000000000..47c80cdd8b0 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_gke_image_vulnerability_scanning_disabled/test/positive1.tf @@ -0,0 +1,5 @@ +resource "google_container_cluster" "fail_block_missing" { + name = "no-security-config" + location = "us-central1" + # Fallo: No existe el bloque de postura de seguridad +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_image_vulnerability_scanning_disabled/test/positive2.tf b/assets/queries/terraform/gcp/gcp_gke_image_vulnerability_scanning_disabled/test/positive2.tf new file mode 100644 index 00000000000..fff3f5bf020 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_gke_image_vulnerability_scanning_disabled/test/positive2.tf @@ -0,0 +1,8 @@ +resource "google_container_cluster" "fail_attr_missing" { + name = "partial-security-config" + + security_posture_config { + mode = "BASIC" + # Fallo: Falta definir vulnerability_mode dentro del bloque + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_image_vulnerability_scanning_disabled/test/positive3.tf b/assets/queries/terraform/gcp/gcp_gke_image_vulnerability_scanning_disabled/test/positive3.tf new file mode 100644 index 00000000000..cf0bab1c4d3 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_gke_image_vulnerability_scanning_disabled/test/positive3.tf @@ -0,0 +1,8 @@ +resource "google_container_cluster" "fail_mode_disabled" { + name = "disabled-scanning-cluster" + + security_posture_config { + mode = "BASIC" + vulnerability_mode = "VULNERABILITY_DISABLED" # Fallo: Deshabilitado + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_image_vulnerability_scanning_disabled/test/positive_expected_result.json b/assets/queries/terraform/gcp/gcp_gke_image_vulnerability_scanning_disabled/test/positive_expected_result.json new file mode 100644 index 00000000000..96bf72e2d4e --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_gke_image_vulnerability_scanning_disabled/test/positive_expected_result.json @@ -0,0 +1,20 @@ +[ + { + "queryName": "GKE Image Vulnerability Scanning Disabled", + "severity": "HIGH", + "line": 1, + "fileName": "positive1.tf" + }, + { + "queryName": "GKE Image Vulnerability Scanning Disabled", + "severity": "HIGH", + "line": 4, + "fileName": "positive2.tf" + }, + { + "queryName": "GKE Image Vulnerability Scanning Disabled", + "severity": "HIGH", + "line": 6, + "fileName": "positive3.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_manual_iam_check/README.md b/assets/queries/terraform/gcp/gcp_gke_manual_iam_check/README.md new file mode 100644 index 00000000000..79981201ecc --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_gke_manual_iam_check/README.md @@ -0,0 +1,38 @@ +# Regla KICS: GKE Service Account IAM Review (Manual) + +## Descripción General + +Esta regla informativa (INFO) de **Supply Chain** audita las identidades utilizadas por los nodos de **Google Kubernetes Engine (GKE)** cuando se han configurado cuentas de servicio personalizadas. + +El uso de cuentas de servicio personalizadas es el primer paso hacia el principio de menor privilegio. Sin embargo, si a esta cuenta se le asignan roles con permisos de escritura (como `roles/artifactregistry.writer` o `roles/storage.objectAdmin`), un atacante que comprometa un nodo podría modificar o reemplazar las imágenes de contenedor en el registro. Esto permitiría ataques de persistencia o escalada de privilegios en otros clústeres que consuman esas mismas imágenes. + +## Lógica de la Regla + +La política identifica los recursos `google_container_cluster` y `google_container_node_pool` que definen explícitamente una `service_account`. Al ser una verificación manual, genera una alerta informativa sobre la línea de la cuenta de servicio para que el auditor valide en el proyecto de GCP que los roles asociados son exclusivamente de **Lectura**. + +## Casos de Fallo Detectados + +--- + +### Caso 1: Revisión de SA en Clúster +* **Descripción:** Se ha definido una identidad personalizada para el clúster. Se debe verificar que no tenga permisos de "Push" al registro de imágenes. +* **Ubicación de la Alerta:** Atributo `service_account` en `google_container_cluster`. + +### Caso 2: Revisión de SA en Node Pool +* **Descripción:** Un pool de nodos específico utiliza una identidad propia que requiere auditoría de roles IAM. +* **Ubicación de la Alerta:** Atributo `service_account` en `google_container_node_pool`. + +## Recurso Involucrado + +* `google_container_cluster` +* `google_container_node_pool` + +## Solución + +Asegúrese de que la Service Account asignada solo posea roles de lectura para el registro de imágenes utilizado. + +**Roles Recomendados:** +* `roles/artifactregistry.reader` (para Artifact Registry) +* `roles/storage.objectViewer` (para Container Registry/GCR) +* `roles/logging.logWriter` +* `roles/monitoring.metricWriter` \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_manual_iam_check/metadata.json b/assets/queries/terraform/gcp/gcp_gke_manual_iam_check/metadata.json new file mode 100644 index 00000000000..5ace58c4a29 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_gke_manual_iam_check/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "b29ff836-5b2e-4089-a40c-086426ef3980", + "queryName": "GKE Service Account IAM Review (Manual)", + "severity": "INFO", + "category": "Supply-Chain", + "descriptionText": "A custom Service Account is configured for GKE nodes. Manual verification is required to ensure this account has Read-Only access to Container Registries (e.g., 'roles/storage.objectViewer') and not Write access.", + "descriptionUrl": "https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/google_project_iam_member", + "platform": "Terraform", + "descriptionID": "b29ff836", + "cloudProvider": "gcp", + "cwe": "CWE-276", + "riskScore": 0.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_manual_iam_check/query.rego b/assets/queries/terraform/gcp/gcp_gke_manual_iam_check/query.rego new file mode 100644 index 00000000000..a3c5faa3751 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_gke_manual_iam_check/query.rego @@ -0,0 +1,33 @@ +package Cx + +# REGLA 1: Detección de SA personalizada en google_container_cluster. +CxPolicy[result] { + doc := input.document[i] + resource := doc.resource.google_container_cluster[name] + + sa := resource.node_config.service_account + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.google_container_cluster.%s.node_config.service_account", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "Custom Service Account IAM roles should be verified as Read-Only", + "keyActualValue": sprintf("Custom Service Account '%s' found. Manual IAM verification required.", [sa]), + } +} + +# REGLA 2: Detección de SA personalizada en google_container_node_pool. +CxPolicy[result] { + doc := input.document[i] + resource := doc.resource.google_container_node_pool[name] + + sa := resource.node_config.service_account + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.google_container_node_pool.%s.node_config.service_account", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "Custom Service Account IAM roles should be verified as Read-Only", + "keyActualValue": sprintf("Custom Service Account '%s' found. Manual IAM verification required.", [sa]), + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_manual_iam_check/test/negative1.tf b/assets/queries/terraform/gcp/gcp_gke_manual_iam_check/test/negative1.tf new file mode 100644 index 00000000000..85a988a70c7 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_gke_manual_iam_check/test/negative1.tf @@ -0,0 +1,5 @@ +# En este caso negativo, no hay SA personalizada (se encargaría la regla de SA por defecto) +resource "google_container_cluster" "no_custom_sa" { + name = "default-sa-cluster" + # No define node_config.service_account +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_manual_iam_check/test/positive1.tf b/assets/queries/terraform/gcp/gcp_gke_manual_iam_check/test/positive1.tf new file mode 100644 index 00000000000..72e297282f2 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_gke_manual_iam_check/test/positive1.tf @@ -0,0 +1,9 @@ +resource "google_container_cluster" "audit_cluster" { + name = "custom-sa-cluster" + location = "us-central1" + + node_config { + # INFO: Esta cuenta requiere revisión manual de IAM + service_account = "gke-nodes-custom@project.iam.gserviceaccount.com" + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_manual_iam_check/test/positive2.tf b/assets/queries/terraform/gcp/gcp_gke_manual_iam_check/test/positive2.tf new file mode 100644 index 00000000000..5a9cf868c63 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_gke_manual_iam_check/test/positive2.tf @@ -0,0 +1,9 @@ +resource "google_container_node_pool" "audit_pool" { + name = "custom-sa-pool" + cluster = "my-cluster" + + node_config { + # INFO: Esta cuenta requiere revisión manual de IAM + service_account = "dedicated-pool-sa@project.iam.gserviceaccount.com" + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_manual_iam_check/test/positive_expected_result.json b/assets/queries/terraform/gcp/gcp_gke_manual_iam_check/test/positive_expected_result.json new file mode 100644 index 00000000000..c62bc67dd8a --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_gke_manual_iam_check/test/positive_expected_result.json @@ -0,0 +1,14 @@ +[ + { + "queryName": "GKE Service Account IAM Review (Manual)", + "severity": "INFO", + "line": 7, + "fileName": "positive1.tf" + }, + { + "queryName": "GKE Service Account IAM Review (Manual)", + "severity": "INFO", + "line": 7, + "fileName": "positive2.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_metadata_server_disabled/README.md b/assets/queries/terraform/gcp/gcp_gke_metadata_server_disabled/README.md new file mode 100644 index 00000000000..cf6928760c7 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_gke_metadata_server_disabled/README.md @@ -0,0 +1,47 @@ +# Regla KICS: GKE Metadata Server Disabled + +## Descripción General + +Esta regla de severidad **ALTA** verifica que los nodos de GKE utilicen el **GKE Metadata Server** para gestionar las identidades de las cargas de trabajo. + +Por defecto, los nodos de GKE tienen acceso al servidor de metadatos de Compute Engine (GCE), el cual expone información sensible del host y tokens de acceso de la cuenta de servicio del nodo. Si un atacante logra comprometer un pod, podría consultar este endpoint para obtener credenciales y realizar un movimiento lateral hacia otros recursos del proyecto. Al habilitar `GKE_METADATA`, se activa el puente hacia **Workload Identity**, interceptando estas peticiones y limitando el acceso solo a lo que el Pod tiene permitido específicamente. + +## Lógica de la Regla + +La política audita los recursos `google_container_cluster` y `google_container_node_pool` bajo dos criterios: +1. **Omisión del Bloque:** Verifica si `workload_metadata_config` está presente dentro de `node_config`. +2. **Modo Inseguro:** Asegura que el atributo `mode` sea estrictamente `GKE_METADATA`. El valor `GCE_METADATA` (valor predeterminado heredado) se considera vulnerable. + +## Casos de Fallo Detectados + +--- + +### Caso 1: Configuración de Metadatos Ausente +* **Descripción:** El clúster o pool de nodos no define el modo de metadatos de carga de trabajo. +* **Ubicación de la Alerta:** Atributo `node_config`. + +### Caso 2: Uso de GCE_METADATA (Legacy/Vulnerable) +* **Descripción:** Se permite explícitamente el acceso de los pods a los metadatos de la instancia de cómputo subyacente. +* **Ubicación de la Alerta:** Atributo `mode` dentro de `workload_metadata_config`. + +## Recurso Involucrado + +* `google_container_cluster` +* `google_container_node_pool` + +## Solución + +Configure el bloque `workload_metadata_config` con el modo `GKE_METADATA`. + +```terraform +resource "google_container_node_pool" "compliant_pool" { + name = "secure-pool" + cluster = google_container_cluster.primary.name + + node_config { + # Solución técnica + workload_metadata_config { + mode = "GKE_METADATA" + } + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_metadata_server_disabled/metadata.json b/assets/queries/terraform/gcp/gcp_gke_metadata_server_disabled/metadata.json new file mode 100644 index 00000000000..e15967fcbb7 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_gke_metadata_server_disabled/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "e858428b-7650-4a62-a7d4-5b0d4c9afdd6", + "queryName": "GKE Metadata Server Disabled", + "severity": "HIGH", + "category": "Access Control", + "descriptionText": "Ensures that the GKE Metadata Server is enabled by setting 'workload_metadata_config.mode' to 'GKE_METADATA'. This prevents pods from accessing the sensitive Compute Engine metadata server and node credentials.", + "descriptionUrl": "https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/container_node_pool#mode", + "platform": "Terraform", + "descriptionID": "e858428b", + "cloudProvider": "gcp", + "cwe": "CWE-284", + "riskScore": 9.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_metadata_server_disabled/query.rego b/assets/queries/terraform/gcp/gcp_gke_metadata_server_disabled/query.rego new file mode 100644 index 00000000000..d853b869a5c --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_gke_metadata_server_disabled/query.rego @@ -0,0 +1,65 @@ +package Cx + +# REGLA 1: 'workload_metadata_config' ausente en google_container_cluster +CxPolicy[result] { + doc := input.document[i] + resource := doc.resource.google_container_cluster[name] + + not resource.node_config.workload_metadata_config + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.google_container_cluster.%s.node_config", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "'workload_metadata_config' should be defined with mode 'GKE_METADATA'", + "keyActualValue": "'workload_metadata_config' is missing", + } +} + +# REGLA 2: 'workload_metadata_config' ausente en google_container_node_pool +CxPolicy[result] { + doc := input.document[i] + resource := doc.resource.google_container_node_pool[name] + + not resource.node_config.workload_metadata_config + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.google_container_node_pool.%s.node_config", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "'workload_metadata_config' should be defined with mode 'GKE_METADATA'", + "keyActualValue": "'workload_metadata_config' is missing", + } +} + +# REGLA 3: Modo incorrecto en google_container_cluster +CxPolicy[result] { + doc := input.document[i] + resource := doc.resource.google_container_cluster[name] + + resource.node_config.workload_metadata_config.mode != "GKE_METADATA" + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.google_container_cluster.%s.node_config.workload_metadata_config.mode", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "'mode' should be set to 'GKE_METADATA'", + "keyActualValue": sprintf("'mode' is set to '%s'", [resource.node_config.workload_metadata_config.mode]), + } +} + +# REGLA 4: Modo incorrecto en google_container_node_pool +CxPolicy[result] { + doc := input.document[i] + resource := doc.resource.google_container_node_pool[name] + + resource.node_config.workload_metadata_config.mode != "GKE_METADATA" + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.google_container_node_pool.%s.node_config.workload_metadata_config.mode", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "'mode' should be set to 'GKE_METADATA'", + "keyActualValue": sprintf("'mode' is set to '%s'", [resource.node_config.workload_metadata_config.mode]), + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_metadata_server_disabled/test/negative1.tf b/assets/queries/terraform/gcp/gcp_gke_metadata_server_disabled/test/negative1.tf new file mode 100644 index 00000000000..d9326a50b15 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_gke_metadata_server_disabled/test/negative1.tf @@ -0,0 +1,8 @@ +resource "google_container_cluster" "pass_cluster" { + name = "secure-cluster" + node_config { + workload_metadata_config { + mode = "GKE_METADATA" + } + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_metadata_server_disabled/test/negative2.tf b/assets/queries/terraform/gcp/gcp_gke_metadata_server_disabled/test/negative2.tf new file mode 100644 index 00000000000..ca108391fbc --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_gke_metadata_server_disabled/test/negative2.tf @@ -0,0 +1,9 @@ +resource "google_container_node_pool" "pass_pool" { + name = "secure-pool" + cluster = "my-cluster" + node_config { + workload_metadata_config { + mode = "GKE_METADATA" + } + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_metadata_server_disabled/test/positive1.tf b/assets/queries/terraform/gcp/gcp_gke_metadata_server_disabled/test/positive1.tf new file mode 100644 index 00000000000..28c1bac2b70 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_gke_metadata_server_disabled/test/positive1.tf @@ -0,0 +1,9 @@ +resource "google_container_cluster" "fail_cluster_no_block" { + name = "cluster-missing-metadata-config" + location = "us-central1" + + node_config { + machine_type = "e2-medium" + # FALLO: No existe workload_metadata_config + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_metadata_server_disabled/test/positive2.tf b/assets/queries/terraform/gcp/gcp_gke_metadata_server_disabled/test/positive2.tf new file mode 100644 index 00000000000..8fd70a39d23 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_gke_metadata_server_disabled/test/positive2.tf @@ -0,0 +1,9 @@ +resource "google_container_node_pool" "fail_pool_no_block" { + name = "pool-missing-metadata-config" + cluster = "my-cluster" + + node_config { + preemptible = true + # FALLO: No existe workload_metadata_config + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_metadata_server_disabled/test/positive3.tf b/assets/queries/terraform/gcp/gcp_gke_metadata_server_disabled/test/positive3.tf new file mode 100644 index 00000000000..f267aff7350 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_gke_metadata_server_disabled/test/positive3.tf @@ -0,0 +1,9 @@ +resource "google_container_cluster" "fail_cluster_wrong_mode" { + name = "cluster-gce-mode" + + node_config { + workload_metadata_config { + mode = "GCE_METADATA" # FALLO: Debe ser GKE_METADATA + } + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_metadata_server_disabled/test/positive4.tf b/assets/queries/terraform/gcp/gcp_gke_metadata_server_disabled/test/positive4.tf new file mode 100644 index 00000000000..ea2bc5ced9d --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_gke_metadata_server_disabled/test/positive4.tf @@ -0,0 +1,10 @@ +resource "google_container_node_pool" "fail_pool_wrong_mode" { + name = "pool-gce-mode" + cluster = "my-cluster" + + node_config { + workload_metadata_config { + mode = "GCE_METADATA" # FALLO: Debe ser GKE_METADATA + } + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_metadata_server_disabled/test/positive_expected_result.json b/assets/queries/terraform/gcp/gcp_gke_metadata_server_disabled/test/positive_expected_result.json new file mode 100644 index 00000000000..defbff8b43b --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_gke_metadata_server_disabled/test/positive_expected_result.json @@ -0,0 +1,26 @@ +[ + { + "queryName": "GKE Metadata Server Disabled", + "severity": "HIGH", + "line": 5, + "fileName": "positive1.tf" + }, + { + "queryName": "GKE Metadata Server Disabled", + "severity": "HIGH", + "line": 5, + "fileName": "positive2.tf" + }, + { + "queryName": "GKE Metadata Server Disabled", + "severity": "HIGH", + "line": 6, + "fileName": "positive3.tf" + }, + { + "queryName": "GKE Metadata Server Disabled", + "severity": "HIGH", + "line": 7, + "fileName": "positive4.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_sandbox_disabled/README.md b/assets/queries/terraform/gcp/gcp_gke_sandbox_disabled/README.md new file mode 100644 index 00000000000..0501cd7724e --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_gke_sandbox_disabled/README.md @@ -0,0 +1,52 @@ +# Regla KICS: GKE Sandbox (gVisor) Disabled + +## Descripción General + +Esta regla de severidad **BAJA** verifica si los pools de nodos de GKE tienen habilitado **GKE Sandbox (gVisor)** para un aislamiento reforzado. + +GKE Sandbox utiliza **gVisor**, un kernel de espacio de usuario que proporciona una barrera de seguridad adicional entre las aplicaciones y el kernel del host. Al interceptar y filtrar las llamadas al sistema, gVisor mitiga el riesgo de ataques de escape de contenedor, donde un proceso malicioso intenta comprometer el nodo subyacente. Aunque gVisor introduce un ligero overhead de rendimiento, es una **Configuración Insegura** omitirlo en entornos que ejecutan código no confiable o aplicaciones multi-tenant. + +## Lógica de la Regla + +La política audita los recursos `google_container_cluster` y `google_container_node_pool`: +1. **Omisión del Bloque:** Detecta si `sandbox_config` no está presente en el `node_config`. +2. **Tipo Incorrecto:** Verifica que el `sandbox_type` sea explícitamente `"gvisor"`. + +## Casos de Fallo Detectados + +--- + +### Caso 1: Sandbox no configurado en Clúster +* **Descripción:** Los nodos predeterminados del clúster no utilizan el aislamiento de gVisor. +* **Ubicación de la Alerta:** Atributo `node_config`. + +### Caso 2: Sandbox no configurado en Node Pool +* **Descripción:** Se ha creado un pool de nodos adicional que carece de protección de Sandbox. +* **Ubicación de la Alerta:** Atributo `node_config`. + +### Caso 3: Tipo de Sandbox Inseguro +* **Descripción:** Se define el bloque pero se utiliza un valor distinto a `gvisor`, lo que desactiva la protección buscada. +* **Ubicación de la Alerta:** Atributo `sandbox_type`. + +## Recurso Involucrado + +* `google_container_cluster` +* `google_container_node_pool` + +## Solución + +Habilite gVisor asegurándose de usar el tipo de imagen compatible `COS_CONTAINERD`. + +```terraform +resource "google_container_node_pool" "secure_pool" { + name = "untrusted-code-pool" + cluster = google_container_cluster.primary.name + + node_config { + image_type = "COS_CONTAINERD" + + sandbox_config { + sandbox_type = "gvisor" + } + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_sandbox_disabled/metadata.json b/assets/queries/terraform/gcp/gcp_gke_sandbox_disabled/metadata.json new file mode 100644 index 00000000000..327a2de5107 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_gke_sandbox_disabled/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "6c8a93a9-c0c5-4314-8ee6-3bcb3a0dbd70", + "queryName": "GKE Sandbox (gVisor) Disabled", + "severity": "LOW", + "category": "Insecure Configurations", + "descriptionText": "GKE Sandbox (gVisor) provides an extra layer of defense for untrusted workloads. It is not enabled on this Node Pool. Consider enabling it if this pool runs untrusted code (e.g., SaaS user scripts).", + "descriptionUrl": "https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/container_node_pool#sandbox_type", + "platform": "Terraform", + "descriptionID": "6c8a93a9", + "cloudProvider": "gcp", + "cwe": "CWE-1038", + "riskScore": 3.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_sandbox_disabled/query.rego b/assets/queries/terraform/gcp/gcp_gke_sandbox_disabled/query.rego new file mode 100644 index 00000000000..7a81e5cd947 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_gke_sandbox_disabled/query.rego @@ -0,0 +1,65 @@ +package Cx + +# REGLA 1: Bloque 'sandbox_config' ausente en google_container_cluster. +CxPolicy[result] { + doc := input.document[i] + resource := doc.resource.google_container_cluster[name] + + not resource.node_config.sandbox_config + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.google_container_cluster.%s.node_config", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "'sandbox_config' should be defined with sandbox_type 'gvisor'", + "keyActualValue": "'sandbox_config' is missing", + } +} + +# REGLA 2: Bloque 'sandbox_config' ausente en google_container_node_pool. +CxPolicy[result] { + doc := input.document[i] + resource := doc.resource.google_container_node_pool[name] + + not resource.node_config.sandbox_config + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.google_container_node_pool.%s.node_config", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "'sandbox_config' should be defined with sandbox_type 'gvisor'", + "keyActualValue": "'sandbox_config' is missing", + } +} + +# REGLA 3: sandbox_type incorrecto en google_container_cluster. +CxPolicy[result] { + doc := input.document[i] + resource := doc.resource.google_container_cluster[name] + + resource.node_config.sandbox_config.sandbox_type != "gvisor" + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.google_container_cluster.%s.node_config.sandbox_config.sandbox_type", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "'sandbox_type' should be 'gvisor'", + "keyActualValue": sprintf("'sandbox_type' is set to '%s'", [resource.node_config.sandbox_config.sandbox_type]), + } +} + +# REGLA 4: sandbox_type incorrecto en google_container_node_pool. +CxPolicy[result] { + doc := input.document[i] + resource := doc.resource.google_container_node_pool[name] + + resource.node_config.sandbox_config.sandbox_type != "gvisor" + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.google_container_node_pool.%s.node_config.sandbox_config.sandbox_type", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "'sandbox_type' should be 'gvisor'", + "keyActualValue": sprintf("'sandbox_type' is set to '%s'", [resource.node_config.sandbox_config.sandbox_type]), + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_sandbox_disabled/test/negative1.tf b/assets/queries/terraform/gcp/gcp_gke_sandbox_disabled/test/negative1.tf new file mode 100644 index 00000000000..5a038181abf --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_gke_sandbox_disabled/test/negative1.tf @@ -0,0 +1,12 @@ +# Caso Negativo 1: Configuración correcta en un clúster principal +resource "google_container_cluster" "pass_cluster" { + name = "secure-main-cluster" + location = "us-central1" + + node_config { + image_type = "COS_CONTAINERD" + sandbox_config { + sandbox_type = "gvisor" + } + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_sandbox_disabled/test/negative2.tf b/assets/queries/terraform/gcp/gcp_gke_sandbox_disabled/test/negative2.tf new file mode 100644 index 00000000000..9bc60d92ffd --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_gke_sandbox_disabled/test/negative2.tf @@ -0,0 +1,12 @@ +# Caso Negativo 2: Configuración correcta en un pool de nodos independiente +resource "google_container_node_pool" "pass_pool" { + name = "secure-untrusted-pool" + cluster = "my-cluster" + + node_config { + image_type = "COS_CONTAINERD" + sandbox_config { + sandbox_type = "gvisor" + } + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_sandbox_disabled/test/positive1.tf b/assets/queries/terraform/gcp/gcp_gke_sandbox_disabled/test/positive1.tf new file mode 100644 index 00000000000..b8f9bd1346a --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_gke_sandbox_disabled/test/positive1.tf @@ -0,0 +1,9 @@ +resource "google_container_cluster" "fail_cluster_no_sandbox" { + name = "cluster-fail" + location = "us-central1" + + node_config { + machine_type = "e2-medium" + # FALLO: Falta sandbox_config + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_sandbox_disabled/test/positive2.tf b/assets/queries/terraform/gcp/gcp_gke_sandbox_disabled/test/positive2.tf new file mode 100644 index 00000000000..43a0a1006c9 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_gke_sandbox_disabled/test/positive2.tf @@ -0,0 +1,9 @@ +resource "google_container_node_pool" "fail_pool_no_sandbox" { + name = "pool-fail" + cluster = "my-cluster" + + node_config { + machine_type = "e2-medium" + # FALLO: Falta sandbox_config + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_sandbox_disabled/test/positive3.tf b/assets/queries/terraform/gcp/gcp_gke_sandbox_disabled/test/positive3.tf new file mode 100644 index 00000000000..65f09f4728b --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_gke_sandbox_disabled/test/positive3.tf @@ -0,0 +1,9 @@ +resource "google_container_cluster" "fail_cluster_wrong_type" { + name = "cluster-wrong-type" + + node_config { + sandbox_config { + sandbox_type = "other_runtime" # FALLO: Debe ser gvisor + } + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_sandbox_disabled/test/positive4.tf b/assets/queries/terraform/gcp/gcp_gke_sandbox_disabled/test/positive4.tf new file mode 100644 index 00000000000..f5c36d3aa4d --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_gke_sandbox_disabled/test/positive4.tf @@ -0,0 +1,10 @@ +resource "google_container_node_pool" "fail_pool_wrong_type" { + name = "pool-wrong-type" + cluster = "my-cluster" + + node_config { + sandbox_config { + sandbox_type = "disabled" # FALLO: Debe ser gvisor + } + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_sandbox_disabled/test/positive_expected_result.json b/assets/queries/terraform/gcp/gcp_gke_sandbox_disabled/test/positive_expected_result.json new file mode 100644 index 00000000000..3e0a67ec379 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_gke_sandbox_disabled/test/positive_expected_result.json @@ -0,0 +1,26 @@ +[ + { + "queryName": "GKE Sandbox (gVisor) Disabled", + "severity": "LOW", + "line": 5, + "fileName": "positive1.tf" + }, + { + "queryName": "GKE Sandbox (gVisor) Disabled", + "severity": "LOW", + "line": 5, + "fileName": "positive2.tf" + }, + { + "queryName": "GKE Sandbox (gVisor) Disabled", + "severity": "LOW", + "line": 6, + "fileName": "positive3.tf" + }, + { + "queryName": "GKE Sandbox (gVisor) Disabled", + "severity": "LOW", + "line": 7, + "fileName": "positive4.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_secrets_encryption_cmek_disabled/README.md b/assets/queries/terraform/gcp/gcp_gke_secrets_encryption_cmek_disabled/README.md new file mode 100644 index 00000000000..499f0c0645d --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_gke_secrets_encryption_cmek_disabled/README.md @@ -0,0 +1,50 @@ +# Regla KICS: GKE Secrets Not Encrypted with CMEK + +## Descripción General + +Esta regla de severidad **ALTA** verifica que los clústeres de GKE tengan habilitado el cifrado de secretos en la capa de aplicación mediante una clave de **Cloud KMS** gestionada por el cliente (**CMEK**). + +Por defecto, GKE cifra los datos en reposo a nivel de disco, pero los secretos de Kubernetes almacenados en la base de datos `etcd` requieren una capa de protección adicional. Al habilitar esta función, GKE utiliza una clave de cifrado de sobres (envelope encryption) donde una Clave de Cifrado de Datos (DEK) cifra el secreto, y dicha DEK es cifrada por una Clave de Cifrado de Claves (KEK) alojada en Cloud KMS. Esto permite que el cliente tenga soberanía absoluta sobre sus secretos, pudiendo revocar el acceso a los mismos instantáneamente inhabilitando la clave en KMS. + +## Lógica de la Regla + +La política audita el recurso `google_container_cluster` evaluando tres fallos de seguridad: +1. **Omisión:** No se define el bloque `database_encryption`. +2. **Desactivación:** El estado de cifrado se establece explícitamente en `DECRYPTED`. +3. **Configuración Incompleta:** Se activa el cifrado pero no se proporciona el identificador de la clave (`key_name`). + +## Casos de Fallo Detectados + +--- + +### Caso 1: Configuración de Cifrado Ausente +* **Descripción:** El clúster se aprovisiona sin la capa de protección para secretos en etcd. +* **Ubicación de la Alerta:** Bloque del recurso `google_container_cluster`. + +### Caso 2: Cifrado Deshabilitado +* **Descripción:** Se configura el bloque pero el estado se marca como no cifrado. +* **Ubicación de la Alerta:** Atributo `state` dentro de `database_encryption`. + +### Caso 3: Clave KMS Faltante +* **Descripción:** Se intenta cifrar pero no se referencia ninguna clave válida de Cloud KMS. +* **Ubicación de la Alerta:** Atributo `key_name` dentro de `database_encryption`. + +## Recurso Involucrado + +* `google_container_cluster` + +## Solución + +Configure el bloque `database_encryption` con el estado `ENCRYPTED` y asigne el recurso de clave correspondiente. + +```terraform +resource "google_container_cluster" "secure_cluster" { + name = "production-cluster" + location = "us-central1" + + # Solución técnica + database_encryption { + state = "ENCRYPTED" + key_name = "projects/my-project/locations/global/keyRings/my-ring/cryptoKeys/my-key" + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_secrets_encryption_cmek_disabled/metadata.json b/assets/queries/terraform/gcp/gcp_gke_secrets_encryption_cmek_disabled/metadata.json new file mode 100644 index 00000000000..e577ed16253 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_gke_secrets_encryption_cmek_disabled/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "83d1c89f-e07c-4748-9d96-51a78c49637c", + "queryName": "GKE Secrets Not Encrypted with CMEK", + "severity": "HIGH", + "category": "Encryption", + "descriptionText": "Ensures that GKE clusters have Application-Layer Secrets Encryption enabled using a Cloud KMS key. This protects sensitive data in etcd with a customer-managed encryption key.", + "descriptionUrl": "https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/container_cluster#database_encryption", + "platform": "Terraform", + "descriptionID": "83d1c89f", + "cloudProvider": "gcp", + "cwe": "CWE-312", + "riskScore": 9.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_secrets_encryption_cmek_disabled/query.rego b/assets/queries/terraform/gcp/gcp_gke_secrets_encryption_cmek_disabled/query.rego new file mode 100644 index 00000000000..65cce79b0e4 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_gke_secrets_encryption_cmek_disabled/query.rego @@ -0,0 +1,52 @@ +package Cx + +# REGLA 1: Bloque 'database_encryption' ausente. +CxPolicy[result] { + doc := input.document[i] + cluster := doc.resource.google_container_cluster[name] + + not cluster.database_encryption + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.google_container_cluster.%s", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "'database_encryption' block should be defined", + "keyActualValue": "'database_encryption' block is missing", + } +} + +# REGLA 2: Estado de cifrado incorrecto (DECRYPTED). +CxPolicy[result] { + doc := input.document[i] + cluster := doc.resource.google_container_cluster[name] + + cluster.database_encryption.state != "ENCRYPTED" + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.google_container_cluster.%s.database_encryption.state", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "'state' should be set to 'ENCRYPTED'", + "keyActualValue": sprintf("'state' is set to '%s'", [cluster.database_encryption.state]), + } +} + +# REGLA 3: Estado ENCRYPTED pero falta el nombre de la clave (key_name). +CxPolicy[result] { + doc := input.document[i] + cluster := doc.resource.google_container_cluster[name] + + cluster.database_encryption.state == "ENCRYPTED" + + key_name := object.get(cluster.database_encryption, "key_name", "") + key_name == "" + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.google_container_cluster.%s.database_encryption.key_name", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "'key_name' should be defined with a valid KMS key ID", + "keyActualValue": "'key_name' is missing or empty", + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_secrets_encryption_cmek_disabled/test/negative1.tf b/assets/queries/terraform/gcp/gcp_gke_secrets_encryption_cmek_disabled/test/negative1.tf new file mode 100644 index 00000000000..5db9fa37360 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_gke_secrets_encryption_cmek_disabled/test/negative1.tf @@ -0,0 +1,8 @@ +resource "google_container_cluster" "pass_encrypted" { + name = "secure-cluster" + + database_encryption { + state = "ENCRYPTED" + key_name = "projects/p1/locations/global/keyRings/r1/cryptoKeys/k1" + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_secrets_encryption_cmek_disabled/test/positive1.tf b/assets/queries/terraform/gcp/gcp_gke_secrets_encryption_cmek_disabled/test/positive1.tf new file mode 100644 index 00000000000..ea4cefc36b6 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_gke_secrets_encryption_cmek_disabled/test/positive1.tf @@ -0,0 +1,5 @@ +resource "google_container_cluster" "fail_missing_block" { + name = "no-encryption-block" + location = "us-central1" + # FALLO: database_encryption no existe +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_secrets_encryption_cmek_disabled/test/positive2.tf b/assets/queries/terraform/gcp/gcp_gke_secrets_encryption_cmek_disabled/test/positive2.tf new file mode 100644 index 00000000000..3de90ed7944 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_gke_secrets_encryption_cmek_disabled/test/positive2.tf @@ -0,0 +1,8 @@ +resource "google_container_cluster" "fail_decrypted" { + name = "explicit-decrypted" + + database_encryption { + state = "DECRYPTED" # FALLO: Debe ser ENCRYPTED + key_name = "" + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_secrets_encryption_cmek_disabled/test/positive3.tf b/assets/queries/terraform/gcp/gcp_gke_secrets_encryption_cmek_disabled/test/positive3.tf new file mode 100644 index 00000000000..aa06e7a3f8d --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_gke_secrets_encryption_cmek_disabled/test/positive3.tf @@ -0,0 +1,8 @@ +resource "google_container_cluster" "fail_missing_key" { + name = "encrypted-no-key" + + # FALLO: Falta key_name + database_encryption { + state = "ENCRYPTED" + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_secrets_encryption_cmek_disabled/test/positive_expected_result.json b/assets/queries/terraform/gcp/gcp_gke_secrets_encryption_cmek_disabled/test/positive_expected_result.json new file mode 100644 index 00000000000..cf68e051c08 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_gke_secrets_encryption_cmek_disabled/test/positive_expected_result.json @@ -0,0 +1,20 @@ +[ + { + "queryName": "GKE Secrets Not Encrypted with CMEK", + "severity": "HIGH", + "line": 1, + "fileName": "positive1.tf" + }, + { + "queryName": "GKE Secrets Not Encrypted with CMEK", + "severity": "HIGH", + "line": 5, + "fileName": "positive2.tf" + }, + { + "queryName": "GKE Secrets Not Encrypted with CMEK", + "severity": "HIGH", + "line": 5, + "fileName": "positive3.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_security_posture_manual/README.md b/assets/queries/terraform/gcp/gcp_gke_security_posture_manual/README.md new file mode 100644 index 00000000000..3080ebf7ef9 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_gke_security_posture_manual/README.md @@ -0,0 +1,45 @@ +# Regla KICS: GKE Security Posture Disabled (Manual) + +## Descripción General + +Esta regla informativa (**INFO**) de **Observabilidad** verifica si el **Security Posture Dashboard** de GKE está activo en la configuración del clúster. + +El panel de postura de seguridad es una herramienta nativa de Google Cloud que escanea automáticamente las cargas de trabajo de Kubernetes en busca de errores de configuración comunes y vulnerabilidades conocidas en las imágenes de los contenedores. Proporciona recomendaciones accionables para mejorar la seguridad sin añadir complejidad operativa. Dado que la disponibilidad de esta función puede variar según el canal de versiones de GKE y el tipo de licencia (Standard vs Autopilot), esta regla alerta sobre su ausencia para permitir una verificación manual de compatibilidad. + +## Lógica de la Regla + +La política audita el recurso `google_container_cluster` identificando dos escenarios: +1. **Omisión:** El bloque `security_posture_config` no está presente en el código Terraform. +2. **Desactivación:** El bloque existe pero el parámetro `mode` se ha establecido explícitamente como `DISABLED`. + +## Casos de Fallo Detectados + +--- + +### Caso 1: Configuración de Postura Ausente +* **Descripción:** El clúster no ha habilitado el panel de control de seguridad. Se recomienda verificar si la versión de GKE permite su activación. +* **Ubicación de la Alerta:** Nivel de recurso `google_container_cluster`. + +### Caso 2: Modo de Postura Deshabilitado +* **Descripción:** Se ha configurado la postura de seguridad pero se ha desactivado mediante el valor `DISABLED`. +* **Ubicación de la Alerta:** Atributo `mode` dentro de `security_posture_config`. + +## Recurso Involucrado + +* `google_container_cluster` + +## Solución + +Habilite la postura de seguridad configurando el modo en `BASIC` o `ENTERPRISE`. + +```terraform +resource "google_container_cluster" "compliant_cluster" { + name = "monitored-cluster" + location = "us-central1" + + # Solución recomendada para observabilidad de seguridad + security_posture_config { + mode = "BASIC" + vulnerability_mode = "VULNERABILITY_BASIC" + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_security_posture_manual/metadata.json b/assets/queries/terraform/gcp/gcp_gke_security_posture_manual/metadata.json new file mode 100644 index 00000000000..a119b6d3e04 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_gke_security_posture_manual/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "0d688e9b-79a1-4adb-8893-4cae93442ffd", + "queryName": "GKE Security Posture Disabled (Manual)", + "severity": "INFO", + "category": "Observability", + "descriptionText": "GKE Security Posture Dashboard provides visibility into the security status of the cluster. It is not explicitly enabled. Verify if the cluster version supports 'security_posture_config' with mode 'BASIC' or 'ENTERPRISE'.", + "descriptionUrl": "https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/container_cluster#security_posture_config", + "platform": "Terraform", + "descriptionID": "0d688e9b", + "cloudProvider": "gcp", + "cwe": "CWE-1038", + "riskScore": 0.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_security_posture_manual/query.rego b/assets/queries/terraform/gcp/gcp_gke_security_posture_manual/query.rego new file mode 100644 index 00000000000..cfeb14b6cb9 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_gke_security_posture_manual/query.rego @@ -0,0 +1,33 @@ +package Cx + +# REGLA 1: Bloque 'security_posture_config' ausente. +CxPolicy[result] { + doc := input.document[i] + cluster := doc.resource.google_container_cluster[name] + + not cluster.security_posture_config + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.google_container_cluster.%s", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "'security_posture_config' block should be defined with mode 'BASIC' or 'ENTERPRISE'", + "keyActualValue": "'security_posture_config' block is missing", + } +} + +# REGLA 2: Bloque presente, pero 'mode' es 'DISABLED'. +CxPolicy[result] { + doc := input.document[i] + cluster := doc.resource.google_container_cluster[name] + + cluster.security_posture_config.mode == "DISABLED" + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.google_container_cluster.%s.security_posture_config.mode", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "'mode' should be set to 'BASIC' or 'ENTERPRISE'", + "keyActualValue": "'mode' is set to 'DISABLED'", + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_security_posture_manual/test/negative1.tf b/assets/queries/terraform/gcp/gcp_gke_security_posture_manual/test/negative1.tf new file mode 100644 index 00000000000..22015e1b111 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_gke_security_posture_manual/test/negative1.tf @@ -0,0 +1,6 @@ +resource "google_container_cluster" "pass_basic" { + name = "secure-cluster-basic" + security_posture_config { + mode = "BASIC" + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_security_posture_manual/test/negative2.tf b/assets/queries/terraform/gcp/gcp_gke_security_posture_manual/test/negative2.tf new file mode 100644 index 00000000000..efe51b91f0f --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_gke_security_posture_manual/test/negative2.tf @@ -0,0 +1,6 @@ +resource "google_container_cluster" "pass_enterprise" { + name = "secure-cluster-enterprise" + security_posture_config { + mode = "ENTERPRISE" + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_security_posture_manual/test/positive1.tf b/assets/queries/terraform/gcp/gcp_gke_security_posture_manual/test/positive1.tf new file mode 100644 index 00000000000..9fef725d35d --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_gke_security_posture_manual/test/positive1.tf @@ -0,0 +1,5 @@ +resource "google_container_cluster" "fail_block_missing" { + name = "cluster-without-posture" + location = "us-central1" + # FALLO: Falta security_posture_config +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_security_posture_manual/test/positive2.tf b/assets/queries/terraform/gcp/gcp_gke_security_posture_manual/test/positive2.tf new file mode 100644 index 00000000000..07f2184f850 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_gke_security_posture_manual/test/positive2.tf @@ -0,0 +1,7 @@ +resource "google_container_cluster" "fail_mode_disabled" { + name = "cluster-posture-off" + + security_posture_config { + mode = "DISABLED" # FALLO: Debe ser BASIC o ENTERPRISE + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_security_posture_manual/test/positive_expected_result.json b/assets/queries/terraform/gcp/gcp_gke_security_posture_manual/test/positive_expected_result.json new file mode 100644 index 00000000000..6a757660260 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_gke_security_posture_manual/test/positive_expected_result.json @@ -0,0 +1,14 @@ +[ + { + "queryName": "GKE Security Posture Disabled (Manual)", + "severity": "INFO", + "line": 1, + "fileName": "positive1.tf" + }, + { + "queryName": "GKE Security Posture Disabled (Manual)", + "severity": "INFO", + "line": 5, + "fileName": "positive2.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_workload_identity_manual/README.md b/assets/queries/terraform/gcp/gcp_gke_workload_identity_manual/README.md new file mode 100644 index 00000000000..62834eadff2 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_gke_workload_identity_manual/README.md @@ -0,0 +1,54 @@ +# Regla KICS: GKE Workload Identity & Dedicated SA (Manual) + +## Descripción General + +Esta regla informativa (**INFO**) de **Access Control** audita la configuración de identidad para las cargas de trabajo que se ejecutan en **Google Kubernetes Engine (GKE)**. + +La mejor práctica de seguridad para que las aplicaciones de GKE accedan a los servicios de Google Cloud (como Cloud Storage o BigQuery) es utilizar **Workload Identity**. Esta funcionalidad elimina la necesidad de descargar y gestionar claves JSON de cuentas de servicio, vinculando de forma segura una Kubernetes Service Account (KSA) con una Google Service Account (GSA). + +Sin embargo, habilitar la función es solo la mitad del trabajo. Una configuración segura requiere un mapeo **1:1**; si múltiples aplicaciones comparten la misma identidad de Google, un compromiso en una de ellas otorgaría acceso lateral a los recursos de las demás. + +## Lógica de la Regla + +La política audita el recurso `google_container_cluster` en dos fases: +1. **Validación de Función:** Detecta si `workload_identity_config` está ausente, lo que obliga a los Pods a usar la identidad del nodo (riesgo alto). +2. **Auditoría de Gobernanza:** Si la función está activa, alerta al auditor para que verifique manualmente en el código que cada microservicio tiene su propia vinculación dedicada. + +## Casos de Fallo Detectados + +--- + +### Caso 1: Workload Identity Deshabilitado +* **Descripción:** El clúster carece de la infraestructura necesaria para identidades granulares. +* **Ubicación de la Alerta:** Nivel de recurso `google_container_cluster`. + +### Caso 2: Revisión de Mapeo de Identidades +* **Descripción:** La funcionalidad está activa, pero se requiere confirmar que no se están utilizando cuentas de servicio compartidas entre distintos microservicios. +* **Ubicación de la Alerta:** Bloque `workload_identity_config`. + +## Recurso Involucrado + +* `google_container_cluster` + +## Solución + +1. Habilite Workload Identity en su recurso de clúster. +2. Implemente vinculaciones granulares utilizando el rol `roles/iam.workloadIdentityUser`. + +```terraform +resource "google_container_cluster" "compliant_cluster" { + name = "secure-cluster" + location = "us-central1" + + # Habilitar la función + workload_identity_config { + workload_pool = "${var.project_id}.svc.id.goog" + } +} + +# Ejemplo de vinculación manual a verificar (Mapeo 1:1) +resource "google_service_account_iam_member" "dedicated_binding" { + service_account_id = google_service_account.app_specific_sa.name + role = "roles/iam.workloadIdentityUser" + member = "serviceAccount:${var.project_id}.svc.id.goog[namespace/ksa-name]" +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_workload_identity_manual/metadata.json b/assets/queries/terraform/gcp/gcp_gke_workload_identity_manual/metadata.json new file mode 100644 index 00000000000..fb66fe944b8 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_gke_workload_identity_manual/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "edea054b-6ed2-4be2-bad2-459891848678", + "queryName": "GKE Workload Identity & Dedicated SA (Manual)", + "severity": "INFO", + "category": "Access Control", + "descriptionText": "Ensures that GKE clusters have Workload Identity enabled. If enabled, manual verification is required to ensure that workloads use dedicated Google Service Accounts (1:1 mapping) rather than shared ones.", + "descriptionUrl": "https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/container_cluster#workload_identity_config", + "platform": "Terraform", + "descriptionID": "edea054b", + "cloudProvider": "gcp", + "cwe": "CWE-284", + "riskScore": 0.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_workload_identity_manual/query.rego b/assets/queries/terraform/gcp/gcp_gke_workload_identity_manual/query.rego new file mode 100644 index 00000000000..d10a078e579 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_gke_workload_identity_manual/query.rego @@ -0,0 +1,33 @@ +package Cx + +# REGLA 1: Workload Identity no habilitado (Missing Attribute). +CxPolicy[result] { + doc := input.document[i] + cluster := doc.resource.google_container_cluster[name] + + not cluster.workload_identity_config + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.google_container_cluster.%s", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "'workload_identity_config' should be enabled to support dedicated Service Accounts", + "keyActualValue": "'workload_identity_config' is missing", + } +} + +# REGLA 2: Workload Identity habilitado (Verificación Manual de Bindings). +CxPolicy[result] { + doc := input.document[i] + cluster := doc.resource.google_container_cluster[name] + + cluster.workload_identity_config + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.google_container_cluster.%s.workload_identity_config", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "Verify that workloads use dedicated Service Accounts (no shared SAs)", + "keyActualValue": "Workload Identity is enabled. Manual verification of bindings required.", + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_workload_identity_manual/test/negative1.tf b/assets/queries/terraform/gcp/gcp_gke_workload_identity_manual/test/negative1.tf new file mode 100644 index 00000000000..015f1237ef7 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_gke_workload_identity_manual/test/negative1.tf @@ -0,0 +1,4 @@ +# Caso negativo: No hay clústeres GKE que auditar. +resource "google_compute_network" "vpc" { + name = "main-vpc" +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_workload_identity_manual/test/positive1.tf b/assets/queries/terraform/gcp/gcp_gke_workload_identity_manual/test/positive1.tf new file mode 100644 index 00000000000..4521fa4170f --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_gke_workload_identity_manual/test/positive1.tf @@ -0,0 +1,5 @@ +resource "google_container_cluster" "fail_disabled" { + name = "insecure-cluster" + location = "us-central1" + # FALLO: Falta workload_identity_config +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_workload_identity_manual/test/positive2.tf b/assets/queries/terraform/gcp/gcp_gke_workload_identity_manual/test/positive2.tf new file mode 100644 index 00000000000..058abb750cc --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_gke_workload_identity_manual/test/positive2.tf @@ -0,0 +1,8 @@ +resource "google_container_cluster" "fail_manual_check" { + name = "cluster-with-wi" + + workload_identity_config { + workload_pool = "my-project.svc.id.goog" + } + # INFO: Requiere revisión manual de los bindings de las aplicaciones +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_workload_identity_manual/test/positive_expected_result.json b/assets/queries/terraform/gcp/gcp_gke_workload_identity_manual/test/positive_expected_result.json new file mode 100644 index 00000000000..1c56c81e5e8 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_gke_workload_identity_manual/test/positive_expected_result.json @@ -0,0 +1,14 @@ +[ + { + "queryName": "GKE Workload Identity & Dedicated SA (Manual)", + "severity": "INFO", + "line": 1, + "fileName": "positive1.tf" + }, + { + "queryName": "GKE Workload Identity & Dedicated SA (Manual)", + "severity": "INFO", + "line": 4, + "fileName": "positive2.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_http_load_balancer_logging_disabled/README.md b/assets/queries/terraform/gcp/gcp_http_load_balancer_logging_disabled/README.md new file mode 100644 index 00000000000..f5e5a8c4375 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_http_load_balancer_logging_disabled/README.md @@ -0,0 +1,47 @@ +# Regla KICS: GCP HTTP(S) Load Balancer Logging Disabled + +## Descripción General + +Esta regla de severidad **MEDIA** verifica que el registro de acceso (Logging) esté habilitado en los recursos `google_compute_backend_service` de Google Cloud. + +Los Backend Services gestionan el tráfico que el balanceador de carga HTTP(S) distribuye hacia los grupos de instancias o buckets. Habilitar los logs de Cloud Armor y del balanceador permite capturar metadatos críticos de cada transacción HTTP: dirección IP de origen, protocolos, latencias de respuesta, y códigos de estado (2xx, 4xx, 5xx). Sin estos registros, la capacidad de respuesta ante incidentes de seguridad (como ataques DoS o inyecciones) y la depuración de errores de infraestructura se ven seriamente limitadas. + +## Lógica de la Regla + +La política audita el recurso `google_compute_backend_service` bajo dos criterios: +1. **Omisión:** Detecta si el bloque `log_config` no ha sido definido. +2. **Desactivación:** Detecta si el atributo `enable` dentro de `log_config` tiene el valor booleano `false`. + +## Casos de Fallo Detectados + +--- + +### Caso 1: Configuración de Logging Ausente +* **Descripción:** El servicio de backend se crea sin parámetros de registro, lo que deshabilita la telemetría de tráfico por defecto. +* **Ubicación de la Alerta:** Nivel de recurso `google_compute_backend_service`. + +### Caso 2: Logging Deshabilitado Explícitamente +* **Descripción:** Se define el bloque de configuración pero se apaga el servicio de logs. +* **Ubicación de la Alerta:** Atributo `enable` dentro de `log_config`. + +## Recurso Involucrado + +* `google_compute_backend_service` + +## Solución + +Añada el bloque `log_config` con el parámetro `enable = true`. Se recomienda un `sample_rate` de `1.0` para producción. + +```terraform +resource "google_compute_backend_service" "compliant_service" { + name = "web-backend-service" + protocol = "HTTP" + port_name = "http" + timeout_sec = 10 + + # Solución técnica + log_config { + enable = true + sample_rate = 1.0 + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_http_load_balancer_logging_disabled/metadata.json b/assets/queries/terraform/gcp/gcp_http_load_balancer_logging_disabled/metadata.json new file mode 100644 index 00000000000..3e26bf5e632 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_http_load_balancer_logging_disabled/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "0ef3f3f1-0592-475b-b073-81ac1ac869e2", + "queryName": "GCP HTTP(S) Load Balancer Logging Disabled", + "severity": "MEDIUM", + "category": "Observability", + "descriptionText": "Ensures that logging is enabled for Google Compute Backend Services used in HTTP(S) Load Balancers. Access logs are essential for monitoring traffic patterns, latency, and identifying potential security threats.", + "descriptionUrl": "https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_backend_service#log_config", + "platform": "Terraform", + "descriptionID": "0ef3f3f1", + "cloudProvider": "gcp", + "cwe": "CWE-778", + "riskScore": 5.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_http_load_balancer_logging_disabled/query.rego b/assets/queries/terraform/gcp/gcp_http_load_balancer_logging_disabled/query.rego new file mode 100644 index 00000000000..e15697ad864 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_http_load_balancer_logging_disabled/query.rego @@ -0,0 +1,33 @@ +package Cx + +# REGLA 1: Bloque 'log_config' ausente. +CxPolicy[result] { + doc := input.document[i] + bs := doc.resource.google_compute_backend_service[name] + + not bs.log_config + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.google_compute_backend_service.%s", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "log_config should be defined with enable set to true", + "keyActualValue": "log_config is missing", + } +} + +# REGLA 2: Bloque 'log_config' existe pero 'enable' es false. +CxPolicy[result] { + doc := input.document[i] + bs := doc.resource.google_compute_backend_service[name] + + bs.log_config.enable == false + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.google_compute_backend_service.%s.log_config.enable", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "log_config.enable should be set to true", + "keyActualValue": "log_config.enable is set to false", + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_http_load_balancer_logging_disabled/test/negative1.tf b/assets/queries/terraform/gcp/gcp_http_load_balancer_logging_disabled/test/negative1.tf new file mode 100644 index 00000000000..66e55cbbf8c --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_http_load_balancer_logging_disabled/test/negative1.tf @@ -0,0 +1,8 @@ +resource "google_compute_backend_service" "pass_logs" { + name = "backend-with-logs" + + log_config { + enable = true + sample_rate = 1.0 + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_http_load_balancer_logging_disabled/test/positive1.tf b/assets/queries/terraform/gcp/gcp_http_load_balancer_logging_disabled/test/positive1.tf new file mode 100644 index 00000000000..0180ab2193a --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_http_load_balancer_logging_disabled/test/positive1.tf @@ -0,0 +1,5 @@ +resource "google_compute_backend_service" "fail_missing_log" { + name = "backend-no-logs" + protocol = "HTTP" + # FALLO: No tiene bloque log_config +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_http_load_balancer_logging_disabled/test/positive2.tf b/assets/queries/terraform/gcp/gcp_http_load_balancer_logging_disabled/test/positive2.tf new file mode 100644 index 00000000000..38412e41a49 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_http_load_balancer_logging_disabled/test/positive2.tf @@ -0,0 +1,7 @@ +resource "google_compute_backend_service" "fail_disabled_log" { + name = "backend-disabled-logs" + + log_config { + enable = false # FALLO: Debe ser true + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_http_load_balancer_logging_disabled/test/positive_expected_result.json b/assets/queries/terraform/gcp/gcp_http_load_balancer_logging_disabled/test/positive_expected_result.json new file mode 100644 index 00000000000..1caf795a721 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_http_load_balancer_logging_disabled/test/positive_expected_result.json @@ -0,0 +1,14 @@ +[ + { + "queryName": "GCP HTTP(S) Load Balancer Logging Disabled", + "severity": "MEDIUM", + "line": 1, + "fileName": "positive1.tf" + }, + { + "queryName": "GCP HTTP(S) Load Balancer Logging Disabled", + "severity": "MEDIUM", + "line": 5, + "fileName": "positive2.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_iap_backend_service_disabled/README.md b/assets/queries/terraform/gcp/gcp_iap_backend_service_disabled/README.md new file mode 100644 index 00000000000..d2cc1522d00 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_iap_backend_service_disabled/README.md @@ -0,0 +1,53 @@ +# Regla KICS: IAP Disabled on Backend Service + +## Descripción General + +Esta regla verifica que **Identity-Aware Proxy (IAP)** esté habilitado en los recursos `google_compute_backend_service`. + +IAP es un componente de seguridad Zero Trust que intercepta las solicitudes web enviadas a tu aplicación, autentica al usuario mediante su identidad de Google y solo permite el paso de la solicitud si el usuario está autorizado. + +**Nota sobre Firewall (Manual):** +Habilitar IAP es solo el primer paso. Para cumplir completamente con la normativa de seguridad ("Permitir solo tráfico de Google"), debes configurar manualmente las reglas de firewall de tu VPC para permitir el tráfico **únicamente** desde los rangos de IP de los balanceadores de carga de Google (`130.211.0.0/22` y `35.191.0.0/16`) hacia tus instancias, bloqueando todo el tráfico directo desde Internet. + +## Lógica de la Regla + +La política audita el recurso `google_compute_backend_service`: +1. Verifica si existe el bloque de configuración `iap`. +2. Si el bloque no existe, se considera que el servicio no está protegido por identidad. +3. Verifica que, si el bloque existe, se definan las credenciales necesarias (`oauth2_client_id` y `oauth2_client_secret`). + +## Casos de Fallo Detectados + +### Caso 1: IAP Deshabilitado + +* **Descripción:** El servicio de backend se ha definido sin el bloque `iap`, lo que significa que el tráfico llega directamente (o a través del LB estándar) sin verificación de identidad previa por parte de IAP. +* **Ubicación de la Alerta:** Recurso `google_compute_backend_service`. + +### Caso 2: Cliente ID de OAuth Ausente + +* **Descripción:** El bloque `iap` está presente pero omite el `oauth2_client_id`, impidiendo la autenticación. +* **Ubicación de la Alerta:** Bloque `iap`. + +### Caso 3: Cliente Secret de OAuth Ausente + +* **Descripción:** El bloque `iap` está presente pero omite el `oauth2_client_secret`, impidiendo la autenticación. +* **Ubicación de la Alerta:** Bloque `iap`. + +## Recurso Involucrado + +* `google_compute_backend_service` + +## Solución + +Define el bloque `iap` dentro del servicio de backend e incluye las credenciales de OAuth necesarias. + +```terraform +resource "google_compute_backend_service" "secure" { + name = "iap-backend-service" + health_checks = [google_compute_health_check.default.id] + + iap { + oauth2_client_id = "abc-123.apps.googleusercontent.com" + oauth2_client_secret = "secret-key" + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_iap_backend_service_disabled/metadata.json b/assets/queries/terraform/gcp/gcp_iap_backend_service_disabled/metadata.json new file mode 100644 index 00000000000..a9432c792f8 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_iap_backend_service_disabled/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "3f1222a9-0161-4fe8-b188-ccabaf7a0ba5", + "queryName": "IAP Disabled on Backend Service", + "severity": "MEDIUM", + "category": "Access Control", + "descriptionText": "Ensures that Identity-Aware Proxy (IAP) is enabled for Google Compute Backend Services. IAP verifies user identity and context before allowing access to applications.", + "descriptionUrl": "https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_backend_service#iap", + "platform": "Terraform", + "descriptionID": "3f1222a9", + "cloudProvider": "gcp", + "cwe": "CWE-284", + "riskScore": 5.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_iap_backend_service_disabled/query.rego b/assets/queries/terraform/gcp/gcp_iap_backend_service_disabled/query.rego new file mode 100644 index 00000000000..7ba18522e71 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_iap_backend_service_disabled/query.rego @@ -0,0 +1,51 @@ +package Cx + +# REGLA 1: El bloque 'iap' está ausente en google_compute_backend_service. +CxPolicy[result] { + doc := input.document[i] + bs := doc.resource.google_compute_backend_service[name] + + not bs.iap + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.google_compute_backend_service.%s", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": sprintf("'google_compute_backend_service.%s' should have an 'iap' block defined", [name]), + "keyActualValue": sprintf("'google_compute_backend_service.%s' is missing the 'iap' block", [name]), + } +} + +# REGLA 2: El bloque 'iap' existe pero falta 'oauth2_client_id'. +CxPolicy[result] { + doc := input.document[i] + bs := doc.resource.google_compute_backend_service[name] + + bs.iap + not bs.iap.oauth2_client_id + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.google_compute_backend_service.%s.iap", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "'oauth2_client_id' should be defined within the 'iap' block", + "keyActualValue": "'oauth2_client_id' is missing", + } +} + +# REGLA 3: El bloque 'iap' existe pero falta 'oauth2_client_secret'. +CxPolicy[result] { + doc := input.document[i] + bs := doc.resource.google_compute_backend_service[name] + + bs.iap + not bs.iap.oauth2_client_secret + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.google_compute_backend_service.%s.iap", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "'oauth2_client_secret' should be defined within the 'iap' block", + "keyActualValue": "'oauth2_client_secret' is missing", + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_iap_backend_service_disabled/test/negative1.tf b/assets/queries/terraform/gcp/gcp_iap_backend_service_disabled/test/negative1.tf new file mode 100644 index 00000000000..1d225647a1a --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_iap_backend_service_disabled/test/negative1.tf @@ -0,0 +1,8 @@ +resource "google_compute_backend_service" "pass_secure" { + name = "fully-secure-service" + + iap { + oauth2_client_id = "client-id" + oauth2_client_secret = "secret-val" + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_iap_backend_service_disabled/test/positive1.tf b/assets/queries/terraform/gcp/gcp_iap_backend_service_disabled/test/positive1.tf new file mode 100644 index 00000000000..054d5dd2c75 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_iap_backend_service_disabled/test/positive1.tf @@ -0,0 +1,4 @@ +resource "google_compute_backend_service" "fail_no_iap" { + name = "no-iap-service" + health_checks = ["check-id"] +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_iap_backend_service_disabled/test/positive2.tf b/assets/queries/terraform/gcp/gcp_iap_backend_service_disabled/test/positive2.tf new file mode 100644 index 00000000000..a83e39a3ac1 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_iap_backend_service_disabled/test/positive2.tf @@ -0,0 +1,6 @@ +resource "google_compute_backend_service" "fail_no_id" { + name = "no-client-id" + iap { + oauth2_client_secret = "some-secret" + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_iap_backend_service_disabled/test/positive3.tf b/assets/queries/terraform/gcp/gcp_iap_backend_service_disabled/test/positive3.tf new file mode 100644 index 00000000000..33eb97d6db4 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_iap_backend_service_disabled/test/positive3.tf @@ -0,0 +1,6 @@ +resource "google_compute_backend_service" "fail_no_secret" { + name = "no-client-secret" + iap { + oauth2_client_id = "some-id" + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_iap_backend_service_disabled/test/positive_expected_result.json b/assets/queries/terraform/gcp/gcp_iap_backend_service_disabled/test/positive_expected_result.json new file mode 100644 index 00000000000..89832969a28 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_iap_backend_service_disabled/test/positive_expected_result.json @@ -0,0 +1,20 @@ +[ + { + "queryName": "IAP Disabled on Backend Service", + "severity": "MEDIUM", + "line": 1, + "fileName": "positive1.tf" + }, + { + "queryName": "IAP Disabled on Backend Service", + "severity": "MEDIUM", + "line": 3, + "fileName": "positive2.tf" + }, + { + "queryName": "IAP Disabled on Backend Service", + "severity": "MEDIUM", + "line": 3, + "fileName": "positive3.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_sql_postgresql_log_error_verbosity_verbose/README.md b/assets/queries/terraform/gcp/gcp_sql_postgresql_log_error_verbosity_verbose/README.md new file mode 100644 index 00000000000..ccb1f2e6d79 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_sql_postgresql_log_error_verbosity_verbose/README.md @@ -0,0 +1,40 @@ +# Regla KICS: Cloud SQL PostgreSQL log_error_verbosity is Verbose + +## Descripción General + +Esta regla verifica la configuración del flag de base de datos `log_error_verbosity` en instancias de **Google Cloud SQL (PostgreSQL)**. + +Este parámetro controla la cantidad de detalles que se escriben en el registro de errores del servidor. El nivel `verbose` incluye información interna como código fuente y números de línea, lo cual no es recomendado para entornos de producción por motivos de seguridad y rendimiento. + +## Lógica de la Regla + +La política inspecciona el recurso `google_sql_database_instance`: +1. Verifica si la versión de la base de datos contiene "POSTGRES". +2. Normaliza el bloque `settings.database_flags` para procesar correctamente tanto configuraciones con un único flag como con múltiples. +3. Si encuentra un flag llamado `log_error_verbosity` con el valor `verbose`, genera una alerta. + +## Casos de Fallo Detectados + +### Caso 1: Verbosity Insegura + +* **Descripción:** El flag está explícitamente configurado como `verbose`. +* **Ubicación de la Alerta:** Bloque `database_flags`. + +## Recurso Involucrado + +* `google_sql_database_instance` + +## Solución + +Establece el valor en `default`, `terse` o elimina el flag. + +```terraform +resource "google_sql_database_instance" "secure" { + database_version = "POSTGRES_14" + settings { + database_flags { + name = "log_error_verbosity" + value = "default" + } + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_sql_postgresql_log_error_verbosity_verbose/metadata.json b/assets/queries/terraform/gcp/gcp_sql_postgresql_log_error_verbosity_verbose/metadata.json new file mode 100644 index 00000000000..ac5563b73c0 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_sql_postgresql_log_error_verbosity_verbose/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "80d98d7b-2f12-4a8c-8cc9-fd17ca19c569", + "queryName": "Cloud SQL PostgreSQL log_error_verbosity is Verbose", + "severity": "MEDIUM", + "category": "Observability", + "descriptionText": "Ensures that the 'log_error_verbosity' flag for Cloud SQL PostgreSQL instances is set to 'default' or 'terse'. Setting it to 'verbose' generates excessive logs containing internal source code details, which can be a security risk.", + "descriptionUrl": "https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/sql_database_instance", + "platform": "Terraform", + "descriptionID": "80d98d7b", + "cloudProvider": "gcp", + "cwe": "CWE-532", + "riskScore": 5.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_sql_postgresql_log_error_verbosity_verbose/query.rego b/assets/queries/terraform/gcp/gcp_sql_postgresql_log_error_verbosity_verbose/query.rego new file mode 100644 index 00000000000..080bfd7bc97 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_sql_postgresql_log_error_verbosity_verbose/query.rego @@ -0,0 +1,25 @@ +package Cx + +ensure_array(x) = x { is_array(x) } +ensure_array(x) = [x] { is_object(x) } + +CxPolicy[result] { + doc := input.document[i] + resource := doc.resource.google_sql_database_instance[name] + + contains(resource.database_version, "POSTGRES") + + flags := ensure_array(resource.settings.database_flags) + flag := flags[j] + + flag.name == "log_error_verbosity" + lower(flag.value) == "verbose" + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.google_sql_database_instance.%s.settings.database_flags", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "'log_error_verbosity' should be set to 'default' or 'terse'", + "keyActualValue": sprintf("'log_error_verbosity' is set to '%s'", [flag.value]), + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_sql_postgresql_log_error_verbosity_verbose/test/negative1.tf b/assets/queries/terraform/gcp/gcp_sql_postgresql_log_error_verbosity_verbose/test/negative1.tf new file mode 100644 index 00000000000..7d68bad7488 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_sql_postgresql_log_error_verbosity_verbose/test/negative1.tf @@ -0,0 +1,11 @@ +resource "google_sql_database_instance" "pass" { + name = "postgres-ok" + database_version = "POSTGRES_14" + + settings { + database_flags { + name = "log_error_verbosity" + value = "default" + } + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_sql_postgresql_log_error_verbosity_verbose/test/positive1.tf b/assets/queries/terraform/gcp/gcp_sql_postgresql_log_error_verbosity_verbose/test/positive1.tf new file mode 100644 index 00000000000..78036f66a5e --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_sql_postgresql_log_error_verbosity_verbose/test/positive1.tf @@ -0,0 +1,12 @@ +resource "google_sql_database_instance" "fail_single" { + name = "postgres-single-flag" + database_version = "POSTGRES_14" + + settings { + tier = "db-f1-micro" + database_flags { + name = "log_error_verbosity" + value = "verbose" + } + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_sql_postgresql_log_error_verbosity_verbose/test/positive2.tf b/assets/queries/terraform/gcp/gcp_sql_postgresql_log_error_verbosity_verbose/test/positive2.tf new file mode 100644 index 00000000000..d8092b0e90f --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_sql_postgresql_log_error_verbosity_verbose/test/positive2.tf @@ -0,0 +1,18 @@ +resource "google_sql_database_instance" "fail_multiple" { + name = "postgres-multiple-flags" + database_version = "POSTGRES_14" + + settings { + tier = "db-f1-micro" + + database_flags { + name = "log_connections" + value = "on" + } + + database_flags { + name = "log_error_verbosity" + value = "verbose" + } + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_sql_postgresql_log_error_verbosity_verbose/test/positive_expected_result.json b/assets/queries/terraform/gcp/gcp_sql_postgresql_log_error_verbosity_verbose/test/positive_expected_result.json new file mode 100644 index 00000000000..1f14d5137b6 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_sql_postgresql_log_error_verbosity_verbose/test/positive_expected_result.json @@ -0,0 +1,14 @@ +[ + { + "queryName": "Cloud SQL PostgreSQL log_error_verbosity is Verbose", + "severity": "MEDIUM", + "line": 7, + "fileName": "positive1.tf" + }, + { + "queryName": "Cloud SQL PostgreSQL log_error_verbosity is Verbose", + "severity": "MEDIUM", + "line": 8, + "fileName": "positive2.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_sql_postgresql_log_statement_improperly_set/README.md b/assets/queries/terraform/gcp/gcp_sql_postgresql_log_statement_improperly_set/README.md new file mode 100644 index 00000000000..25aed51b502 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_sql_postgresql_log_statement_improperly_set/README.md @@ -0,0 +1,51 @@ +# Regla KICS: Cloud SQL PostgreSQL log_statement Improperly Set + +## Descripción General + +Esta regla verifica la configuración del flag `log_statement` en instancias de **Google Cloud SQL (PostgreSQL)**. + +Este parámetro controla qué sentencias SQL se registran en los logs del servidor: +* **`none`:** No registra ninguna sentencia (Default). +* **`ddl`:** Registra sentencias de definición de datos (CREATE, ALTER, DROP). Recomendado por CIS Benchmark como línea base. +* **`mod`:** Registra DDL y sentencias de modificación de datos (INSERT, UPDATE, DELETE). +* **`all`:** Registra todas las sentencias. + +Para cumplir con normativas de auditoría y seguridad, se debe configurar al menos en `ddl` para rastrear cambios estructurales en la base de datos que podrían comprometer la integridad de la misma. + +## Lógica de la Regla + +La política evalúa el recurso `google_sql_database_instance` (PostgreSQL): +1. **Flag Ausente:** Si `log_statement` no se encuentra dentro de la lista de flags configurados, falla (ya que el valor por defecto de PostgreSQL es insuficiente para auditoría). +2. **Flag Incorrecto:** Si `log_statement` está presente pero su valor es explícitamente `none`, falla. + +## Casos de Fallo Detectados + +### Caso 1: Configuración Ausente +* **Descripción:** No se ha definido el flag, por lo que la base de datos no está auditando sentencias críticas. +* **Ubicación de la Alerta:** Bloque `database_flags`. + +### Caso 2: Auditoría Deshabilitada Explícitamente +* **Descripción:** El flag está configurado con el valor `none`, desactivando el registro de sentencias. +* **Ubicación de la Alerta:** Bloque `database_flags`. + +## Recurso Involucrado + +* `google_sql_database_instance` + +## Solución + +Establezca el valor del flag `log_statement` en `ddl` (mínimo recomendado para auditoría), `mod` o `all`. + +```terraform +resource "google_sql_database_instance" "secure" { + name = "secure-postgresql" + database_version = "POSTGRES_14" + region = "us-central1" + + settings { + database_flags { + name = "log_statement" + value = "ddl" + } + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_sql_postgresql_log_statement_improperly_set/metadata.json b/assets/queries/terraform/gcp/gcp_sql_postgresql_log_statement_improperly_set/metadata.json new file mode 100644 index 00000000000..46a857f7d7b --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_sql_postgresql_log_statement_improperly_set/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "ede2d9e4-d3a6-4751-a89b-561bc9bacbe4", + "queryName": "Cloud SQL PostgreSQL log_statement Improperly Set", + "severity": "MEDIUM", + "category": "Observability", + "descriptionText": "Ensures that the 'log_statement' database flag for Cloud SQL PostgreSQL instances is set to 'ddl', 'mod', or 'all'. The default value is 'none', which disables statement logging and hinders auditing capability.", + "descriptionUrl": "https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/sql_database_instance", + "platform": "Terraform", + "descriptionID": "ede2d9e4", + "cloudProvider": "gcp", + "cwe": "CWE-778", + "riskScore": 5.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_sql_postgresql_log_statement_improperly_set/query.rego b/assets/queries/terraform/gcp/gcp_sql_postgresql_log_statement_improperly_set/query.rego new file mode 100644 index 00000000000..a8fc36e8108 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_sql_postgresql_log_statement_improperly_set/query.rego @@ -0,0 +1,49 @@ +package Cx + +ensure_array(x) = x { is_array(x) } +ensure_array(x) = [x] { is_object(x) } + +has_log_statement(flags_list) { + flag := flags_list[_] + flag.name == "log_statement" +} + +# REGLA 1: El flag 'log_statement' no está definido (Ausente). +CxPolicy[result] { + doc := input.document[i] + resource := doc.resource.google_sql_database_instance[name] + + contains(resource.database_version, "POSTGRES") + + flags_list := ensure_array(resource.settings.database_flags) + not has_log_statement(flags_list) + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.google_sql_database_instance.%s.settings.database_flags", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "'database_flags' should include 'log_statement' set to 'ddl', 'mod', or 'all'", + "keyActualValue": "'log_statement' flag is missing (defaults to 'none')", + } +} + +# REGLA 2: El flag 'log_statement' existe pero está en 'none'. +CxPolicy[result] { + doc := input.document[i] + resource := doc.resource.google_sql_database_instance[name] + + contains(resource.database_version, "POSTGRES") + + flags_list := ensure_array(resource.settings.database_flags) + flag := flags_list[_] + flag.name == "log_statement" + lower(flag.value) == "none" + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.google_sql_database_instance.%s.settings.database_flags", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "'log_statement' should be set to 'ddl', 'mod', or 'all'", + "keyActualValue": "'log_statement' is set to 'none'", + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_sql_postgresql_log_statement_improperly_set/test/negative1.tf b/assets/queries/terraform/gcp/gcp_sql_postgresql_log_statement_improperly_set/test/negative1.tf new file mode 100644 index 00000000000..f399c7b136c --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_sql_postgresql_log_statement_improperly_set/test/negative1.tf @@ -0,0 +1,11 @@ +# Caso Pass: Valor DDL +resource "google_sql_database_instance" "pass_ddl" { + name = "postgres-ok-ddl" + database_version = "POSTGRES_13" + settings { + database_flags { + name = "log_statement" + value = "ddl" + } + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_sql_postgresql_log_statement_improperly_set/test/positive1.tf b/assets/queries/terraform/gcp/gcp_sql_postgresql_log_statement_improperly_set/test/positive1.tf new file mode 100644 index 00000000000..3816f570485 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_sql_postgresql_log_statement_improperly_set/test/positive1.tf @@ -0,0 +1,12 @@ +resource "google_sql_database_instance" "fail_missing_flag" { + name = "postgres-no-audit" + database_version = "POSTGRES_13" + + settings { + tier = "db-f1-micro" + database_flags { + name = "log_connections" + value = "on" + } + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_sql_postgresql_log_statement_improperly_set/test/positive2.tf b/assets/queries/terraform/gcp/gcp_sql_postgresql_log_statement_improperly_set/test/positive2.tf new file mode 100644 index 00000000000..74e7691edcc --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_sql_postgresql_log_statement_improperly_set/test/positive2.tf @@ -0,0 +1,11 @@ +resource "google_sql_database_instance" "fail_explicit_none" { + name = "postgres-none-audit" + database_version = "POSTGRES_14" + + settings { + database_flags { + name = "log_statement" + value = "none" # FALLO + } + } +} \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_sql_postgresql_log_statement_improperly_set/test/positive_expected_result.json b/assets/queries/terraform/gcp/gcp_sql_postgresql_log_statement_improperly_set/test/positive_expected_result.json new file mode 100644 index 00000000000..dd142772ba1 --- /dev/null +++ b/assets/queries/terraform/gcp/gcp_sql_postgresql_log_statement_improperly_set/test/positive_expected_result.json @@ -0,0 +1,14 @@ +[ + { + "queryName": "Cloud SQL PostgreSQL log_statement Improperly Set", + "severity": "MEDIUM", + "line": 7, + "fileName": "positive1.tf" + }, + { + "queryName": "Cloud SQL PostgreSQL log_statement Improperly Set", + "severity": "MEDIUM", + "line": 6, + "fileName": "positive2.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_activity_tracker_global_events_disabled/README.md b/assets/queries/terraform/ibm/ibm_activity_tracker_global_events_disabled/README.md new file mode 100644 index 00000000000..e0bac430cd4 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_activity_tracker_global_events_disabled/README.md @@ -0,0 +1,65 @@ +# Regla KICS: Log de Auditoría para IBM Cloud IAM (Activity Tracker) + +## Descripción General + +Esta regla de KICS para Terraform asegura que una instancia de IBM Cloud Activity Tracker esté configurada para capturar eventos de auditoría, especialmente los eventos globales relacionados con la gestión de identidades y accesos (IAM). + +IBM Cloud genera eventos de auditoría para acciones críticas en la cuenta (creación de usuarios, cambios de políticas, etc.). Para que estos eventos sean registrados y retenidos, se debe provisionar un servicio `activity-tracker`. Además, para capturar eventos que ocurren a nivel de cuenta (globales), la instancia de Activity Tracker debe estar ubicada en una región específica designada por IBM como "región de eventos globales". Sin esta configuración, las acciones de los administradores sobre IAM podrían no quedar registradas, dificultando investigaciones forenses o auditorías de cumplimiento. + +## Lógica de la Regla + +La política se compone de dos reglas que verifican la configuración de Activity Tracker: +1. **Validación Global:** Confirma que al menos una instancia de `activity-tracker` exista en la configuración. +2. **Validación de Ubicación:** Si existen instancias, confirma que su atributo `location` corresponda a una región compatible con eventos globales. Las regiones soportadas son: `eu-de` (Frankfurt), `eu-gb` (Londres), `us-south` (Dallas) y `au-syd` (Sídney). + +## Casos de Fallo Detectados + +A continuación se describen los dos escenarios que esta política detectará. + +--- + +### Caso 1: Instancia de `activity-tracker` Ausente + +* **Descripción:** Esta regla se activa si no existe ningún recurso `ibm_resource_instance` en la configuración de Terraform que tenga el atributo `service` configurado como `"activity-tracker"`. Esto significa que no se está capturando ningún log de auditoría en la cuenta. +* **Ejemplo de Código Terraform Problemático:** + ```terraform + # El proyecto contiene varios recursos, pero ninguno define el servicio 'activity-tracker'. + resource "ibm_is_vpc" "example" { + name = "my-vpc" + } + ``` +* **Ubicación de la Alerta:** La alerta será general y se anclará al bloque `provider "ibm" {}` indicando la ausencia del recurso. + +--- + +### Caso 2: Instancia en una Región de Eventos Globales Incorrecta + +* **Descripción:** Esta regla se activa si el recurso `activity-tracker` tiene su atributo `location` configurado en una región que no está designada por IBM para recibir eventos globales. Esto resultaría en la pérdida de visibilidad sobre los eventos de IAM. +* **Ejemplo de Código Terraform Problemático:** + ```terraform + resource "ibm_resource_instance" "activity_tracker_wrong_region" { + name = "my-activity-tracker" + service = "activity-tracker" + plan = "lite" + location = "us-east" # <-- ¡PROBLEMA! No es una región de eventos globales. + } + ``` +* **Ubicación de la Alerta:** La alerta señalará específicamente al atributo `location` de la instancia de Activity Tracker incorrecta. + +## Recurso Involucrado + +* `ibm_resource_instance` + +## Solución + +Para solucionar los problemas detectados, asegúrate de que exista al menos un recurso `ibm_resource_instance` para el servicio `activity-tracker` y que su atributo `location` esté configurado en una de las regiones de eventos globales soportadas (Frankfurt, Londres, Dallas o Sídney). + +```terraform +resource "ibm_resource_instance" "activity_tracker_correct" { + name = "my-global-activity-tracker" + service = "activity-tracker" + plan = "lite" + + # 'eu-de' es una de las regiones que soportan eventos globales. + location = "eu-de" +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_activity_tracker_global_events_disabled/metadata.json b/assets/queries/terraform/ibm/ibm_activity_tracker_global_events_disabled/metadata.json new file mode 100644 index 00000000000..55ea8278a4b --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_activity_tracker_global_events_disabled/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "479bbdfd-342a-4121-8844-7387eea26d96", + "queryName": "Activity Tracker for Global Events Disabled", + "severity": "MEDIUM", + "category": "Observability", + "descriptionText": "Ensures that an IBM Cloud Activity Tracker instance is provisioned to capture IAM and other global account events. For compliance and security, at least one instance must be configured in a region that supports receiving global events.", + "descriptionUrl": "https://cloud.ibm.com/docs/activity-tracker?topic=activity-tracker-global-events", + "platform": "Terraform", + "descriptionID": "479bbdfd", + "cloudProvider": "ibm", + "cwe": "CWE-778", + "riskScore": 3.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_activity_tracker_global_events_disabled/query.rego b/assets/queries/terraform/ibm/ibm_activity_tracker_global_events_disabled/query.rego new file mode 100644 index 00000000000..2f71acd96dd --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_activity_tracker_global_events_disabled/query.rego @@ -0,0 +1,40 @@ +package Cx + +# REGLA 1: No existe ningún recurso 'ibm_resource_instance' para 'activity-tracker'. +CxPolicy[result] { + doc := input.document[i] + _ := doc.provider.ibm + + all_activity_trackers := [tracker | + tracker := input.document[_].resource.ibm_resource_instance[_] + tracker.service == "activity-tracker" + ] + + count(all_activity_trackers) == 0 + + result := { + "documentId": doc.id, + "searchKey": "provider.ibm", + "issueType": "MissingAttribute", + "keyExpectedValue": "An 'ibm_resource_instance' with service='activity-tracker' should exist", + "keyActualValue": "No 'ibm_resource_instance' for service 'activity-tracker' was found", + } +} + +# REGLA 2: Una instancia de 'activity-tracker' está en una región incorrecta para eventos globales. +CxPolicy[result] { + global_event_regions := {"eu-de", "eu-gb", "us-south", "au-syd"} + + tracker := input.document[i].resource.ibm_resource_instance[tracker_name] + tracker.service == "activity-tracker" + + not global_event_regions[tracker.location] + + result := { + "documentId": input.document[i].id, + "searchKey": sprintf("resource.ibm_resource_instance.%s.location", [tracker_name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "Activity Tracker 'location' should be a global event region (e.g., 'eu-de', 'us-south')", + "keyActualValue": sprintf("Activity Tracker 'location' is '%s', which is not a global event region", [tracker.location]), + } +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_activity_tracker_global_events_disabled/test/negative1.tf b/assets/queries/terraform/ibm/ibm_activity_tracker_global_events_disabled/test/negative1.tf new file mode 100644 index 00000000000..2854dd08df0 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_activity_tracker_global_events_disabled/test/negative1.tf @@ -0,0 +1,10 @@ +provider "ibm" { + region = "us-south" +} + +resource "ibm_resource_instance" "tracker_correct" { + name = "global-tracker" + service = "activity-tracker" + plan = "7-day" + location = "us-south" # CORRECTO: Región de eventos globales (Dallas) +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_activity_tracker_global_events_disabled/test/positive1.tf b/assets/queries/terraform/ibm/ibm_activity_tracker_global_events_disabled/test/positive1.tf new file mode 100644 index 00000000000..92c27402faf --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_activity_tracker_global_events_disabled/test/positive1.tf @@ -0,0 +1,7 @@ +provider "ibm" { + region = "us-south" +} + +resource "ibm_is_vpc" "example_vpc" { + name = "production-vpc" +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_activity_tracker_global_events_disabled/test/positive2.tf b/assets/queries/terraform/ibm/ibm_activity_tracker_global_events_disabled/test/positive2.tf new file mode 100644 index 00000000000..ca284ef7a48 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_activity_tracker_global_events_disabled/test/positive2.tf @@ -0,0 +1,10 @@ +provider "ibm" { + region = "us-south" +} + +resource "ibm_resource_instance" "tracker_wrong_location" { + name = "regional-tracker" + service = "activity-tracker" + plan = "lite" + location = "us-east" # FALLO: No soporta eventos globales +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_activity_tracker_global_events_disabled/test/positive_expected_result.json b/assets/queries/terraform/ibm/ibm_activity_tracker_global_events_disabled/test/positive_expected_result.json new file mode 100644 index 00000000000..04f21d98a44 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_activity_tracker_global_events_disabled/test/positive_expected_result.json @@ -0,0 +1,14 @@ +[ + { + "queryName": "Activity Tracker for Global Events Disabled", + "severity": "MEDIUM", + "line": 1, + "fileName": "positive1.tf" + }, + { + "queryName": "Activity Tracker for Global Events Disabled", + "severity": "MEDIUM", + "line": 9, + "fileName": "positive2.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_activity_tracker_platform_logs_disabled/README.md b/assets/queries/terraform/ibm/ibm_activity_tracker_platform_logs_disabled/README.md new file mode 100644 index 00000000000..8bb0d4d359e --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_activity_tracker_platform_logs_disabled/README.md @@ -0,0 +1,86 @@ +# Regla KICS: Logs de Plataforma Habilitados en Activity Tracker + +## Descripción General + +Esta regla de KICS para Terraform asegura que el servicio IBM Cloud Activity Tracker esté configurado para recibir y registrar los logs de gestión de la plataforma. + +Los logs de plataforma (`platform_logs`) capturan eventos críticos a nivel de cuenta, como la gestión de usuarios y accesos (IAM), la facturación y la modificación de recursos, que son esenciales para una auditoría de seguridad completa y el cumplimiento normativo. Si esta opción no está habilitada, se pierde visibilidad sobre las acciones más importantes que ocurren en la cuenta, dejando puntos ciegos en la monitorización de seguridad. + +## Lógica de la Regla + +La política se divide en tres reglas especializadas para cubrir todos los escenarios en los que los logs de plataforma no están habilitados: +1. **Existencia Global:** Verifica que al menos un Activity Tracker esté provisionado en la cuenta. +2. **Atributo Faltante:** Verifica que el atributo `platform_logs` esté definido (ya que por defecto es `false`). +3. **Valor Incorrecto:** Verifica que el atributo no esté explícitamente desactivado. + +## Casos de Fallo Detectados + +A continuación se describen los tres escenarios que esta política detectará. + +--- + +### Caso 1: Instancia de `activity-tracker` Ausente + +* **Descripción:** Esta regla se activa si no existe ningún recurso `ibm_resource_instance` con `service = "activity-tracker"`. Sin esta instancia, no hay ningún servicio que pueda recopilar los logs de la plataforma. +* **Ejemplo de Código Terraform Problemático:** + ```terraform + # El proyecto de Terraform no define ninguna instancia para el servicio 'activity-tracker'. + resource "ibm_is_vpc" "example" { + name = "my-vpc" + } + ``` +* **Ubicación de la Alerta:** La alerta será general y se anclará al bloque `provider "ibm" {}` para indicar que falta un recurso `activity-tracker` en la configuración global. + +--- + +### Caso 2: Atributo `platform_logs` Ausente + +* **Descripción:** Esta regla detecta una instancia de `activity-tracker` que existe, pero a la que le falta el atributo `platform_logs`. Según la documentación de Terraform para IBM Cloud, el valor por defecto de este atributo es `false`, por lo que su ausencia constituye una configuración insegura. +* **Ejemplo de Código Terraform Problemático:** + ```terraform + resource "ibm_resource_instance" "tracker_missing_attribute" { + name = "my-activity-tracker" + service = "activity-tracker" + plan = "lite" + location = "eu-de" + + # El atributo 'platform_logs' no está presente. + } + ``` +* **Ubicación de la Alerta:** La alerta señalará directamente al bloque del recurso `ibm_resource_instance` al que le falta el atributo. + +--- + +### Caso 3: Atributo `platform_logs` es `false` + +* **Descripción:** Esta regla busca una instancia de `activity-tracker` que tiene el atributo `platform_logs` explícitamente configurado como `false`. +* **Ejemplo de Código Terraform Problemático:** + ```terraform + resource "ibm_resource_instance" "tracker_disabled_explicitly" { + name = "my-activity-tracker" + service = "activity-tracker" + plan = "lite" + location = "eu-de" + + platform_logs = false # <-- ¡PROBLEMA! + } + ``` +* **Ubicación de la Alerta:** La alerta señalará específicamente a la línea `platform_logs = false` dentro del recurso `ibm_resource_instance`. + +## Recurso Involucrado + +* `ibm_resource_instance` + +## Solución + +Para solucionar los problemas detectados, asegúrate de que exista al menos un recurso `ibm_resource_instance` para el servicio `activity-tracker` y que incluya la siguiente línea: + +```terraform +resource "ibm_resource_instance" "activity_tracker_correct" { + name = "my-activity-tracker" + service = "activity-tracker" + plan = "lite" + location = "eu-de" # Región compatible con eventos globales + + platform_logs = true +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_activity_tracker_platform_logs_disabled/metadata.json b/assets/queries/terraform/ibm/ibm_activity_tracker_platform_logs_disabled/metadata.json new file mode 100644 index 00000000000..9f93f69fbc2 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_activity_tracker_platform_logs_disabled/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "ccf65fe5-85f2-414d-b68e-b5101bc4bc03", + "queryName": "Activity Tracker Platform Logs Disabled", + "severity": "HIGH", + "category": "Observability", + "descriptionText": "Ensures that the IBM Cloud Activity Tracker instance is configured to receive platform logs. Capturing platform management events is critical for security auditing, threat detection, and compliance.", + "descriptionUrl": "https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/resource_instance#platform_logs", + "platform": "Terraform", + "descriptionID": "ccf65fe5", + "cloudProvider": "ibm", + "cwe": "CWE-778", + "riskScore": 6.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_activity_tracker_platform_logs_disabled/query.rego b/assets/queries/terraform/ibm/ibm_activity_tracker_platform_logs_disabled/query.rego new file mode 100644 index 00000000000..7fb022a7f88 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_activity_tracker_platform_logs_disabled/query.rego @@ -0,0 +1,54 @@ +package Cx + +# REGLA 1: No existe ningún recurso 'ibm_resource_instance' para el servicio 'activity-tracker'. +CxPolicy[result] { + doc := input.document[i] + _ := doc.provider.ibm + + all_activity_trackers := [tracker | + tracker := input.document[_].resource.ibm_resource_instance[_] + tracker.service == "activity-tracker" + ] + + count(all_activity_trackers) == 0 + + result := { + "documentId": doc.id, + "searchKey": "provider.ibm", + "issueType": "MissingAttribute", + "keyExpectedValue": "An 'ibm_resource_instance' with service='activity-tracker' should exist", + "keyActualValue": "No 'ibm_resource_instance' for service 'activity-tracker' was found", + } +} + +# REGLA 2: El atributo 'platform_logs' está ausente en la instancia de Activity Tracker. +CxPolicy[result] { + tracker := input.document[i].resource.ibm_resource_instance[tracker_name] + tracker.service == "activity-tracker" + + object.get(tracker, "platform_logs", null) == null + + result := { + "documentId": input.document[i].id, + "searchKey": sprintf("resource.ibm_resource_instance.%s", [tracker_name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "'platform_logs' attribute should be present and set to 'true'", + "keyActualValue": "'platform_logs' attribute is missing and defaults to 'false'", + } +} + +# REGLA 3: El atributo 'platform_logs' está explícitamente configurado como 'false'. +CxPolicy[result] { + tracker := input.document[i].resource.ibm_resource_instance[tracker_name] + tracker.service == "activity-tracker" + + tracker.platform_logs == false + + result := { + "documentId": input.document[i].id, + "searchKey": sprintf("resource.ibm_resource_instance.%s.platform_logs", [tracker_name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "'platform_logs' attribute should be 'true'", + "keyActualValue": "'platform_logs' attribute is 'false'", + } +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_activity_tracker_platform_logs_disabled/test/negative1.tf b/assets/queries/terraform/ibm/ibm_activity_tracker_platform_logs_disabled/test/negative1.tf new file mode 100644 index 00000000000..5500054ac55 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_activity_tracker_platform_logs_disabled/test/negative1.tf @@ -0,0 +1,11 @@ +provider "ibm" { + region = "eu-de" +} + +resource "ibm_resource_instance" "tracker_compliant" { + name = "activity-tracker-secure" + service = "activity-tracker" + plan = "7-day" + location = "eu-de" + platform_logs = true +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_activity_tracker_platform_logs_disabled/test/positive1.tf b/assets/queries/terraform/ibm/ibm_activity_tracker_platform_logs_disabled/test/positive1.tf new file mode 100644 index 00000000000..341279abc94 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_activity_tracker_platform_logs_disabled/test/positive1.tf @@ -0,0 +1,8 @@ +provider "ibm" { + region = "eu-de" +} + +# No hay activity-tracker +resource "ibm_is_vpc" "vpc_test" { + name = "test-vpc" +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_activity_tracker_platform_logs_disabled/test/positive2.tf b/assets/queries/terraform/ibm/ibm_activity_tracker_platform_logs_disabled/test/positive2.tf new file mode 100644 index 00000000000..6094a5408a3 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_activity_tracker_platform_logs_disabled/test/positive2.tf @@ -0,0 +1,11 @@ +provider "ibm" { + region = "eu-de" +} + +resource "ibm_resource_instance" "tracker_no_attr" { + name = "activity-tracker-missing" + service = "activity-tracker" + plan = "lite" + location = "eu-de" + # FALLO: Falta platform_logs +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_activity_tracker_platform_logs_disabled/test/positive3.tf b/assets/queries/terraform/ibm/ibm_activity_tracker_platform_logs_disabled/test/positive3.tf new file mode 100644 index 00000000000..a0da5210e4a --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_activity_tracker_platform_logs_disabled/test/positive3.tf @@ -0,0 +1,13 @@ +provider "ibm" { + region = "eu-de" +} + +resource "ibm_resource_instance" "tracker_disabled" { + name = "activity-tracker-off" + service = "activity-tracker" + plan = "lite" + location = "eu-de" + + # FALLO: Deshabilitado explícitamente + platform_logs = false +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_activity_tracker_platform_logs_disabled/test/positive_expected_result.json b/assets/queries/terraform/ibm/ibm_activity_tracker_platform_logs_disabled/test/positive_expected_result.json new file mode 100644 index 00000000000..ebcb1e4e1b0 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_activity_tracker_platform_logs_disabled/test/positive_expected_result.json @@ -0,0 +1,20 @@ +[ + { + "queryName": "Activity Tracker Platform Logs Disabled", + "severity": "HIGH", + "line": 1, + "fileName": "positive1.tf" + }, + { + "queryName": "Activity Tracker Platform Logs Disabled", + "severity": "HIGH", + "line": 5, + "fileName": "positive2.tf" + }, + { + "queryName": "Activity Tracker Platform Logs Disabled", + "severity": "HIGH", + "line": 12, + "fileName": "positive3.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_block_storage_customer_encryption_unified/README.md b/assets/queries/terraform/ibm/ibm_block_storage_customer_encryption_unified/README.md new file mode 100644 index 00000000000..bd44b061a45 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_block_storage_customer_encryption_unified/README.md @@ -0,0 +1,58 @@ +# Regla KICS: IBM Block Storage Encryption (CMK/BYOK/KYOK) Manual + +## Descripción General + +Esta regla unificada (INFO) audita los volúmenes de almacenamiento en bloque (`ibm_is_volume`) en IBM Cloud VPC para asegurar que utilizan estrategias de cifrado gestionadas por el cliente. + +Cubre tres controles de seguridad distintos pero técnicamente equivalentes en la configuración de Terraform: +1. **CMK (Customer Managed Key):** Uso de claves raíz almacenadas en Key Protect estándar. +2. **BYOK (Bring Your Own Key):** Uso de material de clave generado por el cliente e importado a IBM Cloud. +3. **KYOK (Keep Your Own Key):** Uso de Hyper Protect Crypto Services (HSM dedicado con certificación FIPS 140-2 Nivel 4). + +La presencia del argumento `encryption_key` es el requisito técnico indispensable para habilitar cualquiera de estos tres modelos de control de claves sobre el almacenamiento. + +## Lógica de la Regla + +La política realiza las siguientes comprobaciones: +1. Identifica todos los recursos de tipo `ibm_is_volume`. +2. Verifica si el atributo `encryption_key` está ausente en la definición del recurso. +3. Si falta, genera una alerta informativa indicando que el volumen está utilizando el cifrado por defecto (claves gestionadas por IBM), lo cual puede no satisfacer requisitos de cumplimiento de alto nivel. + +## Casos de Fallo Detectados + +A continuación se describe el escenario que esta política detectará. + +--- + +### Caso 1: Cifrado por Defecto (Provider-Managed) + +* **Descripción:** El volumen de bloque ha sido definido sin asignar una clave de cifrado específica. Aunque los datos están cifrados en reposo, las claves son controladas automáticamente por IBM Cloud. +* **Ejemplo de Código Terraform Problemático:** + ```terraform + resource "ibm_is_volume" "example_default_enc" { + name = "my-volume" + profile = "10iops-tier" + zone = "us-south-1" + + # Falta el atributo 'encryption_key' + } + ``` +* **Ubicación de la Alerta:** La alerta señalará directamente al bloque del recurso `ibm_is_volume`. + +## Recurso Involucrado + +* `ibm_is_volume` + +## Solución + +Para cumplir con políticas de CMK, BYOK o KYOK, debes asignar el CRN (Cloud Resource Name) de una clave raíz válida proveniente de Key Protect o Hyper Protect Crypto Services. + +```terraform +resource "ibm_is_volume" "secure_volume" { + name = "data-volume" + profile = "general-purpose" + zone = "us-south-1" + + # Habilitar Cifrado por el Cliente (Cubre CMK, BYOK y KYOK) + encryption_key = "crn:v1:bluemix:public:kms:us-south:a/aaaaaa:bbbbbb:key:cccccc" +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_block_storage_customer_encryption_unified/metadata.json b/assets/queries/terraform/ibm/ibm_block_storage_customer_encryption_unified/metadata.json new file mode 100644 index 00000000000..b6962cab19c --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_block_storage_customer_encryption_unified/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "56cd75da-f919-4350-8b32-1d0e4313e965", + "queryName": "IBM Block Storage Encryption (CMK/BYOK/KYOK) Manual", + "severity": "INFO", + "category": "Encryption", + "descriptionText": "The Block Storage volume uses default provider-managed encryption. It is not configured with 'encryption_key', which is required for Customer Managed Keys (CMK), Bring Your Own Key (BYOK), or Keep Your Own Key (KYOK). Manual verification of the key source is required.", + "descriptionUrl": "https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/is_volume", + "platform": "Terraform", + "descriptionID": "56cd75da", + "cloudProvider": "ibm", + "cwe": "CWE-312", + "riskScore": 0.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_block_storage_customer_encryption_unified/query.rego b/assets/queries/terraform/ibm/ibm_block_storage_customer_encryption_unified/query.rego new file mode 100644 index 00000000000..690198eefdc --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_block_storage_customer_encryption_unified/query.rego @@ -0,0 +1,17 @@ +package Cx + +# REGLA UNIFICADA: Cifrado Gestionado por el Cliente en Block Storage. +CxPolicy[result] { + doc := input.document[i] + volume := doc.resource.ibm_is_volume[name] + + object.get(volume, "encryption_key", "undefined") == "undefined" + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.ibm_is_volume.%s", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "'encryption_key' attribute should be defined (prerequisite for CMK, BYOK, or KYOK)", + "keyActualValue": "'encryption_key' is missing (using default provider-managed encryption)", + } +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_block_storage_customer_encryption_unified/test/negative1.tf b/assets/queries/terraform/ibm/ibm_block_storage_customer_encryption_unified/test/negative1.tf new file mode 100644 index 00000000000..8e5181710a7 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_block_storage_customer_encryption_unified/test/negative1.tf @@ -0,0 +1,12 @@ +provider "ibm" { + region = "us-south" +} + +resource "ibm_is_volume" "volume_secure" { + name = "secure-volume" + profile = "general-purpose" + zone = "us-south-1" + + # CORRECTO: Se define una clave de cifrado + encryption_key = "crn:v1:bluemix:public:kms:us-south:a/test:test:key:test" +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_block_storage_customer_encryption_unified/test/positive1.tf b/assets/queries/terraform/ibm/ibm_block_storage_customer_encryption_unified/test/positive1.tf new file mode 100644 index 00000000000..aa5e37d06c0 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_block_storage_customer_encryption_unified/test/positive1.tf @@ -0,0 +1,10 @@ +provider "ibm" { + region = "us-south" +} + +resource "ibm_is_volume" "volume_insecure" { + name = "insecure-volume" + profile = "general-purpose" + zone = "us-south-1" + # FALLO: Falta el atributo encryption_key +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_block_storage_customer_encryption_unified/test/positive_expected_result.json b/assets/queries/terraform/ibm/ibm_block_storage_customer_encryption_unified/test/positive_expected_result.json new file mode 100644 index 00000000000..a9e0734f996 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_block_storage_customer_encryption_unified/test/positive_expected_result.json @@ -0,0 +1,8 @@ +[ + { + "queryName": "IBM Block Storage Encryption (CMK/BYOK/KYOK) Manual", + "severity": "INFO", + "line": 5, + "fileName": "positive1.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_certificate_manager_auto_renew_disabled/README.md b/assets/queries/terraform/ibm/ibm_certificate_manager_auto_renew_disabled/README.md new file mode 100644 index 00000000000..8c432427f2c --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_certificate_manager_auto_renew_disabled/README.md @@ -0,0 +1,65 @@ +# Regla KICS: Renovación Automática Habilitada para Certificados + +## Descripción General + +Esta regla de KICS para Terraform asegura que todos los certificados gestionados a través de IBM Cloud Certificate Manager (`ibm_cm_certificate`) tengan habilitada la funcionalidad de renovación automática. + +La gestión manual de la caducidad de los certificados TLS es propensa a errores humanos, que pueden resultar en la caducidad de un certificado y causar interrupciones de servicio, errores de confianza y riesgos de seguridad. La renovación automática es una práctica recomendada para garantizar la continuidad del servicio y reducir la carga operativa de mantenimiento de seguridad. + +## Lógica de la Regla + +La política se compone de dos reglas especializadas para cubrir los casos en los que la renovación automática no está habilitada: +1. **Atributo Faltante:** Identifica recursos donde no se define `auto_renew_enabled`, heredando el valor por defecto inseguro (`false`). +2. **Valor Incorrecto:** Identifica recursos donde se ha desactivado explícitamente la renovación automática. + +## Casos de Fallo Detectados + +A continuación se describen los dos escenarios que esta política detectará. + +--- + +### Caso 1: Atributo `auto_renew_enabled` Ausente + +* **Descripción:** Esta regla detecta cualquier recurso `ibm_cm_certificate` donde el atributo `auto_renew_enabled` no está definido. Según la documentación del proveedor de Terraform para IBM, el valor por defecto de este atributo es `false`. +* **Ejemplo de Código Terraform Problemático:** + ```terraform + resource "ibm_cm_certificate" "cert_renew_missing" { + instance_id = ibm_resource_instance.cm_instance.guid + name = "my-cert-renew-missing" + + # El atributo 'auto_renew_enabled' no está presente. + } + ``` +* **Ubicación de la Alerta:** La alerta señalará directamente al bloque de código del recurso `ibm_cm_certificate` al que le falta el atributo. + +--- + +### Caso 2: Atributo `auto_renew_enabled` es `false` + +* **Descripción:** Esta regla busca un recurso `ibm_cm_certificate` que tiene el atributo `auto_renew_enabled` explícitamente configurado como `false`. +* **Ejemplo de Código Terraform Problemático:** + ```terraform + resource "ibm_cm_certificate" "cert_renew_disabled" { + instance_id = ibm_resource_instance.cm_instance.guid + name = "my-cert-renew-disabled" + + auto_renew_enabled = false # <-- ¡PROBLEMA! + } + ``` +* **Ubicación de la Alerta:** La alerta señalará específicamente a la línea `auto_renew_enabled = false` dentro del recurso `ibm_cm_certificate`. + +## Recurso Involucrado + +* `ibm_cm_certificate` + +## Solución + +Para solucionar los problemas detectados por esta regla, asegúrate de que cada recurso `ibm_cm_certificate` incluya el atributo habilitado: + +```terraform +resource "ibm_cm_certificate" "cert_correct" { + instance_id = ibm_resource_instance.cm_instance.guid + name = "my-secure-certificate" + + auto_renew_enabled = true +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_certificate_manager_auto_renew_disabled/metadata.json b/assets/queries/terraform/ibm/ibm_certificate_manager_auto_renew_disabled/metadata.json new file mode 100644 index 00000000000..71ce8c750b8 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_certificate_manager_auto_renew_disabled/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "6797782b-0523-4419-9297-1747e1bf82ab", + "queryName": "Certificate Manager Auto Renew Disabled", + "severity": "MEDIUM", + "category": "Best Practices", + "descriptionText": "Ensures that certificates managed by IBM Cloud Certificate Manager are configured to renew automatically before expiration. Disabling auto-renewal can lead to service disruptions and security risks due to expired certificates.", + "descriptionUrl": "https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/cm_certificate#auto_renew_enabled", + "platform": "Terraform", + "descriptionID": "6797782b", + "cloudProvider": "ibm", + "cwe": "CWE-320", + "riskScore": 3.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_certificate_manager_auto_renew_disabled/query.rego b/assets/queries/terraform/ibm/ibm_certificate_manager_auto_renew_disabled/query.rego new file mode 100644 index 00000000000..9a54f0dae44 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_certificate_manager_auto_renew_disabled/query.rego @@ -0,0 +1,33 @@ +package Cx + +# REGLA 1: El atributo 'auto_renew_enabled' está ausente en el certificado. +CxPolicy[result] { + doc := input.document[i] + certificate := doc.resource.ibm_cm_certificate[cert_name] + + object.get(certificate, "auto_renew_enabled", null) == null + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.ibm_cm_certificate.%s", [cert_name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "'auto_renew_enabled' should be present and set to 'true'", + "keyActualValue": "'auto_renew_enabled' is missing and defaults to 'false'", + } +} + +# REGLA 2: El atributo 'auto_renew_enabled' está explícitamente configurado como 'false'. +CxPolicy[result] { + doc := input.document[i] + certificate := doc.resource.ibm_cm_certificate[cert_name] + + certificate.auto_renew_enabled == false + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.ibm_cm_certificate.%s.auto_renew_enabled", [cert_name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "'auto_renew_enabled' attribute should be 'true'", + "keyActualValue": "'auto_renew_enabled' attribute is 'false'", + } +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_certificate_manager_auto_renew_disabled/test/negative1.tf b/assets/queries/terraform/ibm/ibm_certificate_manager_auto_renew_disabled/test/negative1.tf new file mode 100644 index 00000000000..e44ea543251 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_certificate_manager_auto_renew_disabled/test/negative1.tf @@ -0,0 +1,8 @@ +resource "ibm_cm_certificate" "cert_compliant" { + instance_id = "crn:v1:bluemix:public:cloudcerts:..." + name = "cert-compliant" + label = "secure" + + # CORRECTO + auto_renew_enabled = true +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_certificate_manager_auto_renew_disabled/test/positive1.tf b/assets/queries/terraform/ibm/ibm_certificate_manager_auto_renew_disabled/test/positive1.tf new file mode 100644 index 00000000000..0c8ea62b35c --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_certificate_manager_auto_renew_disabled/test/positive1.tf @@ -0,0 +1,6 @@ +resource "ibm_cm_certificate" "cert_missing_attr" { + instance_id = "crn:v1:bluemix:public:cloudcerts:..." + name = "cert-missing" + label = "test" + # FALLO: Falta auto_renew_enabled +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_certificate_manager_auto_renew_disabled/test/positive2.tf b/assets/queries/terraform/ibm/ibm_certificate_manager_auto_renew_disabled/test/positive2.tf new file mode 100644 index 00000000000..569d61fc484 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_certificate_manager_auto_renew_disabled/test/positive2.tf @@ -0,0 +1,8 @@ +resource "ibm_cm_certificate" "cert_disabled" { + instance_id = "crn:v1:bluemix:public:cloudcerts:..." + name = "cert-disabled" + label = "test" + + # FALLO: Deshabilitado explícitamente + auto_renew_enabled = false +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_certificate_manager_auto_renew_disabled/test/positive_expected_result.json b/assets/queries/terraform/ibm/ibm_certificate_manager_auto_renew_disabled/test/positive_expected_result.json new file mode 100644 index 00000000000..4680efed476 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_certificate_manager_auto_renew_disabled/test/positive_expected_result.json @@ -0,0 +1,14 @@ +[ + { + "queryName": "Certificate Manager Auto Renew Disabled", + "severity": "MEDIUM", + "line": 1, + "fileName": "positive1.tf" + }, + { + "queryName": "Certificate Manager Auto Renew Disabled", + "severity": "MEDIUM", + "line": 7, + "fileName": "positive2.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_cis_dns_not_proxied_manual/README.md b/assets/queries/terraform/ibm/ibm_cis_dns_not_proxied_manual/README.md new file mode 100644 index 00000000000..0aca177091f --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_cis_dns_not_proxied_manual/README.md @@ -0,0 +1,77 @@ +# Regla KICS: IBM CIS DNS Record Not Proxied (Manual) + +## Descripción General + +Esta regla informativa (INFO) audita los registros DNS creados en **IBM Cloud Internet Services (CIS)** mediante el recurso `ibm_cis_dns_record`. + +En la arquitectura de CIS, la **Protección DDoS** de capa 3/4 y capa 7, el WAF y la aceleración CDN solo se activan cuando el tráfico es enrutado a través de la red perimetral de CIS. Esto se configura estableciendo el atributo `proxied` en `true` (modo comúnmente conocido como "Orange Cloud"). + +Si `proxied` es `false` (o se omite), CIS actúa solo como un servidor DNS tradicional ("DNS Only" o "Grey Cloud"), resolviendo la IP del servidor de origen directamente hacia el cliente. Esto expone la IP real del servidor a internet, haciéndolo vulnerable a ataques directos de denegación de servicio (DDoS) y eludiendo cualquier regla de seguridad perimetral de CIS. + +## Lógica de la Regla + +1. Identifica recursos de tipo `ibm_cis_dns_record`. +2. Verifica el valor del atributo `proxied`. +3. Si el valor es explícitamente `false` o si el atributo no ha sido definido (lo que resulta en un valor por defecto de `false`), genera una alerta informativa. + +**Nota para el Auditor:** No todos los registros deben estar protegidos por proxy. Registros TXT, MX, o servicios que utilizan protocolos no soportados por el proxy de CIS (como el tráfico no HTTP en ciertos puertos) deben permanecer en `false`. La alerta sirve para verificar manualmente que los registros `A`, `AAAA` y `CNAME` de aplicaciones web críticas estén bajo el escudo de protección. + +## Casos de Fallo Detectados + +A continuación se describen los escenarios que esta política detectará. + +--- + +### Caso 1: Proxy Desactivado Explícitamente + +* **Descripción:** Se ha configurado `proxied = false`. El tráfico fluye directamente al servidor de origen sin pasar por los filtros de CIS. +* **Ejemplo de Código Terraform Problemático:** + ```terraform + resource "ibm_cis_dns_record" "dns_only" { + cis_id = ibm_cis.instance.id + domain_id = ibm_cis_domain.example.id + name = "web" + type = "A" + content = "1.2.3.4" + proxied = false # <-- Alerta INFO + } + ``` +* **Ubicación de la Alerta:** Atributo `proxied`. + +--- + +### Caso 2: Proxy No Definido (DNS Only por Defecto) + +* **Descripción:** Falta el atributo en la definición. Por defecto, CIS no activa el proxy, dejando el registro sin protección DDoS. +* **Ejemplo de Código Terraform Problemático:** + ```terraform + resource "ibm_cis_dns_record" "default_record" { + cis_id = ibm_cis.instance.id + domain_id = ibm_cis_domain.example.id + name = "app" + type = "A" + content = "1.2.3.4" + # Falta el atributo 'proxied' + } + ``` +* **Ubicación de la Alerta:** Bloque del recurso `ibm_cis_dns_record`. + +## Recurso Involucrado + +* `ibm_cis_dns_record` + +## Solución + +Habilita el proxy para activar la protección DDoS y las características de aceleración de CIS. + +```terraform +resource "ibm_cis_dns_record" "secure_record" { + cis_id = var.cis_instance_id + domain_id = var.domain_id + name = "www" + type = "A" + content = "1.2.3.4" + + # Habilitar protección DDoS, WAF y CDN + proxied = true +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_cis_dns_not_proxied_manual/metadata.json b/assets/queries/terraform/ibm/ibm_cis_dns_not_proxied_manual/metadata.json new file mode 100644 index 00000000000..9227b92c430 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_cis_dns_not_proxied_manual/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "124e18ee-0b42-474d-8623-185f108fa545", + "queryName": "IBM CIS DNS Record Not Proxied (Manual)", + "severity": "INFO", + "category": "Availability", + "descriptionText": "The DNS record in IBM Cloud Internet Services (CIS) is not proxied. DDoS protection, WAF, and CDN features are only active when 'proxied' is set to 'true'. Verify if this record points to a web workload that requires DDoS mitigation.", + "descriptionUrl": "https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/cis_dns_record", + "platform": "Terraform", + "descriptionID": "124e18ee", + "cloudProvider": "ibm", + "cwe": "CWE-770", + "riskScore": 0.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_cis_dns_not_proxied_manual/query.rego b/assets/queries/terraform/ibm/ibm_cis_dns_not_proxied_manual/query.rego new file mode 100644 index 00000000000..86d3a509490 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_cis_dns_not_proxied_manual/query.rego @@ -0,0 +1,33 @@ +package Cx + +# CASO 1: El atributo 'proxied' está explícitamente en false. +CxPolicy[result] { + doc := input.document[i] + record := doc.resource.ibm_cis_dns_record[name] + + record.proxied == false + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.ibm_cis_dns_record.%s.proxied", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "'proxied' should be set to 'true' to enable DDoS protection", + "keyActualValue": "'proxied' is set to 'false'", + } +} + +# CASO 2: El atributo 'proxied' falta (por defecto es false). +CxPolicy[result] { + doc := input.document[i] + record := doc.resource.ibm_cis_dns_record[name] + + object.get(record, "proxied", "undefined") == "undefined" + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.ibm_cis_dns_record.%s", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "'proxied' should be defined and set to 'true' to enable DDoS protection", + "keyActualValue": "'proxied' is missing (DNS resolution only, no DDoS protection)", + } +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_cis_dns_not_proxied_manual/test/negative1.tf b/assets/queries/terraform/ibm/ibm_cis_dns_not_proxied_manual/test/negative1.tf new file mode 100644 index 00000000000..25f0badd996 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_cis_dns_not_proxied_manual/test/negative1.tf @@ -0,0 +1,10 @@ +resource "ibm_cis_dns_record" "dns_record_secure" { + cis_id = "crn:v1:bluemix:public:internet-svcs:..." + domain_id = "example-id" + name = "www" + type = "A" + content = "1.2.3.4" + + # CORRECTO: Bajo el escudo de CIS + proxied = true +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_cis_dns_not_proxied_manual/test/positive1.tf b/assets/queries/terraform/ibm/ibm_cis_dns_not_proxied_manual/test/positive1.tf new file mode 100644 index 00000000000..8509014995b --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_cis_dns_not_proxied_manual/test/positive1.tf @@ -0,0 +1,10 @@ +resource "ibm_cis_dns_record" "dns_record_false" { + cis_id = "crn:v1:bluemix:public:internet-svcs:..." + domain_id = "example-id" + name = "test" + type = "A" + content = "192.168.1.1" + + # FALLO: Deshabilitado explícitamente + proxied = false +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_cis_dns_not_proxied_manual/test/positive2.tf b/assets/queries/terraform/ibm/ibm_cis_dns_not_proxied_manual/test/positive2.tf new file mode 100644 index 00000000000..de686df1b4c --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_cis_dns_not_proxied_manual/test/positive2.tf @@ -0,0 +1,8 @@ +resource "ibm_cis_dns_record" "dns_record_missing" { + cis_id = "crn:v1:bluemix:public:internet-svcs:..." + domain_id = "example-id" + name = "test-missing" + type = "A" + content = "192.168.1.2" + # FALLO: Falta el atributo proxied +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_cis_dns_not_proxied_manual/test/positive_expected_result.json b/assets/queries/terraform/ibm/ibm_cis_dns_not_proxied_manual/test/positive_expected_result.json new file mode 100644 index 00000000000..12c19487774 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_cis_dns_not_proxied_manual/test/positive_expected_result.json @@ -0,0 +1,14 @@ +[ + { + "queryName": "IBM CIS DNS Record Not Proxied (Manual)", + "severity": "INFO", + "line": 9, + "fileName": "positive1.tf" + }, + { + "queryName": "IBM CIS DNS Record Not Proxied (Manual)", + "severity": "INFO", + "line": 1, + "fileName": "positive2.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_cis_waf_enabled_manual/README.md b/assets/queries/terraform/ibm/ibm_cis_waf_enabled_manual/README.md new file mode 100644 index 00000000000..fd9eb45b2fc --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_cis_waf_enabled_manual/README.md @@ -0,0 +1,72 @@ +# Regla KICS: IBM CIS WAF Not Enabled (Manual) + +## Descripción General + +Esta regla informativa (INFO) audita la configuración de **IBM Cloud Internet Services (CIS)** para asegurar que la protección de capa de aplicación esté activa. + +El recurso `ibm_cis_domain_settings` permite gestionar las capacidades de seguridad a nivel de dominio. El atributo `waf` funciona como el interruptor principal del Cortafuegos de Aplicaciones Web. Para mitigar riesgos asociados a vulnerabilidades web comunes y cumplir con estándares de seguridad industrial (como PCI-DSS), este atributo debe estar establecido explícitamente en `on`. + +**Nota importante:** Activar `waf = "on"` habilita el motor de inspección, pero la efectividad real depende de la configuración posterior de los paquetes de reglas (OWASP, reglas específicas de CIS, etc.) mediante el recurso `ibm_cis_waf_package`. Esta alerta sirve como punto de partida para verificar que la base de la protección está presente. + +## Lógica de la Regla + +1. Identifica todos los recursos de tipo `ibm_cis_domain_settings`. +2. Verifica la presencia y el valor del atributo `waf`. +3. Genera una alerta informativa si: + * El atributo `waf` está configurado con un valor distinto a `"on"` (por ejemplo, `"off"`). + * El atributo `waf` no ha sido definido en el recurso. + +## Casos de Fallo Detectados + +A continuación se describen los escenarios que esta política detectará. + +--- + +### Caso 1: WAF Deshabilitado Explícitamente + +* **Descripción:** El recurso define el WAF como `"off"`, desactivando la inspección de tráfico de capa 7. +* **Ejemplo de Código Terraform Problemático:** + ```terraform + resource "ibm_cis_domain_settings" "insecure" { + cis_id = ibm_cis.instance.id + domain_id = ibm_cis_domain.example.id + waf = "off" # <-- Alerta INFO + } + ``` +* **Ubicación de la Alerta:** Atributo `waf`. + +--- + +### Caso 2: Configuración de WAF Ausente + +* **Descripción:** No se especifica el estado del WAF, lo que puede llevar a estados de seguridad inconsistentes dependiendo del plan de CIS contratado. +* **Ejemplo de Código Terraform Problemático:** + ```terraform + resource "ibm_cis_domain_settings" "default_settings" { + cis_id = ibm_cis.instance.id + domain_id = ibm_cis_domain.example.id + # El atributo 'waf' no está definido + } + ``` +* **Ubicación de la Alerta:** Bloque del recurso `ibm_cis_domain_settings`. + +## Recurso Involucrado + +* `ibm_cis_domain_settings` + +## Solución + +Habilita el motor del WAF estableciendo el atributo a `"on"` dentro de la configuración del dominio. + +```terraform +resource "ibm_cis_domain_settings" "secure_settings" { + cis_id = ibm_cis.instance.id + domain_id = ibm_cis_domain.example.id + + # Habilitar WAF Global + waf = "on" + + # Recomendaciones adicionales de seguridad + ssl = "strict" + min_tls_version = "1.2" +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_cis_waf_enabled_manual/metadata.json b/assets/queries/terraform/ibm/ibm_cis_waf_enabled_manual/metadata.json new file mode 100644 index 00000000000..dbcc6548aa0 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_cis_waf_enabled_manual/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "1e0c94f5-6178-4601-b33e-a86d8aa20bd6", + "queryName": "IBM CIS WAF Not Enabled (Manual)", + "severity": "INFO", + "category": "Networking and Firewall", + "descriptionText": "The Web Application Firewall (WAF) is not explicitly set to 'on' in the IBM Cloud Internet Services (CIS) domain settings. WAF is a critical compensatory control for protecting web workloads. Manual verification is required to ensure WAF is enabled and rules are properly tuned.", + "descriptionUrl": "https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/cis_domain_settings", + "platform": "Terraform", + "descriptionID": "1e0c94f5", + "cloudProvider": "ibm", + "cwe": "CWE-1021", + "riskScore": 0.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_cis_waf_enabled_manual/query.rego b/assets/queries/terraform/ibm/ibm_cis_waf_enabled_manual/query.rego new file mode 100644 index 00000000000..4c986b46e3f --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_cis_waf_enabled_manual/query.rego @@ -0,0 +1,34 @@ +package Cx + +# CASO 1: El atributo 'waf' existe pero tiene un valor incorrecto (ej. "off"). +CxPolicy[result] { + doc := input.document[i] + settings := doc.resource.ibm_cis_domain_settings[name] + + settings.waf + settings.waf != "on" + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.ibm_cis_domain_settings.%s.waf", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "'waf' attribute should be set to 'on'", + "keyActualValue": sprintf("'waf' attribute is set to '%s'", [settings.waf]), + } +} + +# CASO 2: El atributo 'waf' NO existe en el recurso (valor por defecto varía por plan, se requiere definición explícita). +CxPolicy[result] { + doc := input.document[i] + settings := doc.resource.ibm_cis_domain_settings[name] + + not settings.waf + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.ibm_cis_domain_settings.%s", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "'waf' attribute should be defined and set to 'on'", + "keyActualValue": "'waf' attribute is missing", + } +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_cis_waf_enabled_manual/test/negative1.tf b/assets/queries/terraform/ibm/ibm_cis_waf_enabled_manual/test/negative1.tf new file mode 100644 index 00000000000..82413ddf80d --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_cis_waf_enabled_manual/test/negative1.tf @@ -0,0 +1,7 @@ +resource "ibm_cis_domain_settings" "settings_secure" { + cis_id = "crn:v1:bluemix:public:internet-svcs:..." + domain_id = "example-id" + + # CORRECTO: WAF habilitado + waf = "on" +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_cis_waf_enabled_manual/test/positive1.tf b/assets/queries/terraform/ibm/ibm_cis_waf_enabled_manual/test/positive1.tf new file mode 100644 index 00000000000..51146b79b6a --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_cis_waf_enabled_manual/test/positive1.tf @@ -0,0 +1,7 @@ +resource "ibm_cis_domain_settings" "settings_off" { + cis_id = "crn:v1:bluemix:public:internet-svcs:..." + domain_id = "example-id" + + # FALLO: WAF desactivado + waf = "off" +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_cis_waf_enabled_manual/test/positive2.tf b/assets/queries/terraform/ibm/ibm_cis_waf_enabled_manual/test/positive2.tf new file mode 100644 index 00000000000..1f424d8f271 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_cis_waf_enabled_manual/test/positive2.tf @@ -0,0 +1,6 @@ +resource "ibm_cis_domain_settings" "settings_missing" { + cis_id = "crn:v1:bluemix:public:internet-svcs:..." + domain_id = "example-id" + # FALLO: Falta el atributo waf + ssl = "full" +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_cis_waf_enabled_manual/test/positive_expected_result.json b/assets/queries/terraform/ibm/ibm_cis_waf_enabled_manual/test/positive_expected_result.json new file mode 100644 index 00000000000..0069e2c83b5 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_cis_waf_enabled_manual/test/positive_expected_result.json @@ -0,0 +1,14 @@ +[ + { + "queryName": "IBM CIS WAF Not Enabled (Manual)", + "severity": "INFO", + "line": 6, + "fileName": "positive1.tf" + }, + { + "queryName": "IBM CIS WAF Not Enabled (Manual)", + "severity": "INFO", + "line": 1, + "fileName": "positive2.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_cloudant_cmk_encryption_manual/README.md b/assets/queries/terraform/ibm/ibm_cloudant_cmk_encryption_manual/README.md new file mode 100644 index 00000000000..c15aa384c73 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_cloudant_cmk_encryption_manual/README.md @@ -0,0 +1,75 @@ +# Regla KICS: IBM Cloudant Encryption with CMK (Manual) + +## Descripción General + +Esta regla informativa (INFO) audita las instancias de **IBM Cloudant** desplegadas mediante el recurso genérico `ibm_resource_instance` para verificar el uso de claves gestionadas por el cliente. + +Cloudant cifra los datos en reposo por defecto utilizando claves gestionadas por IBM. Sin embargo, para cumplir con requisitos de gestión de claves avanzada como **CMK** (Customer Managed Key) o **BYOK** (Bring Your Own Key), se debe inyectar el CRN (Cloud Resource Name) de una clave raíz proveniente de Key Protect o Hyper Protect Crypto Services. + +En Terraform, esta configuración se realiza pasando el parámetro específico `key_protect_key` dentro del mapa de `parameters` del recurso. Si este mapa se omite o si la clave no está presente dentro de él, la instancia se provisionará con el cifrado estándar del proveedor. + +## Lógica de la Regla + +La política realiza las siguientes comprobaciones sobre recursos `ibm_resource_instance` donde el servicio es `"cloudantnosqldb"`: +1. **Ausencia de Parámetros:** Detecta si el bloque `parameters` no ha sido definido en absoluto. +2. **Parámetro Faltante:** Detecta si el bloque `parameters` existe pero no incluye la entrada `key_protect_key`. + +## Casos de Fallo Detectados + +A continuación se describen los escenarios que esta política detectará. + +--- + +### Caso 1: Bloque de Parámetros Ausente + +* **Descripción:** No se define ninguna configuración adicional, por lo que se asume el cifrado por defecto de IBM. +* **Ejemplo de Código Terraform Problemático:** + ```terraform + resource "ibm_resource_instance" "cloudant_no_params" { + name = "my-nosql-db" + service = "cloudantnosqldb" + plan = "standard" + location = "us-south" + # Falta el bloque parameters + } + ``` +* **Ubicación de la Alerta:** Nivel del recurso `ibm_resource_instance`. + +--- + +### Caso 2: Clave de Cifrado Faltante en Parámetros + +* **Descripción:** Se definen parámetros pero se omite la clave de seguridad `key_protect_key`. +* **Ejemplo de Código Terraform Problemático:** + ```terraform + resource "ibm_resource_instance" "cloudant_partial_params" { + name = "my-nosql-db" + service = "cloudantnosqldb" + plan = "standard" + location = "us-south" + parameters = { + "db_type" = "nosql" + } + } + ``` +* **Ubicación de la Alerta:** Atributo `parameters`. + +## Recurso Involucrado + +* `ibm_resource_instance` (Service: `cloudantnosqldb`) + +## Solución + +Añade el bloque `parameters` con la clave `key_protect_key` apuntando a un CRN válido. + +```terraform +resource "ibm_resource_instance" "cloudant_secure" { + name = "my-secure-cloudant" + service = "cloudantnosqldb" + plan = "standard" + location = "us-south" + + parameters = { + key_protect_key = "crn:v1:bluemix:public:kms:us-south:a/aaaa:bbbb:key:cccc" + } +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_cloudant_cmk_encryption_manual/metadata.json b/assets/queries/terraform/ibm/ibm_cloudant_cmk_encryption_manual/metadata.json new file mode 100644 index 00000000000..2e63c668243 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_cloudant_cmk_encryption_manual/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "601ca2bb-20a9-4113-83b3-866a3d1a2a67", + "queryName": "IBM Cloudant Encryption with CMK (Manual)", + "severity": "INFO", + "category": "Encryption", + "descriptionText": "The IBM Cloudant instance (provisioned via 'ibm_resource_instance') does not appear to have Customer Managed Encryption configured. To enable CMK/BYOK, the 'parameters' block must contain the Key Protect root key CRN (usually 'key_protect_key'). Manual verification is required.", + "descriptionUrl": "https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/resource_instance", + "platform": "Terraform", + "descriptionID": "601ca2bb", + "cloudProvider": "ibm", + "cwe": "CWE-312", + "riskScore": 0.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_cloudant_cmk_encryption_manual/query.rego b/assets/queries/terraform/ibm/ibm_cloudant_cmk_encryption_manual/query.rego new file mode 100644 index 00000000000..b4949dff8d0 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_cloudant_cmk_encryption_manual/query.rego @@ -0,0 +1,36 @@ +package Cx + +# CASO 1: El bloque 'parameters' está ausente. +CxPolicy[result] { + doc := input.document[i] + resource := doc.resource.ibm_resource_instance[name] + resource.service == "cloudantnosqldb" + + object.get(resource, "parameters", "undefined") == "undefined" + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.ibm_resource_instance.%s", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "Cloudant instance should have a 'parameters' block containing 'key_protect_key'", + "keyActualValue": "The 'parameters' block is missing (using default encryption)", + } +} + +# CASO 2: El bloque 'parameters' existe pero falta 'key_protect_key'. +CxPolicy[result] { + doc := input.document[i] + resource := doc.resource.ibm_resource_instance[name] + resource.service == "cloudantnosqldb" + + resource.parameters + object.get(resource.parameters, "key_protect_key", "undefined") == "undefined" + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.ibm_resource_instance.%s.parameters", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "Cloudant 'parameters' should contain 'key_protect_key' with a valid Key Protect CRN", + "keyActualValue": "'key_protect_key' is missing within the parameters map", + } +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_cloudant_cmk_encryption_manual/test/negative1.tf b/assets/queries/terraform/ibm/ibm_cloudant_cmk_encryption_manual/test/negative1.tf new file mode 100644 index 00000000000..6f7be642193 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_cloudant_cmk_encryption_manual/test/negative1.tf @@ -0,0 +1,10 @@ +resource "ibm_resource_instance" "cloudant_secure" { + name = "cloudant-secure" + service = "cloudantnosqldb" + plan = "standard" + location = "us-south" + + parameters = { + key_protect_key = "crn:v1:bluemix:public:kms:us-south:a/test:test:key:test" + } +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_cloudant_cmk_encryption_manual/test/positive1.tf b/assets/queries/terraform/ibm/ibm_cloudant_cmk_encryption_manual/test/positive1.tf new file mode 100644 index 00000000000..31bb0b041db --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_cloudant_cmk_encryption_manual/test/positive1.tf @@ -0,0 +1,6 @@ +resource "ibm_resource_instance" "cloudant_no_params" { + name = "cloudant-no-params" + service = "cloudantnosqldb" + plan = "standard" + location = "us-south" +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_cloudant_cmk_encryption_manual/test/positive2.tf b/assets/queries/terraform/ibm/ibm_cloudant_cmk_encryption_manual/test/positive2.tf new file mode 100644 index 00000000000..48239b22f62 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_cloudant_cmk_encryption_manual/test/positive2.tf @@ -0,0 +1,10 @@ +resource "ibm_resource_instance" "cloudant_partial_params" { + name = "cloudant-partial" + service = "cloudantnosqldb" + plan = "standard" + location = "us-south" + + parameters = { + "db_type" = "nosql" + } +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_cloudant_cmk_encryption_manual/test/positive_expected_result.json b/assets/queries/terraform/ibm/ibm_cloudant_cmk_encryption_manual/test/positive_expected_result.json new file mode 100644 index 00000000000..7f0f616c691 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_cloudant_cmk_encryption_manual/test/positive_expected_result.json @@ -0,0 +1,14 @@ +[ + { + "queryName": "IBM Cloudant Encryption with CMK (Manual)", + "severity": "INFO", + "line": 1, + "fileName": "positive1.tf" + }, + { + "queryName": "IBM Cloudant Encryption with CMK (Manual)", + "severity": "INFO", + "line": 7, + "fileName": "positive2.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_container_cluster_entitlement_check/README.md b/assets/queries/terraform/ibm/ibm_container_cluster_entitlement_check/README.md new file mode 100644 index 00000000000..a8aad50d427 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_container_cluster_entitlement_check/README.md @@ -0,0 +1,62 @@ +# Regla KICS: IBM Cluster Entitlement Key Missing (Automated) + +## Descripción General + +Esta regla informativa (INFO) audita el recurso `ibm_container_cluster` en IBM Cloud Kubernetes Service (IKS). + +En IBM Cloud, la gestión de **Image Pull Secrets** (credenciales para descargar imágenes de contenedores) se puede automatizar en Terraform de varias formas. Una de las más directas y seguras para el software empresarial es el uso del argumento `entitlement`. + +Cuando se define este argumento, el sistema crea automáticamente un secreto de Kubernetes dentro del clúster con las credenciales necesarias para autenticarse contra el **IBM Entitled Registry**. Esto asegura la integridad de la cadena de suministro al permitir el despliegue de **IBM Cloud Paks** y otro software licenciado sin la necesidad de crear, inyectar o gestionar manualmente objetos `Secret` de Kubernetes, reduciendo el riesgo de exposición de credenciales y fallos en el despliegue por secretos expirados. + +## Lógica de la Regla + +1. Identifica recursos de tipo `ibm_container_cluster`. +2. Verifica si el atributo `entitlement` está presente en la configuración. +3. Si falta, genera una alerta informativa. + +**Nota técnica:** No todos los clústeres requieren software titulado (muchos corren aplicaciones propias). Sin embargo, en entornos corporativos de IBM, es una mejor práctica verificar si se ha habilitado esta automatización. Para registros privados que no sean el Entitled Registry, se recomienda usar el recurso `ibm_container_bind_service`. + +## Casos de Fallo Detectados + +A continuación se describe el escenario que esta política detectará. + +--- + +### Caso 1: Clave de Titulación (Entitlement) Faltante + +* **Descripción:** El clúster se provisiona sin credenciales automáticas para el registro de IBM. Esto requerirá que el administrador gestione los secretos de descarga de imágenes de forma manual. +* **Ejemplo de Código Terraform Problemático:** + ```terraform + resource "ibm_container_cluster" "example_no_entitlement" { + name = "my-app-cluster" + datacenter = "dal10" + machine_type = "b3c.4x16" + hardware = "shared" + public_vlan_id = "1234567" + private_vlan_id = "7654321" + + # Falta el atributo 'entitlement' + } + ``` +* **Ubicación de la Alerta:** Bloque del recurso `ibm_container_cluster`. + +## Recurso Involucrado + +* `ibm_container_cluster` + +## Solución + +Si el clúster va a ejecutar software licenciado de IBM, añade tu clave de titulación al recurso del clúster. + +```terraform +resource "ibm_container_cluster" "secure_cluster" { + name = "production-cluster" + datacenter = "dal10" + machine_type = "u3c.2x4" + hardware = "shared" + public_vlan_id = "123456" + private_vlan_id = "654321" + + # Automatización del Image Pull Secret para IBM Entitled Registry + entitlement = "cloud_pak_entitlement_key_or_api_key" +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_container_cluster_entitlement_check/metadata.json b/assets/queries/terraform/ibm/ibm_container_cluster_entitlement_check/metadata.json new file mode 100644 index 00000000000..36922ba0ea7 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_container_cluster_entitlement_check/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "d2fc37af-3cd9-4830-b8a0-a3eda8fba0ed", + "queryName": "IBM Cluster Entitlement Key Missing (Automated)", + "severity": "INFO", + "category": "Supply-Chain", + "descriptionText": "The IBM Cloud Kubernetes Service cluster does not have an 'entitlement' key configured. This argument automates the creation of image pull secrets for the IBM Entitled Registry. If using IBM Cloud Paks or entitled software, this should be defined to ensure secure image pull access.", + "descriptionUrl": "https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/container_cluster#entitlement", + "platform": "Terraform", + "descriptionID": "d2fc37af", + "cloudProvider": "ibm", + "cwe": "CWE-284", + "riskScore": 0.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_container_cluster_entitlement_check/query.rego b/assets/queries/terraform/ibm/ibm_container_cluster_entitlement_check/query.rego new file mode 100644 index 00000000000..7e661363b45 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_container_cluster_entitlement_check/query.rego @@ -0,0 +1,17 @@ +package Cx + +# REGLA: Verificar si el cluster tiene configurada la clave de "entitlement". +CxPolicy[result] { + doc := input.document[i] + cluster := doc.resource.ibm_container_cluster[name] + + object.get(cluster, "entitlement", "undefined") == "undefined" + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.ibm_container_cluster.%s", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "'entitlement' attribute should be defined for clusters running IBM Entitled Software", + "keyActualValue": "'entitlement' attribute is missing", + } +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_container_cluster_entitlement_check/test/negative1.tf b/assets/queries/terraform/ibm/ibm_container_cluster_entitlement_check/test/negative1.tf new file mode 100644 index 00000000000..0d9ea9b13b7 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_container_cluster_entitlement_check/test/negative1.tf @@ -0,0 +1,15 @@ +provider "ibm" { + region = "us-south" +} + +resource "ibm_container_cluster" "cluster_with_entitlement" { + name = "secure-cluster" + datacenter = "dal10" + machine_type = "b3c.4x16" + hardware = "shared" + public_vlan_id = "123" + private_vlan_id = "456" + + # CORRECTO: Se automatiza el secreto de descarga + entitlement = "my-entitlement-key-crn" +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_container_cluster_entitlement_check/test/positive1.tf b/assets/queries/terraform/ibm/ibm_container_cluster_entitlement_check/test/positive1.tf new file mode 100644 index 00000000000..cf80ba4a204 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_container_cluster_entitlement_check/test/positive1.tf @@ -0,0 +1,13 @@ +provider "ibm" { + region = "us-south" +} + +resource "ibm_container_cluster" "cluster_without_entitlement" { + name = "test-cluster" + datacenter = "dal10" + machine_type = "b3c.4x16" + hardware = "shared" + public_vlan_id = "123" + private_vlan_id = "456" + # FALLO: No se define 'entitlement' +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_container_cluster_entitlement_check/test/positive_expected_result.json b/assets/queries/terraform/ibm/ibm_container_cluster_entitlement_check/test/positive_expected_result.json new file mode 100644 index 00000000000..b2aa6c25605 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_container_cluster_entitlement_check/test/positive_expected_result.json @@ -0,0 +1,8 @@ +[ + { + "queryName": "IBM Cluster Entitlement Key Missing (Automated)", + "severity": "INFO", + "line": 5, + "fileName": "positive1.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_container_registry_va_alerts_missing/README.md b/assets/queries/terraform/ibm/ibm_container_registry_va_alerts_missing/README.md new file mode 100644 index 00000000000..4472f3e9caf --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_container_registry_va_alerts_missing/README.md @@ -0,0 +1,54 @@ +# Regla KICS: IBM Cluster Missing VA Alerts (Automated) + +## Descripción General + +Esta regla de severidad **MEDIA** audita el despliegue de clústeres de Kubernetes (`ibm_container_cluster`) en IBM Cloud para asegurar que existan canales de comunicación activos para la gestión de vulnerabilidades. + +El uso de orquestadores de contenedores implica el manejo constante de imágenes de software. IBM Cloud proporciona el **Vulnerability Advisor (VA)**, que escanea automáticamente estas imágenes en el registro. Sin embargo, en un flujo de trabajo de DevSecOps eficiente, el escaneo por sí solo no es suficiente; es imperativo que el sistema pueda notificar a los responsables cuando se detecten nuevas vulnerabilidades o cambios en el estado de seguridad de una imagen. + +Si estás desplegando un clúster mediante Terraform, la práctica recomendada es incluir en el mismo código la configuración de las notificaciones (`ibm_container_va_notification`), ya sea mediante correo electrónico o webhooks, para garantizar una respuesta rápida ante posibles amenazas. + +## Lógica de la Regla + +1. Identifica todos los recursos de tipo `ibm_container_cluster` en la configuración. +2. Escanea el documento de Terraform en busca de *cualquier* instancia del recurso `ibm_container_va_notification`. +3. Si se detecta la creación de un clúster pero no existe ninguna configuración de notificaciones de VA, genera una alerta apuntando al recurso del clúster. + +## Casos de Fallo Detectados + +A continuación se describe el escenario que esta política detectará. + +--- + +### Caso 1: Clúster sin Alertas de Vulnerabilidad + +* **Descripción:** Se define un clúster de Kubernetes en la infraestructura, pero se omite la configuración del canal de alertas (email/webhook) para el Vulnerability Advisor. Esto crea un punto ciego donde las imágenes vulnerables podrían pasar desapercibidas. +* **Ejemplo de Código Terraform Problemático:** + ```terraform + resource "ibm_container_cluster" "production_cluster" { + name = "prod-cluster" + datacenter = "dal10" + machine_type = "b3c.4x16" + hardware = "shared" + public_vlan_id = "123456" + private_vlan_id = "654321" + + # No hay un recurso 'ibm_container_va_notification' asociado en el proyecto + } + ``` +* **Ubicación de la Alerta:** Bloque del recurso `ibm_container_cluster`. + +## Recurso Involucrado + +* `ibm_container_cluster` +* `ibm_container_va_notification` + +## Solución + +Para solucionar esta alerta, añade un recurso de notificación de VA que apunte al clúster o al grupo de recursos correspondiente. + +```terraform +resource "ibm_container_va_notification" "vulnerability_alerts" { + cluster_id = ibm_container_cluster.production_cluster.id + email = "security-ops@tu-empresa.com" +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_container_registry_va_alerts_missing/metadata.json b/assets/queries/terraform/ibm/ibm_container_registry_va_alerts_missing/metadata.json new file mode 100644 index 00000000000..6d1b5a772d7 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_container_registry_va_alerts_missing/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "92440d0d-889c-49c5-a0e6-13c5f326b872", + "queryName": "IBM Container Registry VA Alerts Missing (Automated)", + "severity": "MEDIUM", + "category": "Observability", + "descriptionText": "Ensures that notifications are enabled for the Vulnerability Advisor (VA) in IBM Cloud Container Registry. The resource 'ibm_container_va_notification' allows configuring alerts (email, webhook) for new vulnerabilities found in container images.", + "descriptionUrl": "https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/container_va_notification", + "platform": "Terraform", + "descriptionID": "92440d0d", + "cloudProvider": "ibm", + "cwe": "CWE-778", + "riskScore": 3.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_container_registry_va_alerts_missing/query.rego b/assets/queries/terraform/ibm/ibm_container_registry_va_alerts_missing/query.rego new file mode 100644 index 00000000000..6137eaab409 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_container_registry_va_alerts_missing/query.rego @@ -0,0 +1,19 @@ +package Cx + +# REGLA: Verificar si el cluster de Kubernetes tiene configuradas las alertas de vulnerabilidad. +CxPolicy[result] { + doc := input.document[i] + cluster := doc.resource.ibm_container_cluster[name] + + va_notifications := [n | n := input.document[_].resource.ibm_container_va_notification[_]] + + count(va_notifications) == 0 + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.ibm_container_cluster.%s", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "A 'ibm_container_va_notification' resource should be defined to alert on image vulnerabilities", + "keyActualValue": "Vulnerability Advisor notifications are missing for this cluster environment", + } +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_container_registry_va_alerts_missing/test/negative1.tf b/assets/queries/terraform/ibm/ibm_container_registry_va_alerts_missing/test/negative1.tf new file mode 100644 index 00000000000..dc8b4fbc202 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_container_registry_va_alerts_missing/test/negative1.tf @@ -0,0 +1,18 @@ +provider "ibm" { + region = "us-south" +} + +resource "ibm_container_cluster" "secure_cluster" { + name = "secure-setup" + datacenter = "dal10" + machine_type = "b3c.4x16" + hardware = "shared" + public_vlan_id = "123" + private_vlan_id = "456" +} + +# CORRECTO: Se define la notificación para el VA +resource "ibm_container_va_notification" "alerts" { + cluster_id = ibm_container_cluster.secure_cluster.id + email = "admin@example.com" +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_container_registry_va_alerts_missing/test/positive1.tf b/assets/queries/terraform/ibm/ibm_container_registry_va_alerts_missing/test/positive1.tf new file mode 100644 index 00000000000..5c53735bfa1 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_container_registry_va_alerts_missing/test/positive1.tf @@ -0,0 +1,13 @@ +provider "ibm" { + region = "us-south" +} + +resource "ibm_container_cluster" "cluster_without_va" { + name = "vulnerable-setup" + datacenter = "dal10" + machine_type = "b3c.4x16" + hardware = "shared" + public_vlan_id = "123" + private_vlan_id = "456" + # FALLO: No existe un ibm_container_va_notification en el documento +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_container_registry_va_alerts_missing/test/positive_expected_result.json b/assets/queries/terraform/ibm/ibm_container_registry_va_alerts_missing/test/positive_expected_result.json new file mode 100644 index 00000000000..f6feb25008e --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_container_registry_va_alerts_missing/test/positive_expected_result.json @@ -0,0 +1,8 @@ +[ + { + "queryName": "IBM Container Registry VA Alerts Missing (Automated)", + "severity": "MEDIUM", + "line": 5, + "fileName": "positive1.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_cos_bucket_customer_encryption_unified/README.md b/assets/queries/terraform/ibm/ibm_cos_bucket_customer_encryption_unified/README.md new file mode 100644 index 00000000000..1635ba2a4fd --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_cos_bucket_customer_encryption_unified/README.md @@ -0,0 +1,60 @@ +# Regla KICS: IBM COS Bucket Encryption (CMK/BYOK/KYOK) Manual + +## Descripción General + +Esta regla unificada (INFO) verifica si los buckets de **IBM Cloud Object Storage (COS)** están configurados para utilizar claves de cifrado gestionadas por el cliente, en lugar de las claves estándar gestionadas por el proveedor. + +En la arquitectura de IBM Cloud, el uso del argumento `key_protect` en el recurso de Terraform es el habilitador técnico para implementar tres niveles avanzados de seguridad: + +1. **CMK (Customer Managed Key):** Utiliza claves raíz almacenadas en el servicio **Key Protect**. +2. **BYOK (Bring Your Own Key):** Permite al cliente importar su propio material de clave generado externamente. +3. **KYOK (Keep Your Own Key):** Ofrece el nivel más alto de aislamiento mediante el uso de **Hyper Protect Crypto Services (HPCS)** con un HSM dedicado con certificación FIPS 140-2 Nivel 4. + +Si este argumento se omite, el bucket utiliza el cifrado en reposo por defecto de IBM (AES-256), donde IBM controla la rotación y el ciclo de vida de la clave. Esta regla sirve para que el auditor verifique si la sensibilidad de los datos almacenados exige un control soberano de las llaves. + +## Lógica de la Regla + +1. Identifica todos los recursos de tipo `ibm_cos_bucket`. +2. Verifica si el atributo `key_protect` está presente en la definición del bucket. +3. Genera una alerta informativa si el atributo no existe, indicando que se está utilizando la configuración de cifrado por defecto. + +## Casos de Fallo Detectados + +A continuación se describe el escenario que esta política detectará. + +--- + +### Caso 1: Cifrado Gestionado por el Proveedor (Default) + +* **Descripción:** El bucket de COS ha sido configurado sin especificar una clave de seguridad del cliente. IBM Cloud cifra los datos, pero el cliente no posee el control total sobre la clave raíz. +* **Ejemplo de Código Terraform Problemático:** + ```terraform + resource "ibm_cos_bucket" "public_data" { + bucket_name = "my-bucket-standard" + resource_instance_id = ibm_resource_instance.cos_instance.id + storage_class = "smart" + region_location = "us-south" + + # Falta el atributo 'key_protect' + } + ``` +* **Ubicación de la Alerta:** Bloque del recurso `ibm_cos_bucket`. + +## Recurso Involucrado + +* `ibm_cos_bucket` + +## Solución + +Para habilitar CMK, BYOK o KYOK, debes proporcionar el CRN (Cloud Resource Name) de tu clave raíz de Key Protect o Hyper Protect en el atributo `key_protect`. + +```terraform +resource "ibm_cos_bucket" "secure_storage" { + bucket_name = "vault-bucket-secure" + resource_instance_id = ibm_resource_instance.cos_instance.id + storage_class = "smart" + region_location = "us-south" + + # Habilitar Cifrado Gestionado por el Cliente + key_protect = "crn:v1:bluemix:public:kms:us-south:a/aaaa:bbbb:key:cccc" +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_cos_bucket_customer_encryption_unified/metadata.json b/assets/queries/terraform/ibm/ibm_cos_bucket_customer_encryption_unified/metadata.json new file mode 100644 index 00000000000..663d9c51106 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_cos_bucket_customer_encryption_unified/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "8f65bf01-e9e9-4dd6-82b1-67051c2f073d", + "queryName": "IBM COS Bucket Encryption (CMK/BYOK/KYOK) Manual", + "severity": "INFO", + "category": "Encryption", + "descriptionText": "The Cloud Object Storage bucket relies on default provider-managed encryption. It is not configured with 'key_protect', which is required for Customer Managed Keys (CMK), Bring Your Own Key (BYOK), or Keep Your Own Key (KYOK). Manual verification is required to determine the specific encryption requirement.", + "descriptionUrl": "https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/cos_bucket", + "platform": "Terraform", + "descriptionID": "8f65bf01", + "cloudProvider": "ibm", + "cwe": "CWE-312", + "riskScore": 0.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_cos_bucket_customer_encryption_unified/query.rego b/assets/queries/terraform/ibm/ibm_cos_bucket_customer_encryption_unified/query.rego new file mode 100644 index 00000000000..04a56602fc0 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_cos_bucket_customer_encryption_unified/query.rego @@ -0,0 +1,17 @@ +package Cx + +# REGLA UNIFICADA: Verificación de Cifrado Gestionado por el Cliente en COS. +CxPolicy[result] { + doc := input.document[i] + bucket := doc.resource.ibm_cos_bucket[name] + + object.get(bucket, "key_protect", "undefined") == "undefined" + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.ibm_cos_bucket.%s", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "'key_protect' attribute should be defined (required for CMK, BYOK, or KYOK)", + "keyActualValue": "'key_protect' is missing (using default provider-managed encryption)", + } +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_cos_bucket_customer_encryption_unified/test/negative1.tf b/assets/queries/terraform/ibm/ibm_cos_bucket_customer_encryption_unified/test/negative1.tf new file mode 100644 index 00000000000..a096fdcc81f --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_cos_bucket_customer_encryption_unified/test/negative1.tf @@ -0,0 +1,13 @@ +provider "ibm" { + region = "us-south" +} + +resource "ibm_cos_bucket" "bucket_secure" { + bucket_name = "secure-bucket" + resource_instance_id = "crn:v1:bluemix:public:cloud-object-storage:..." + storage_class = "standard" + region_location = "us-south" + + # CORRECTO: Se define la clave de cifrado del cliente + key_protect = "crn:v1:bluemix:public:kms:us-south:a/test:test:key:test" +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_cos_bucket_customer_encryption_unified/test/positive1.tf b/assets/queries/terraform/ibm/ibm_cos_bucket_customer_encryption_unified/test/positive1.tf new file mode 100644 index 00000000000..c71b8b828c4 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_cos_bucket_customer_encryption_unified/test/positive1.tf @@ -0,0 +1,11 @@ +provider "ibm" { + region = "us-south" +} + +resource "ibm_cos_bucket" "bucket_insecure" { + bucket_name = "insecure-bucket" + resource_instance_id = "crn:v1:bluemix:public:cloud-object-storage:..." + storage_class = "standard" + region_location = "us-south" + # FALLO: Falta el atributo key_protect +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_cos_bucket_customer_encryption_unified/test/positive_expected_result.json b/assets/queries/terraform/ibm/ibm_cos_bucket_customer_encryption_unified/test/positive_expected_result.json new file mode 100644 index 00000000000..1a8da52305e --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_cos_bucket_customer_encryption_unified/test/positive_expected_result.json @@ -0,0 +1,8 @@ +[ + { + "queryName": "IBM COS Bucket Encryption (CMK/BYOK/KYOK) Manual", + "severity": "INFO", + "line": 5, + "fileName": "positive1.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_database_cmk_encryption_manual/README.md b/assets/queries/terraform/ibm/ibm_database_cmk_encryption_manual/README.md new file mode 100644 index 00000000000..d2ed09aeeff --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_database_cmk_encryption_manual/README.md @@ -0,0 +1,57 @@ +# Regla KICS: IBM Cloud Database Without CMK (Manual) + +## Descripción General + +Esta regla informativa (INFO) audita los recursos `ibm_database` en Terraform para identificar instancias de la familia **IBM Cloud Databases (ICD)** que no utilizan claves de cifrado gestionadas por el cliente. + +Este recurso se utiliza para aprovisionar una amplia variedad de servicios de datos gestionados, incluyendo PostgreSQL, Redis, Elasticsearch, MongoDB, etcd, entre otros. Por defecto, todas estas bases de datos cifran el almacenamiento en reposo utilizando claves gestionadas automáticamente por IBM Cloud. + +Sin embargo, para cumplir con marcos regulatorios estrictos o políticas internas de seguridad avanzadas (**CMK**, **BYOK** o **KYOK**), es imperativo que el cliente proporcione y gestione la clave raíz. Esto se logra asignando el CRN (Cloud Resource Name) de una clave proveniente de **Key Protect** o **Hyper Protect Crypto Services (HPCS)** en el argumento `key_protect_key`. + +## Lógica de la Regla + +La política realiza las siguientes acciones: +1. Identifica todos los recursos de tipo `ibm_database` en el código de Terraform. +2. Verifica si el atributo `key_protect_key` ha sido definido. +3. Si el atributo falta, genera una alerta informativa indicando que la instancia depende del cifrado por defecto del proveedor. + +## Casos de Fallo Detectados + +A continuación se describe el escenario que esta política detectará. + +--- + +### Caso 1: Cifrado por Defecto (Provider-Managed) + +* **Descripción:** La base de datos ICD ha sido configurada sin asignar una clave de seguridad propia. Aunque los datos están cifrados, el cliente no tiene control sobre el ciclo de vida de la clave de cifrado de volumen. +* **Ejemplo de Código Terraform Problemático:** + ```terraform + resource "ibm_database" "postgres_default" { + name = "my-db-standard" + service = "databases-for-postgresql" + plan = "standard" + location = "us-south" + + # Falta el atributo 'key_protect_key' + } + ``` +* **Ubicación de la Alerta:** Bloque del recurso `ibm_database`. + +## Recurso Involucrado + +* `ibm_database` + +## Solución + +Para habilitar el cifrado gestionado por el cliente, asigna el CRN de una clave raíz válida de tu Vault (Key Protect o HPCS) al argumento `key_protect_key`. + +```terraform +resource "ibm_database" "secure_db" { + name = "my-secure-postgres" + service = "databases-for-postgresql" + plan = "standard" + location = "us-south" + + # Habilitar CMK/BYOK/KYOK + key_protect_key = "crn:v1:bluemix:public:kms:us-south:a/aaaa:bbbb:key:cccc" +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_database_cmk_encryption_manual/metadata.json b/assets/queries/terraform/ibm/ibm_database_cmk_encryption_manual/metadata.json new file mode 100644 index 00000000000..78ee536c83a --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_database_cmk_encryption_manual/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "c973475c-4797-42b1-8cee-4038c050283e", + "queryName": "IBM Cloud Database Without CMK (Manual)", + "severity": "INFO", + "category": "Encryption", + "descriptionText": "The IBM Cloud Database instance is not configured with a Customer Managed Key (CMK). It relies on default provider-managed encryption. Verify if the data sensitivity requires Bring Your Own Key (BYOK) or Keep Your Own Key (KYOK) via the 'key_protect_key' argument.", + "descriptionUrl": "https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/database", + "platform": "Terraform", + "descriptionID": "c973475c", + "cloudProvider": "ibm", + "cwe": "CWE-312", + "riskScore": 0.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_database_cmk_encryption_manual/query.rego b/assets/queries/terraform/ibm/ibm_database_cmk_encryption_manual/query.rego new file mode 100644 index 00000000000..7b0a644b906 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_database_cmk_encryption_manual/query.rego @@ -0,0 +1,17 @@ +package Cx + +# CASO 1: Base de datos sin 'key_protect_key'. +CxPolicy[result] { + doc := input.document[i] + db := doc.resource.ibm_database[name] + + object.get(db, "key_protect_key", "undefined") == "undefined" + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.ibm_database.%s", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "'key_protect_key' attribute should be defined with a Key Protect/HPCS CRN", + "keyActualValue": "'key_protect_key' is missing (using default provider-managed encryption)", + } +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_database_cmk_encryption_manual/test/negative1.tf b/assets/queries/terraform/ibm/ibm_database_cmk_encryption_manual/test/negative1.tf new file mode 100644 index 00000000000..e180275711d --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_database_cmk_encryption_manual/test/negative1.tf @@ -0,0 +1,13 @@ +provider "ibm" { + region = "us-south" +} + +resource "ibm_database" "db_secure" { + name = "db-protected" + service = "databases-for-postgresql" + plan = "standard" + location = "us-south" + + # CORRECTO: Se define la clave de cifrado del cliente + key_protect_key = "crn:v1:bluemix:public:kms:us-south:a/test:test:key:test" +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_database_cmk_encryption_manual/test/positive1.tf b/assets/queries/terraform/ibm/ibm_database_cmk_encryption_manual/test/positive1.tf new file mode 100644 index 00000000000..0263c170bcf --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_database_cmk_encryption_manual/test/positive1.tf @@ -0,0 +1,11 @@ +provider "ibm" { + region = "us-south" +} + +resource "ibm_database" "db_insecure" { + name = "db-standard" + service = "databases-for-mongodb" + plan = "standard" + location = "us-south" + # FALLO: Falta el atributo key_protect_key +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_database_cmk_encryption_manual/test/positive_expected_result.json b/assets/queries/terraform/ibm/ibm_database_cmk_encryption_manual/test/positive_expected_result.json new file mode 100644 index 00000000000..9385193bf19 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_database_cmk_encryption_manual/test/positive_expected_result.json @@ -0,0 +1,8 @@ +[ + { + "queryName": "IBM Cloud Database Without CMK (Manual)", + "severity": "INFO", + "line": 5, + "fileName": "positive1.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_iam_account_ip_restrictions_manual/README.md b/assets/queries/terraform/ibm/ibm_iam_account_ip_restrictions_manual/README.md new file mode 100644 index 00000000000..2aa835fe523 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_iam_account_ip_restrictions_manual/README.md @@ -0,0 +1,69 @@ +# Regla KICS: IBM Account IP Restrictions (Manual) + +## Descripción General + +Esta regla informativa (INFO) audita la configuración global de la cuenta en IBM Cloud a través del recurso `ibm_iam_account_settings`. + +Para mitigar el riesgo de compromiso de credenciales y accesos no autorizados, es una práctica de seguridad fundamental restringir el acceso a la consola de IBM Cloud y a las interfaces de API únicamente a direcciones IP o rangos de red de confianza (como la VPN corporativa, oficinas centrales o gateways de seguridad). + +Si el atributo `allowed_ip_addresses` no se configura, IBM Cloud permite por defecto el acceso desde cualquier dirección IP pública (0.0.0.0/0). Habilitar estas restricciones reduce drásticamente la superficie de ataque para amenazas externas, ya que incluso si un atacante obtiene credenciales válidas, el sistema denegará la conexión si no se origina desde un origen autorizado. + +## Lógica de la Regla + +La política realiza las siguientes validaciones: +1. Identifica el recurso único `ibm_iam_account_settings` en el proyecto. +2. **Validación de Presencia:** Verifica si el atributo `allowed_ip_addresses` ha sido definido. +3. **Validación de Integridad:** Verifica que la lista proporcionada no sea una lista vacía `[]`. +4. Genera una alerta informativa si falta la configuración, instando al auditor a definir el perímetro de red de la cuenta. + +## Casos de Fallo Detectados + +A continuación se describen los escenarios que esta política detectará. + +--- + +### Caso 1: Acceso Irrestricto (Sin Atributo) + +* **Descripción:** El recurso no define ninguna restricción de red. El acceso a la cuenta está abierto a cualquier punto de internet. +* **Ejemplo de Código Terraform Problemático:** + ```terraform + resource "ibm_iam_account_settings" "default" { + mfa = "TOTP" + session_expiration_in_seconds = 3600 + # Falta el atributo 'allowed_ip_addresses' + } + ``` +* **Ubicación de la Alerta:** Bloque del recurso `ibm_iam_account_settings`. + +--- + +### Caso 2: Lista de IPs Vacía + +* **Descripción:** Se define el atributo pero no se incluyen direcciones IP de confianza. +* **Ejemplo de Código Terraform Problemático:** + ```terraform + resource "ibm_iam_account_settings" "empty_list" { + allowed_ip_addresses = [] # <-- Alerta INFO + } + ``` +* **Ubicación de la Alerta:** Atributo `allowed_ip_addresses`. + +## Recurso Involucrado + +* `ibm_iam_account_settings` + +## Solución + +Define explícitamente la lista de direcciones IP o subredes (en formato CIDR) que deben tener permiso de acceso a la cuenta. + +```terraform +resource "ibm_iam_account_settings" "secure_account" { + mfa = "TOTP" + session_expiration_in_seconds = 86400 + + # Restringir acceso solo a la VPN corporativa y oficina central + allowed_ip_addresses = [ + "203.0.113.10", # IP Estática Oficina + "192.0.2.0/24" # Rango VPN Corporativa + ] +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_iam_account_ip_restrictions_manual/metadata.json b/assets/queries/terraform/ibm/ibm_iam_account_ip_restrictions_manual/metadata.json new file mode 100644 index 00000000000..c220420064a --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_iam_account_ip_restrictions_manual/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "61d76d70-0021-45c7-855d-c8c5c3116877", + "queryName": "IBM Account IP Restrictions (Manual)", + "severity": "INFO", + "category": "Networking and Firewall", + "descriptionText": "The account global settings do not enforce IP address restrictions. The 'allowed_ip_addresses' attribute should be configured to restrict login access (including the account owner) to trusted networks only.", + "descriptionUrl": "https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/iam_account_settings", + "platform": "Terraform", + "descriptionID": "61d76d70", + "cloudProvider": "ibm", + "cwe": "CWE-284", + "riskScore": 0.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_iam_account_ip_restrictions_manual/query.rego b/assets/queries/terraform/ibm/ibm_iam_account_ip_restrictions_manual/query.rego new file mode 100644 index 00000000000..7be385c5a10 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_iam_account_ip_restrictions_manual/query.rego @@ -0,0 +1,34 @@ +package Cx + +# CASO 1: Restricciones de IP no configuradas (Atributo ausente). +CxPolicy[result] { + doc := input.document[i] + settings := doc.resource.ibm_iam_account_settings[name] + + object.get(settings, "allowed_ip_addresses", "undefined") == "undefined" + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.ibm_iam_account_settings.%s", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "'allowed_ip_addresses' should be defined with a list of trusted IPs", + "keyActualValue": "'allowed_ip_addresses' is missing (access allowed from anywhere)", + } +} + +# CASO 2: Restricciones definidas pero lista vacía. +CxPolicy[result] { + doc := input.document[i] + settings := doc.resource.ibm_iam_account_settings[name] + + ips := settings.allowed_ip_addresses + count(ips) == 0 + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.ibm_iam_account_settings.%s.allowed_ip_addresses", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "'allowed_ip_addresses' should contain at least one trusted IP/Subnet", + "keyActualValue": "'allowed_ip_addresses' is empty", + } +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_iam_account_ip_restrictions_manual/test/negative1.tf b/assets/queries/terraform/ibm/ibm_iam_account_ip_restrictions_manual/test/negative1.tf new file mode 100644 index 00000000000..96939b1d2a8 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_iam_account_ip_restrictions_manual/test/negative1.tf @@ -0,0 +1,7 @@ +resource "ibm_iam_account_settings" "settings_secure" { + mfa = "TOTP" + allowed_ip_addresses = [ + "10.1.2.3", + "192.168.1.0/24" + ] +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_iam_account_ip_restrictions_manual/test/positive1.tf b/assets/queries/terraform/ibm/ibm_iam_account_ip_restrictions_manual/test/positive1.tf new file mode 100644 index 00000000000..253da211578 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_iam_account_ip_restrictions_manual/test/positive1.tf @@ -0,0 +1,4 @@ +resource "ibm_iam_account_settings" "settings_no_ips" { + mfa = "TOTP" + session_expiration_in_seconds = 3600 +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_iam_account_ip_restrictions_manual/test/positive2.tf b/assets/queries/terraform/ibm/ibm_iam_account_ip_restrictions_manual/test/positive2.tf new file mode 100644 index 00000000000..f3bbafbe44e --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_iam_account_ip_restrictions_manual/test/positive2.tf @@ -0,0 +1,3 @@ +resource "ibm_iam_account_settings" "settings_empty_ips" { + allowed_ip_addresses = [] +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_iam_account_ip_restrictions_manual/test/positive_expected_result.json b/assets/queries/terraform/ibm/ibm_iam_account_ip_restrictions_manual/test/positive_expected_result.json new file mode 100644 index 00000000000..5f64891c33a --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_iam_account_ip_restrictions_manual/test/positive_expected_result.json @@ -0,0 +1,14 @@ +[ + { + "queryName": "IBM Account IP Restrictions (Manual)", + "severity": "INFO", + "line": 1, + "fileName": "positive1.tf" + }, + { + "queryName": "IBM Account IP Restrictions (Manual)", + "severity": "INFO", + "line": 1, + "fileName": "positive2.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_iam_account_mfa_disabled/README.md b/assets/queries/terraform/ibm/ibm_iam_account_mfa_disabled/README.md new file mode 100644 index 00000000000..0531e8afc6f --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_iam_account_mfa_disabled/README.md @@ -0,0 +1,46 @@ +# Regla KICS: MFA Obligatorio a Nivel de Cuenta en IBM IAM + +## Descripción General + +Esta regla de KICS para Terraform asegura que la Autenticación Multi-Factor (MFA) esté habilitada y forzada a un nivel seguro para toda la cuenta de IBM Cloud. La configuración se gestiona a través del recurso `ibm_iam_account_settings`. + +Forzar el uso de MFA es una de las medidas de seguridad más efectivas para proteger una cuenta contra el acceso no autorizado. Incluso si las credenciales (usuario y contraseña) de un administrador o usuario con privilegios son comprometidas, el MFA actúa como una barrera crítica de segundo nivel. + +## Lógica de la Regla + +La política se divide en tres comprobaciones complementarias para cubrir todos los escenarios de riesgo: +1. **Ausencia de Recurso:** Verifica que exista el recurso de configuración de cuenta. +2. **Atributo Faltante:** Verifica que el parámetro `mfa` esté definido explícitamente. +3. **Nivel Inseguro:** Asegura que no se utilicen niveles débiles. Los niveles seguros aceptados son `LEVEL2` (TOTP/App de autenticación) y `LEVEL3` (U2F/Llave física). + +## Casos de Fallo Detectados + +A continuación se describen los tres escenarios que esta política detectará. + +--- + +### Caso 1: Recurso `ibm_iam_account_settings` Ausente +* **Descripción:** No se está gestionando la política de la cuenta mediante código, dejando el MFA bajo configuración manual o por defecto (inseguro). +* **Ubicación:** Bloque `provider "ibm"`. + +### Caso 2: Atributo `mfa` Ausente +* **Descripción:** El recurso de configuración existe pero no define el comportamiento del MFA. +* **Ubicación:** Recurso `ibm_iam_account_settings`. + +### Caso 3: Valor de `mfa` Inseguro (`NONE` o `LEVEL1`) +* **Descripción:** El MFA está desactivado (`NONE`) o utiliza métodos débiles como email (`LEVEL1`). +* **Ubicación:** Atributo `mfa`. + +## Recurso Involucrado + +* `ibm_iam_account_settings` + +## Solución + +Para cumplir con esta política, define el recurso de configuración de cuenta y fuerza el uso de MFA de nivel 2 o superior. + +```terraform +resource "ibm_iam_account_settings" "secure_iam_policy" { + # Forzar el uso de TOTP (Time-based One-Time Password) + mfa = "LEVEL2" +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_iam_account_mfa_disabled/metadata.json b/assets/queries/terraform/ibm/ibm_iam_account_mfa_disabled/metadata.json new file mode 100644 index 00000000000..5cbe9b1f843 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_iam_account_mfa_disabled/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "ebaef99e-4add-440e-8270-caeb718e7c93", + "queryName": "Account Level MFA Is Not Enforced", + "severity": "HIGH", + "category": "Access Control", + "descriptionText": "Enforces Multi-Factor Authentication (MFA) at the account level. Requiring MFA is a fundamental security practice to prevent unauthorized access resulting from compromised credentials.", + "descriptionUrl": "https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/iam_account_settings#mfa", + "platform": "Terraform", + "descriptionID": "ebaef99e", + "cloudProvider": "ibm", + "cwe": "CWE-308", + "riskScore": 9.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_iam_account_mfa_disabled/query.rego b/assets/queries/terraform/ibm/ibm_iam_account_mfa_disabled/query.rego new file mode 100644 index 00000000000..a2a2e9ebd82 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_iam_account_mfa_disabled/query.rego @@ -0,0 +1,50 @@ +package Cx + +# REGLA 1: El recurso 'ibm_iam_account_settings' no existe en la configuración. +CxPolicy[result] { + doc := input.document[i] + _ := doc.provider.ibm + + all_iam_settings := [settings | + settings := input.document[_].resource.ibm_iam_account_settings[_] + ] + + count(all_iam_settings) == 0 + + result := { + "documentId": doc.id, + "searchKey": "provider.ibm", + "issueType": "MissingAttribute", + "keyExpectedValue": "Resource 'ibm_iam_account_settings' should exist to enforce MFA", + "keyActualValue": "Resource 'ibm_iam_account_settings' is missing in this IBM configuration", + } +} + +# REGLA 2: El atributo 'mfa' está ausente dentro del recurso. +CxPolicy[result] { + settings := input.document[i].resource.ibm_iam_account_settings[settings_name] + object.get(settings, "mfa", null) == null + + result := { + "documentId": input.document[i].id, + "searchKey": sprintf("resource.ibm_iam_account_settings.%s", [settings_name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "'mfa' attribute should be present and set to 'LEVEL2' or 'LEVEL3'", + "keyActualValue": "'mfa' attribute is missing", + } +} + +# REGLA 3: El atributo 'mfa' tiene un valor inseguro ('NONE' o 'LEVEL1'). +CxPolicy[result] { + settings := input.document[i].resource.ibm_iam_account_settings[settings_name] + insecure_mfa_levels := {"NONE", "LEVEL1"} + insecure_mfa_levels[settings.mfa] + + result := { + "documentId": input.document[i].id, + "searchKey": sprintf("resource.ibm_iam_account_settings.%s.mfa", [settings_name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "'mfa' attribute should be 'LEVEL2' or 'LEVEL3'", + "keyActualValue": sprintf("'mfa' attribute is set to '%s'", [settings.mfa]), + } +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_iam_account_mfa_disabled/test/negative1.tf b/assets/queries/terraform/ibm/ibm_iam_account_mfa_disabled/test/negative1.tf new file mode 100644 index 00000000000..0ca8fbb6935 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_iam_account_mfa_disabled/test/negative1.tf @@ -0,0 +1,4 @@ +resource "ibm_iam_account_settings" "iam_secure" { + # CORRECTO: LEVEL2 es seguro + mfa = "LEVEL2" +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_iam_account_mfa_disabled/test/positive1.tf b/assets/queries/terraform/ibm/ibm_iam_account_mfa_disabled/test/positive1.tf new file mode 100644 index 00000000000..34c0e3bb7e1 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_iam_account_mfa_disabled/test/positive1.tf @@ -0,0 +1,8 @@ +provider "ibm" { + region = "us-south" +} + +# Solo recursos de infraestructura, nada de IAM settings +resource "ibm_is_vpc" "example" { + name = "test-vpc" +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_iam_account_mfa_disabled/test/positive2.tf b/assets/queries/terraform/ibm/ibm_iam_account_mfa_disabled/test/positive2.tf new file mode 100644 index 00000000000..2435762e151 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_iam_account_mfa_disabled/test/positive2.tf @@ -0,0 +1,4 @@ +resource "ibm_iam_account_settings" "iam_no_mfa" { + # Falta el atributo mfa + session_expiration_in_seconds = 3600 +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_iam_account_mfa_disabled/test/positive3.tf b/assets/queries/terraform/ibm/ibm_iam_account_mfa_disabled/test/positive3.tf new file mode 100644 index 00000000000..8c8a160cf24 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_iam_account_mfa_disabled/test/positive3.tf @@ -0,0 +1,4 @@ +resource "ibm_iam_account_settings" "iam_weak_mfa" { + # FALLO: LEVEL1 no es suficientemente seguro + mfa = "LEVEL1" +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_iam_account_mfa_disabled/test/positive_expected_result.json b/assets/queries/terraform/ibm/ibm_iam_account_mfa_disabled/test/positive_expected_result.json new file mode 100644 index 00000000000..6f93077e52d --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_iam_account_mfa_disabled/test/positive_expected_result.json @@ -0,0 +1,20 @@ +[ + { + "queryName": "Account Level MFA Is Not Enforced", + "severity": "HIGH", + "line": 1, + "fileName": "positive1.tf" + }, + { + "queryName": "Account Level MFA Is Not Enforced", + "severity": "HIGH", + "line": 1, + "fileName": "positive2.tf" + }, + { + "queryName": "Account Level MFA Is Not Enforced", + "severity": "HIGH", + "line": 3, + "fileName": "positive3.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_iam_api_key_unused_manual/README.md b/assets/queries/terraform/ibm/ibm_iam_api_key_unused_manual/README.md new file mode 100644 index 00000000000..77a3fa2022b --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_iam_api_key_unused_manual/README.md @@ -0,0 +1,60 @@ +# Regla KICS: IBM Cloud API Keys Unused for 180 Days (Manual) + +## Descripción General + +Esta regla informativa (INFO) audita la creación de claves de API en **IBM Cloud**, cubriendo tanto las claves asociadas a usuarios (`ibm_iam_api_key`) como las asociadas a identidades de servicio o Service IDs (`ibm_iam_service_api_key`). + +El cumplimiento de normativas de seguridad de la industria y las mejores prácticas de IBM Cloud exigen que las credenciales de acceso que no se hayan utilizado durante un periodo prolongado (típicamente 180 días) sean desactivadas o eliminadas para minimizar el riesgo de uso indebido. + +**Terraform no tiene visibilidad sobre el historial de uso en tiempo real** ni sobre la telemetría de autenticación de las claves que gestiona. Por lo tanto, esta regla actúa como un recordatorio crítico para que el equipo de seguridad implemente procesos de monitoreo externos (como scripts de auditoría con la CLI de IBM Cloud o integraciones con el Activity Tracker) que detecten y reaccionen ante claves inactivas. + +## Lógica de la Regla + +La política realiza las siguientes acciones: +1. Identifica la creación de cualquier recurso `ibm_iam_api_key`. +2. Identifica la creación de cualquier recurso `ibm_iam_service_api_key`. +3. Genera una alerta informativa para cada clave detectada, instando al auditor a verificar que existe una política de ciclo de vida activa. + +## Casos de Fallo Detectados + +A continuación se describen los escenarios que esta política detectará. + +--- + +### Caso 1: API Key de Usuario Creada + +* **Descripción:** Se ha definido una clave de API personal. Estas claves suelen tener privilegios amplios y deben ser vigiladas. +* **Ejemplo de Código Terraform:** + ```terraform + resource "ibm_iam_api_key" "my_key" { + name = "user-api-key" + } + ``` +* **Ubicación de la Alerta:** Recurso `ibm_iam_api_key`. + +--- + +### Caso 2: API Key de Service ID Creada + +* **Descripción:** Se ha definido una clave para una aplicación o servicio automatizado. +* **Ejemplo de Código Terraform:** + ```terraform + resource "ibm_iam_service_api_key" "service_key" { + name = "app-service-key" + service_id_crn = ibm_iam_service_id.serviceID.crn + } + ``` +* **Ubicación de la Alerta:** Recurso `ibm_iam_service_api_key`. + +## Recursos Involucrados + +* `ibm_iam_api_key` +* `ibm_iam_service_api_key` + +## Solución + +Establezca un proceso automatizado o una revisión periódica (fuera de Terraform) que consulte el estado de las claves. Puede usar la CLI de IBM Cloud para identificar claves inactivas: + +```bash +# Ejemplo: Listar claves de API y verificar el campo 'Last Used' +ibmcloud iam api-keys --output json \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_iam_api_key_unused_manual/metadata.json b/assets/queries/terraform/ibm/ibm_iam_api_key_unused_manual/metadata.json new file mode 100644 index 00000000000..c9c10f6d76a --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_iam_api_key_unused_manual/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "a2b88f1a-038f-4898-a7c1-61eb2aa41143", + "queryName": "IBM Cloud API Keys Unused for 180 Days (Manual)", + "severity": "INFO", + "category": "Observability", + "descriptionText": "Terraform cannot detect if an IBM Cloud IAM API Key is unused. This rule flags the creation of API Keys to remind the auditor to establish an automated process to detect and disable keys unused for 180 days.", + "descriptionUrl": "https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/iam_api_key", + "platform": "Terraform", + "descriptionID": "a2b88f1a", + "cloudProvider": "ibm", + "cwe": "CWE-798", + "riskScore": 0.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_iam_api_key_unused_manual/query.rego b/assets/queries/terraform/ibm/ibm_iam_api_key_unused_manual/query.rego new file mode 100644 index 00000000000..76f1454c05a --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_iam_api_key_unused_manual/query.rego @@ -0,0 +1,29 @@ +package Cx + +# CASO 1: IBM Cloud IAM API Key (Usuario). +CxPolicy[result] { + doc := input.document[i] + resource := doc.resource.ibm_iam_api_key[name] + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.ibm_iam_api_key.%s", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "An external process should monitor and disable this key if unused for 180 days", + "keyActualValue": "IBM IAM User API Key created via Terraform. Manual verification of lifecycle policy required.", + } +} + +# CASO 2: IBM Cloud IAM Service API Key (Service ID). +CxPolicy[result] { + doc := input.document[i] + resource := doc.resource.ibm_iam_service_api_key[name] + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.ibm_iam_service_api_key.%s", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "An external process should monitor and disable this key if unused for 180 days", + "keyActualValue": "IBM IAM Service API Key created via Terraform. Manual verification of lifecycle policy required.", + } +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_iam_api_key_unused_manual/test/negative1.tf b/assets/queries/terraform/ibm/ibm_iam_api_key_unused_manual/test/negative1.tf new file mode 100644 index 00000000000..28dbe58cfe0 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_iam_api_key_unused_manual/test/negative1.tf @@ -0,0 +1,8 @@ +# El cumplimiento es "negativo" cuando no hay recursos de claves de API que requieran monitoreo de ciclo de vida. +resource "ibm_is_vpc" "safe_vpc" { + name = "compliant-vpc" +} + +resource "ibm_iam_access_group" "acc_group" { + name = "test-group" +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_iam_api_key_unused_manual/test/positive1.tf b/assets/queries/terraform/ibm/ibm_iam_api_key_unused_manual/test/positive1.tf new file mode 100644 index 00000000000..bbcaa0dfd23 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_iam_api_key_unused_manual/test/positive1.tf @@ -0,0 +1,3 @@ +resource "ibm_iam_api_key" "personal_key" { + name = "admin-key" +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_iam_api_key_unused_manual/test/positive2.tf b/assets/queries/terraform/ibm/ibm_iam_api_key_unused_manual/test/positive2.tf new file mode 100644 index 00000000000..e2d54fb69f0 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_iam_api_key_unused_manual/test/positive2.tf @@ -0,0 +1,4 @@ +resource "ibm_iam_service_api_key" "app_key" { + name = "automation-key" + service_id_crn = "crn:v1:bluemix:public:iam-identity::a/123::serviceid:ServiceId-123" +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_iam_api_key_unused_manual/test/positive_expected_result.json b/assets/queries/terraform/ibm/ibm_iam_api_key_unused_manual/test/positive_expected_result.json new file mode 100644 index 00000000000..e17a67b4b7b --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_iam_api_key_unused_manual/test/positive_expected_result.json @@ -0,0 +1,14 @@ +[ + { + "queryName": "IBM Cloud API Keys Unused for 180 Days (Manual)", + "severity": "INFO", + "line": 1, + "fileName": "positive1.tf" + }, + { + "queryName": "IBM Cloud API Keys Unused for 180 Days (Manual)", + "severity": "INFO", + "line": 1, + "fileName": "positive2.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_iam_owner_api_key_manual/README.md b/assets/queries/terraform/ibm/ibm_iam_owner_api_key_manual/README.md new file mode 100644 index 00000000000..fa1c74979f2 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_iam_owner_api_key_manual/README.md @@ -0,0 +1,50 @@ +# Regla KICS: IBM Owner Account API Key (Manual) + +## Descripción General + +Esta regla informativa (INFO) detecta la creación de claves de API de usuario (`ibm_iam_api_key`) en IBM Cloud para mitigar riesgos de privilegios excesivos. + +Según las mejores prácticas de seguridad, como el **IBM Cloud Foundations Benchmark**, la cuenta propietaria (**Account Owner**) no debe tener claves de API activas. El propietario tiene privilegios absolutos sobre la facturación, gestión de identidades y la eliminación total de la cuenta. Las claves de API eluden el control de MFA (Autenticación Multi-Factor), por lo que una clave de propietario comprometida otorga control total e irreversible a un atacante. + +## Lógica de la Regla + +1. Identifica recursos del tipo `ibm_iam_api_key`. +2. Genera una alerta informativa que requiere validación manual. Dado que Terraform no tiene visibilidad del rol del usuario (Owner o no) en tiempo de escaneo estático, la regla alerta sobre cualquier clave de usuario creada. +3. Esta regla **excluye** las claves de Service ID (`ibm_iam_service_api_key`), ya que estas son la alternativa segura recomendada y no pueden poseer el rol de "Account Owner". + +## Casos de Fallo Detectados + +A continuación se describe el escenario que esta política detectará. + +--- + +### Caso 1: API Key de Usuario (Potencial Owner) + +* **Descripción:** Se ha creado una clave de API personal. Se debe confirmar que el usuario que emite la clave no sea el propietario de la cuenta. +* **Ejemplo de Código Terraform Problemático:** + ```terraform + resource "ibm_iam_api_key" "personal_key" { + name = "admin-key" + } + ``` +* **Ubicación de la Alerta:** Recurso `ibm_iam_api_key`. + +## Recurso Involucrado + +* `ibm_iam_api_key` + +## Solución + +1. Verifique en la consola de IAM que el usuario asociado a la clave no es el "Account Owner". +2. Si lo es, elimine la clave y utilice un **Service ID** con permisos limitados mediante el principio de mínimo privilegio. + +```terraform +# RECOMENDADO: Usar Service ID para automatizaciones +resource "ibm_iam_service_id" "app_identity" { + name = "app-automation-id" +} + +resource "ibm_iam_service_api_key" "app_key" { + name = "automation-key" + iam_service_id = ibm_iam_service_id.app_identity.iam_id +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_iam_owner_api_key_manual/metadata.json b/assets/queries/terraform/ibm/ibm_iam_owner_api_key_manual/metadata.json new file mode 100644 index 00000000000..b0a60a9691d --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_iam_owner_api_key_manual/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "dae483b9-d462-49d1-993c-4d398cad281e", + "queryName": "IBM Owner Account API Key (Manual)", + "severity": "INFO", + "category": "Access Control", + "descriptionText": "The account owner should not have an associated API Key. Using owner credentials implies unlimited privileges and poses a catastrophic risk if compromised. Verify that this API Key does not belong to the Account Owner.", + "descriptionUrl": "https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/iam_api_key", + "platform": "Terraform", + "descriptionID": "dae483b9", + "cloudProvider": "ibm", + "cwe": "CWE-269", + "riskScore": 0.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_iam_owner_api_key_manual/query.rego b/assets/queries/terraform/ibm/ibm_iam_owner_api_key_manual/query.rego new file mode 100644 index 00000000000..5ed5881e0d0 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_iam_owner_api_key_manual/query.rego @@ -0,0 +1,15 @@ +package Cx + +# CASO 1: Detección de API Key de Usuario. +CxPolicy[result] { + doc := input.document[i] + resource := doc.resource.ibm_iam_api_key[name] + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.ibm_iam_api_key.%s", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "The API Key should belong to a functional user or Service ID, NEVER the Account Owner", + "keyActualValue": "IBM IAM User API Key detected. Verify this key is NOT for the Account Owner.", + } +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_iam_owner_api_key_manual/test/negative1.tf b/assets/queries/terraform/ibm/ibm_iam_owner_api_key_manual/test/negative1.tf new file mode 100644 index 00000000000..430cc21292a --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_iam_owner_api_key_manual/test/negative1.tf @@ -0,0 +1,9 @@ +# Las claves de Service ID son el estándar seguro y no disparan esta alerta +resource "ibm_iam_service_id" "service_id" { + name = "safe-service" +} + +resource "ibm_iam_service_api_key" "service_key" { + name = "safe-key" + iam_service_id = ibm_iam_service_id.service_id.iam_id +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_iam_owner_api_key_manual/test/positive1.tf b/assets/queries/terraform/ibm/ibm_iam_owner_api_key_manual/test/positive1.tf new file mode 100644 index 00000000000..4c8f76627cc --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_iam_owner_api_key_manual/test/positive1.tf @@ -0,0 +1,3 @@ +resource "ibm_iam_api_key" "owner_candidate_key" { + name = "potential-owner-key" +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_iam_owner_api_key_manual/test/positive_expected_result.json b/assets/queries/terraform/ibm/ibm_iam_owner_api_key_manual/test/positive_expected_result.json new file mode 100644 index 00000000000..98f50c83fc9 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_iam_owner_api_key_manual/test/positive_expected_result.json @@ -0,0 +1,8 @@ +[ + { + "queryName": "IBM Owner Account API Key (Manual)", + "severity": "INFO", + "line": 1, + "fileName": "positive1.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_iam_policy_assigned_to_user_manual/README.md b/assets/queries/terraform/ibm/ibm_iam_policy_assigned_to_user_manual/README.md new file mode 100644 index 00000000000..844fcfb4414 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_iam_policy_assigned_to_user_manual/README.md @@ -0,0 +1,65 @@ +# Regla KICS: IBM IAM Policies Attached to Users (Manual) + +## Descripción General + +Esta regla informativa (INFO) detecta el uso del recurso `ibm_iam_user_policy` en Terraform. + +La asignación de permisos basada en grupos (**RBAC**) es el pilar de una gobernanza de identidades robusta. Las mejores prácticas de IBM Cloud y marcos de cumplimiento como CIS recomiendan gestionar los permisos a través de **Grupos de Acceso** (Access Groups) en lugar de asignar políticas directamente a usuarios individuales. + +La asignación directa usuario a usuario crea una infraestructura "snowflake" donde cada identidad tiene permisos únicos, lo que hace casi imposible auditar el impacto de cambios globales o garantizar el principio de mínimo privilegio. El uso de grupos facilita el "onboarding" y "offboarding" de usuarios y centraliza el control de acceso. + +## Lógica de la Regla + +1. Escanea el código de Terraform en busca del recurso `ibm_iam_user_policy`. +2. Genera una alerta para cada ocurrencia detectada. +3. El auditor debe verificar si esta asignación directa está realmente justificada (como en el caso de cuentas de administración de emergencia o "break-glass") o si debe ser refactorizada para integrarse en un grupo de acceso. + +## Casos de Fallo Detectados + +A continuación se describe el escenario que esta política detectará. + +--- + +### Caso 1: Asignación de Política Directa a Usuario + +* **Descripción:** Se utiliza el recurso `ibm_iam_user_policy` para otorgar roles a una identidad de usuario específica. +* **Ejemplo de Código Terraform Problemático:** + ```terraform + resource "ibm_iam_user_policy" "direct_access" { + ibm_id = "user@example.com" + roles = ["Administrator"] + + resources { + service = "is" + } + } + ``` +* **Ubicación de la Alerta:** Recurso `ibm_iam_user_policy`. + +## Recurso Involucrado + +* `ibm_iam_user_policy` + +## Solución + +La práctica recomendada consiste en crear un grupo de acceso, asignar la política a dicho grupo y posteriormente añadir a los usuarios como miembros. + +```terraform +# PATRÓN CORRECTO Y ESCALABLE +resource "ibm_iam_access_group" "admins" { + name = "Cloud-Administrators" +} + +resource "ibm_iam_access_group_policy" "admin_policy" { + access_group_id = ibm_iam_access_group.admins.id + roles = ["Administrator"] + + resources { + service = "is" + } +} + +resource "ibm_iam_access_group_members" "admin_members" { + access_group_id = ibm_iam_access_group.admins.id + ibm_ids = ["user@example.com"] +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_iam_policy_assigned_to_user_manual/metadata.json b/assets/queries/terraform/ibm/ibm_iam_policy_assigned_to_user_manual/metadata.json new file mode 100644 index 00000000000..97aee910dd5 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_iam_policy_assigned_to_user_manual/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "d08b8443-aa59-4099-9523-5cfc6bee1d4a", + "queryName": "IBM IAM Policies Attached to Users (Manual)", + "severity": "INFO", + "category": "Access Control", + "descriptionText": "Detects usage of 'ibm_iam_user_policy'. Access policies should be assigned to Access Groups ('ibm_iam_access_group_policy') rather than directly to users to ensure scalable and manageable IAM governance.", + "descriptionUrl": "https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/iam_user_policy", + "platform": "Terraform", + "descriptionID": "d08b8443", + "cloudProvider": "ibm", + "cwe": "CWE-284", + "riskScore": 0.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_iam_policy_assigned_to_user_manual/query.rego b/assets/queries/terraform/ibm/ibm_iam_policy_assigned_to_user_manual/query.rego new file mode 100644 index 00000000000..6c640e7996c --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_iam_policy_assigned_to_user_manual/query.rego @@ -0,0 +1,15 @@ +package Cx + +# CASO 1: Detección de políticas directas a usuario. +CxPolicy[result] { + doc := input.document[i] + policy := doc.resource.ibm_iam_user_policy[name] + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.ibm_iam_user_policy.%s", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "IAM policies should be assigned to Access Groups, not users", + "keyActualValue": "Direct user policy assignment detected. Manual review required to justify exception.", + } +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_iam_policy_assigned_to_user_manual/test/negative1.tf b/assets/queries/terraform/ibm/ibm_iam_policy_assigned_to_user_manual/test/negative1.tf new file mode 100644 index 00000000000..7febdcd80cc --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_iam_policy_assigned_to_user_manual/test/negative1.tf @@ -0,0 +1,14 @@ +# Uso correcto de políticas mediante grupos de acceso +resource "ibm_iam_access_group" "auditors" { + name = "Audit-Team" +} + +resource "ibm_iam_access_group_policy" "group_policy" { + access_group_id = ibm_iam_access_group.auditors.id + roles = ["Viewer"] +} + +resource "ibm_iam_access_group_members" "members" { + access_group_id = ibm_iam_access_group.auditors.id + ibm_ids = ["auditor@company.com"] +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_iam_policy_assigned_to_user_manual/test/positive1.tf b/assets/queries/terraform/ibm/ibm_iam_policy_assigned_to_user_manual/test/positive1.tf new file mode 100644 index 00000000000..cbbb7c042be --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_iam_policy_assigned_to_user_manual/test/positive1.tf @@ -0,0 +1,8 @@ +resource "ibm_iam_user_policy" "insecure_policy" { + ibm_id = "auditor@company.com" + roles = ["Viewer"] + + resources { + resource_type = "resource-group" + } +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_iam_policy_assigned_to_user_manual/test/positive_expected_result.json b/assets/queries/terraform/ibm/ibm_iam_policy_assigned_to_user_manual/test/positive_expected_result.json new file mode 100644 index 00000000000..fb108f03019 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_iam_policy_assigned_to_user_manual/test/positive_expected_result.json @@ -0,0 +1,8 @@ +[ + { + "queryName": "IBM IAM Policies Attached to Users (Manual)", + "severity": "INFO", + "line": 1, + "fileName": "positive1.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_iam_restrict_apikey_creation_manual/README.md b/assets/queries/terraform/ibm/ibm_iam_restrict_apikey_creation_manual/README.md new file mode 100644 index 00000000000..7f68b7b9667 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_iam_restrict_apikey_creation_manual/README.md @@ -0,0 +1,50 @@ +# Regla KICS: Restrict API Key & Service ID Creation (Manual) + +## Descripción General + +Esta regla informativa (INFO) audita las asignaciones de roles en **IBM Cloud IAM** para asegurar que la capacidad de crear identidades y credenciales esté estrictamente controlada. + +Según los controles de seguridad **CIS IBM Cloud Foundations**, la creación de **claves de API de usuario** y **Service IDs** es una acción de alto privilegio que debe restringirse a un número mínimo de usuarios de confianza. En IBM Cloud, roles predefinidos de plataforma como `Administrator` o `Editor` sobre el servicio de identidad incluyen permisos potentes como `iam.service_id.create` o `iam.api_key.create`. + +Esta regla identifica las definiciones de políticas para que un auditor verifique que estos roles no se estén asignando de forma masiva o a sujetos que no requieren capacidades administrativas de identidad, cumpliendo con el **Principio de Mínimo Privilegio**. + +## Lógica de la Regla + +La política realiza las siguientes acciones: +1. Identifica recursos `ibm_iam_user_policy` y `ibm_iam_access_group_policy`. +2. Extrae y evalúa la lista de roles asignados en cada política. +3. Genera una alerta para que se verifique manualmente si los roles asignados (especialmente Administrator o Editor) son estrictamente necesarios para el propósito del usuario o grupo. + +## Casos de Fallo Detectados + +A continuación se describen los escenarios que esta política detectará. + +--- + +### Caso 1: Revisión de Roles en Políticas de Usuario +* **Descripción:** Se asignan roles directamente a una identidad de usuario. +* **Ubicación de la Alerta:** Atributo `roles` dentro de `ibm_iam_user_policy`. + +### Caso 2: Revisión de Roles en Políticas de Grupo +* **Descripción:** Se asignan roles a un Grupo de Acceso (Access Group), afectando a todos sus miembros. +* **Ubicación de la Alerta:** Atributo `roles` dentro de `ibm_iam_access_group_policy`. + +## Recurso Involucrado + +* `ibm_iam_user_policy` +* `ibm_iam_access_group_policy` + +## Solución + +Revisa los roles asignados. Evita otorgar roles de plataforma amplios como `Editor` o `Administrator` de forma generalizada. Si un usuario solo necesita gestionar recursos existentes, utiliza el rol `Operator` o `Viewer`. Si se requiere una granularidad mayor, implemente **Roles Personalizados (Custom Roles)** que omitan específicamente las acciones de creación de identidades. + +```terraform +# Ejemplo de política restrictiva usando roles de menor privilegio +resource "ibm_iam_access_group_policy" "restricted_group_policy" { + access_group_id = ibm_iam_access_group.group.id + roles = ["Viewer", "Operator"] # Roles que no permiten creación de IDs/Keys + + resources { + service = "is" # VPC Infrastructure + } +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_iam_restrict_apikey_creation_manual/metadata.json b/assets/queries/terraform/ibm/ibm_iam_restrict_apikey_creation_manual/metadata.json new file mode 100644 index 00000000000..8f2aba14b52 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_iam_restrict_apikey_creation_manual/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "30640fc8-88be-4053-882c-55fd0febaf3f", + "queryName": "Restrict API Key & Service ID Creation (Manual)", + "severity": "INFO", + "category": "Access Control", + "descriptionText": "CIS controls recommend restricting the creation of API Keys and Service IDs to privileged users only. Review IAM policies to ensure 'Editor' or 'Administrator' roles (or custom roles with creation actions) are not granted broadly.", + "descriptionUrl": "https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/iam_user_policy", + "platform": "Terraform", + "descriptionID": "30640fc8", + "cloudProvider": "ibm", + "cwe": "CWE-276", + "riskScore": 0.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_iam_restrict_apikey_creation_manual/query.rego b/assets/queries/terraform/ibm/ibm_iam_restrict_apikey_creation_manual/query.rego new file mode 100644 index 00000000000..d60ab79cae8 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_iam_restrict_apikey_creation_manual/query.rego @@ -0,0 +1,38 @@ +package Cx + +ensure_array(x) = x { is_array(x) } +ensure_array(x) = [x] { not is_array(x) } + +# CASO 1: Revisión de Políticas de Usuario (ibm_iam_user_policy). +CxPolicy[result] { + doc := input.document[i] + policy := doc.resource.ibm_iam_user_policy[name] + + roles := ensure_array(policy.roles) + count(roles) > 0 + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.ibm_iam_user_policy.%s.roles", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "Roles should not grant 'iam.service_id.create' or 'iam.api_key.create' to non-privileged users", + "keyActualValue": sprintf("User policy grants roles: %v. Manual review required.", [roles]), + } +} + +# CASO 2: Revisión de Políticas de Grupo de Acceso (ibm_iam_access_group_policy). +CxPolicy[result] { + doc := input.document[i] + policy := doc.resource.ibm_iam_access_group_policy[name] + + roles := ensure_array(policy.roles) + count(roles) > 0 + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.ibm_iam_access_group_policy.%s.roles", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "Roles should not grant 'iam.service_id.create' or 'iam.api_key.create' to non-privileged users", + "keyActualValue": sprintf("Access Group policy grants roles: %v. Manual review required.", [roles]), + } +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_iam_restrict_apikey_creation_manual/test/negative1.tf b/assets/queries/terraform/ibm/ibm_iam_restrict_apikey_creation_manual/test/negative1.tf new file mode 100644 index 00000000000..6f3d0627404 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_iam_restrict_apikey_creation_manual/test/negative1.tf @@ -0,0 +1,4 @@ +# El cumplimiento es "negativo" cuando no hay políticas de IAM que auditar en este contexto. +resource "ibm_is_vpc" "example_vpc" { + name = "compliant-network" +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_iam_restrict_apikey_creation_manual/test/positive1.tf b/assets/queries/terraform/ibm/ibm_iam_restrict_apikey_creation_manual/test/positive1.tf new file mode 100644 index 00000000000..8f249923af7 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_iam_restrict_apikey_creation_manual/test/positive1.tf @@ -0,0 +1,8 @@ +resource "ibm_iam_user_policy" "high_privilege_user" { + ibm_id = "user@example.com" + roles = ["Administrator"] # Alerta: requiere revisión manual + + resources { + service = "iam-identity" + } +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_iam_restrict_apikey_creation_manual/test/positive2.tf b/assets/queries/terraform/ibm/ibm_iam_restrict_apikey_creation_manual/test/positive2.tf new file mode 100644 index 00000000000..372270cfa91 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_iam_restrict_apikey_creation_manual/test/positive2.tf @@ -0,0 +1,8 @@ +resource "ibm_iam_access_group_policy" "broad_group_policy" { + access_group_id = "access-group-id" + roles = ["Editor", "Viewer"] # Alerta: Editor permite creación de credenciales + + resources { + service = "iam-identity" + } +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_iam_restrict_apikey_creation_manual/test/positive_expected_result.json b/assets/queries/terraform/ibm/ibm_iam_restrict_apikey_creation_manual/test/positive_expected_result.json new file mode 100644 index 00000000000..f0aa46eaf5e --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_iam_restrict_apikey_creation_manual/test/positive_expected_result.json @@ -0,0 +1,14 @@ +[ + { + "queryName": "Restrict API Key & Service ID Creation (Manual)", + "severity": "INFO", + "line": 3, + "fileName": "positive1.tf" + }, + { + "queryName": "Restrict API Key & Service ID Creation (Manual)", + "severity": "INFO", + "line": 3, + "fileName": "positive2.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_iam_session_expiration_too_long/README.md b/assets/queries/terraform/ibm/ibm_iam_session_expiration_too_long/README.md new file mode 100644 index 00000000000..fc8fc907db3 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_iam_session_expiration_too_long/README.md @@ -0,0 +1,56 @@ +# Regla KICS: IBM Account Session Expiration Too Long + +## Descripción General + +Esta regla de severidad **MEDIA** verifica que la **Expiración de Sesión** en la configuración de la cuenta de IBM Cloud esté configurada de manera segura para mitigar riesgos de seguridad de identidad. + +Por defecto, o si se configura de forma laxa, las sesiones de usuario pueden permanecer activas durante periodos prolongados (hasta 24 horas). Esto aumenta considerablemente la ventana de oportunidad para ataques de secuestro de sesión (Session Hijacking) y accesos no autorizados en terminales físicas compartidas. Según las mejores prácticas de la industria y el **CIS IBM Cloud Foundations Benchmark**, se recomienda limitar la duración de la sesión a **3600 segundos (1 hora)** o menos para forzar una re-autenticación periódica. + +## Lógica de la Regla + +La política audita el recurso `ibm_iam_account_settings` realizando dos comprobaciones: +1. **Validación de Presencia:** Si falta el atributo `session_expiration_in_seconds`, la regla falla al asumir que la cuenta depende de valores por defecto inseguros. +2. **Validación de Valor:** Si el valor configurado es numéricamente superior a `3600` segundos, la regla falla. + +## Casos de Fallo Detectados + +A continuación se describen los escenarios que esta política detectará. + +--- + +### Caso 1: Configuración de Expiración Ausente +* **Descripción:** El recurso existe pero no define el tiempo de vida de la sesión, delegando el control al proveedor. +* **Ejemplo de Código Terraform Problemático:** + ```terraform + resource "ibm_iam_account_settings" "insecure_default" { + mfa = "LEVEL2" + # Falta session_expiration_in_seconds + } + ``` +* **Ubicación de la Alerta:** Bloque del recurso `ibm_iam_account_settings`. + +--- + +### Caso 2: Tiempo de Sesión Excesivo +* **Descripción:** La sesión se ha configurado con una duración mayor a la hora recomendada (ej. 8 horas o 24 horas). +* **Ejemplo de Código Terraform Problemático:** + ```terraform + resource "ibm_iam_account_settings" "too_long" { + session_expiration_in_seconds = 28800 # 8 horas + } + ``` +* **Ubicación de la Alerta:** Atributo `session_expiration_in_seconds`. + +## Recurso Involucrado + +* `ibm_iam_account_settings` + +## Solución + +Asegúrese de definir el límite de sesión en 3600 segundos o menos para cumplir con los estándares de seguridad. + +```terraform +resource "ibm_iam_account_settings" "compliant_settings" { + mfa = "LEVEL2" + session_expiration_in_seconds = 3600 # 1 Hora +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_iam_session_expiration_too_long/metadata.json b/assets/queries/terraform/ibm/ibm_iam_session_expiration_too_long/metadata.json new file mode 100644 index 00000000000..3cd3a3c7988 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_iam_session_expiration_too_long/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "3d6db2d6-fa2d-41df-a783-d3dee76f717f", + "queryName": "IBM Account Session Expiration Too Long", + "severity": "MEDIUM", + "category": "Access Control", + "descriptionText": "Ensures that the IBM Cloud account session expiration is configured to a secure limit (e.g., 3600 seconds / 1 hour). Long session timeouts increase the risk of session hijacking.", + "descriptionUrl": "https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/iam_account_settings", + "platform": "Terraform", + "descriptionID": "3d6db2d6", + "cloudProvider": "ibm", + "cwe": "CWE-613", + "riskScore": 5.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_iam_session_expiration_too_long/query.rego b/assets/queries/terraform/ibm/ibm_iam_session_expiration_too_long/query.rego new file mode 100644 index 00000000000..d374d167dda --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_iam_session_expiration_too_long/query.rego @@ -0,0 +1,35 @@ +package Cx + +# REGLA 1: Falta el atributo 'session_expiration_in_seconds'. +CxPolicy[result] { + doc := input.document[i] + settings := doc.resource.ibm_iam_account_settings[name] + + object.get(settings, "session_expiration_in_seconds", "undefined") == "undefined" + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.ibm_iam_account_settings.%s", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "'session_expiration_in_seconds' should be defined and set to 3600 (1 hour) or less", + "keyActualValue": "'session_expiration_in_seconds' is missing (using insecure default)", + } +} + +# REGLA 2: La sesión dura más de 1 hora (3600 segundos). +CxPolicy[result] { + doc := input.document[i] + settings := doc.resource.ibm_iam_account_settings[name] + + expiration := to_number(settings.session_expiration_in_seconds) + + expiration > 3600 + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.ibm_iam_account_settings.%s.session_expiration_in_seconds", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "'session_expiration_in_seconds' should be <= 3600", + "keyActualValue": sprintf("'session_expiration_in_seconds' is set to '%v'", [expiration]), + } +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_iam_session_expiration_too_long/test/negative1.tf b/assets/queries/terraform/ibm/ibm_iam_session_expiration_too_long/test/negative1.tf new file mode 100644 index 00000000000..bb5077c463f --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_iam_session_expiration_too_long/test/negative1.tf @@ -0,0 +1,4 @@ +resource "ibm_iam_account_settings" "settings_secure" { + mfa = "LEVEL2" + session_expiration_in_seconds = 3600 # 1 Hora +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_iam_session_expiration_too_long/test/positive1.tf b/assets/queries/terraform/ibm/ibm_iam_session_expiration_too_long/test/positive1.tf new file mode 100644 index 00000000000..2fe6df87104 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_iam_session_expiration_too_long/test/positive1.tf @@ -0,0 +1,3 @@ +resource "ibm_iam_account_settings" "settings_missing_attr" { + mfa = "LEVEL2" +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_iam_session_expiration_too_long/test/positive2.tf b/assets/queries/terraform/ibm/ibm_iam_session_expiration_too_long/test/positive2.tf new file mode 100644 index 00000000000..b12be11ded2 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_iam_session_expiration_too_long/test/positive2.tf @@ -0,0 +1,3 @@ +resource "ibm_iam_account_settings" "settings_too_long" { + session_expiration_in_seconds = 86400 # 24 Horas +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_iam_session_expiration_too_long/test/positive_expected_result.json b/assets/queries/terraform/ibm/ibm_iam_session_expiration_too_long/test/positive_expected_result.json new file mode 100644 index 00000000000..cccff9041d8 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_iam_session_expiration_too_long/test/positive_expected_result.json @@ -0,0 +1,14 @@ +[ + { + "queryName": "IBM Account Session Expiration Too Long", + "severity": "MEDIUM", + "line": 1, + "fileName": "positive1.tf" + }, + { + "queryName": "IBM Account Session Expiration Too Long", + "severity": "MEDIUM", + "line": 2, + "fileName": "positive2.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_iks_cluster_logging_disabled/README.md b/assets/queries/terraform/ibm/ibm_iks_cluster_logging_disabled/README.md new file mode 100644 index 00000000000..c47c34a27be --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_iks_cluster_logging_disabled/README.md @@ -0,0 +1,55 @@ +# Regla KICS: Logging Habilitado para Clusters de IKS + +## Descripción General + +Esta regla de KICS para Terraform asegura que cada clúster de IBM Cloud Kubernetes Service (`ibm_container_cluster`) tenga una configuración de logging de IBM Cloud Log Analysis (`ibm_ob_logging_config`) correctamente asociada. + +La agregación de logs de un clúster de Kubernetes es una práctica fundamental para la observabilidad y la seguridad. Permite a los equipos de desarrollo y operaciones centralizar los logs de los contenedores, los nodos y los componentes del sistema. Sin esta configuración, el análisis forense tras un incidente de seguridad o la resolución de errores en aplicaciones distribuidas se vuelve extremadamente compleja. + +## Lógica de la Regla + +La política implementa una lógica de correlación de recursos en todo el proyecto. Su función es identificar cualquier instancia de `ibm_container_cluster` que no esté referenciada en el atributo `scope` de ningún recurso `ibm_ob_logging_config`. La conexión se considera válida cuando el nombre o CRN del clúster aparece en el alcance (scope) de la configuración de observabilidad. + +## Casos de Fallo Detectados + +A continuación se describe el escenario que esta política detectará. + +--- + +### Caso Único: Configuración de Logging Ausente para un Clúster + +* **Descripción:** Esta regla detecta cualquier clúster de IKS que se esté aprovisionando sin su correspondiente pieza de telemetría para logs. +* **Ejemplo de Código Terraform Problemático:** + ```terraform + resource "ibm_container_cluster" "orphan_cluster" { + name = "my-production-cluster" + datacenter = "dal10" + machine_type = "b3c.4x16" + hardware = "shared" + # No existe un recurso ibm_ob_logging_config que use este clúster como scope + } + ``` +* **Ubicación de la Alerta:** La alerta señalará directamente al bloque de código del recurso `ibm_container_cluster` huérfano de configuración de logs. + +## Recursos Involucrados + +* `ibm_container_cluster` +* `ibm_ob_logging_config` + +## Solución + +Para solucionar los problemas detectados, asegúrese de que para cada clúster exista un recurso `ibm_ob_logging_config` que vincule el clúster con una instancia de Log Analysis. + +```terraform +resource "ibm_ob_logging_config" "cluster_telemetry" { + # Vincular con el clúster mediante su CRN o ID + scope = ibm_container_cluster.my_cluster.crn + instance = ibm_resource_instance.log_analysis_inst.guid +} + +resource "ibm_resource_instance" "log_analysis_inst" { + name = "log-aggregator" + service = "logdna" + plan = "7-day" + location = "us-south" +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_iks_cluster_logging_disabled/metadata.json b/assets/queries/terraform/ibm/ibm_iks_cluster_logging_disabled/metadata.json new file mode 100644 index 00000000000..5461ea44b9c --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_iks_cluster_logging_disabled/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "e55a0224-47b8-41a8-8cc1-8c8581e13a8d", + "queryName": "IKS Cluster Logging Disabled", + "severity": "MEDIUM", + "category": "Observability", + "descriptionText": "Ensures that every IBM Cloud Kubernetes Service (IKS) cluster has an associated logging service configuration. Centralized logging is essential for troubleshooting applications, monitoring cluster activity, and performing security analysis.", + "descriptionUrl": "https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/ob_logging_config", + "platform": "Terraform", + "descriptionID": "e55a0224", + "cloudProvider": "ibm", + "cwe": "CWE-778", + "riskScore": 5.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_iks_cluster_logging_disabled/query.rego b/assets/queries/terraform/ibm/ibm_iks_cluster_logging_disabled/query.rego new file mode 100644 index 00000000000..26b576f814a --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_iks_cluster_logging_disabled/query.rego @@ -0,0 +1,21 @@ +package Cx + +CxPolicy[result] { + doc := input.document[i] + _ := doc.resource.ibm_container_cluster[cluster_name] + + matching_configs := [config | + config := input.document[_].resource.ibm_ob_logging_config[_] + contains(config.scope, cluster_name) + ] + + count(matching_configs) == 0 + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.ibm_container_cluster.%s", [cluster_name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "ibm_container_cluster should have an associated ibm_ob_logging_config resource", + "keyActualValue": "ibm_container_cluster does not have an associated ibm_ob_logging_config resource", + } +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_iks_cluster_logging_disabled/test/negative1.tf b/assets/queries/terraform/ibm/ibm_iks_cluster_logging_disabled/test/negative1.tf new file mode 100644 index 00000000000..38a35202e55 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_iks_cluster_logging_disabled/test/negative1.tf @@ -0,0 +1,13 @@ +resource "ibm_container_cluster" "secure_cluster" { + name = "compliant-cluster" + datacenter = "dal10" + machine_type = "b3c.4x16" + hardware = "shared" + public_vlan_id = "123" + private_vlan_id = "456" +} + +resource "ibm_ob_logging_config" "logging_ok" { + scope = ibm_container_cluster.secure_cluster.crn + instance = "crn:v1:bluemix:public:logdna:..." +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_iks_cluster_logging_disabled/test/positive1.tf b/assets/queries/terraform/ibm/ibm_iks_cluster_logging_disabled/test/positive1.tf new file mode 100644 index 00000000000..c940fb487ab --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_iks_cluster_logging_disabled/test/positive1.tf @@ -0,0 +1,8 @@ +resource "ibm_container_cluster" "cluster_without_logs" { + name = "vulnerable-cluster" + datacenter = "dal10" + machine_type = "b3c.4x16" + hardware = "shared" + public_vlan_id = "123" + private_vlan_id = "456" +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_iks_cluster_logging_disabled/test/positive_expected_result.json b/assets/queries/terraform/ibm/ibm_iks_cluster_logging_disabled/test/positive_expected_result.json new file mode 100644 index 00000000000..64f347ff4ab --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_iks_cluster_logging_disabled/test/positive_expected_result.json @@ -0,0 +1,8 @@ +[ + { + "queryName": "IKS Cluster Logging Disabled", + "severity": "MEDIUM", + "line": 1, + "fileName": "positive1.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_iks_cluster_monitoring_disabled/README.md b/assets/queries/terraform/ibm/ibm_iks_cluster_monitoring_disabled/README.md new file mode 100644 index 00000000000..c3ff1801860 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_iks_cluster_monitoring_disabled/README.md @@ -0,0 +1,55 @@ +# Regla KICS: Monitorización Habilitada para Clusters de IKS + +## Descripción General + +Esta regla de KICS para Terraform asegura que cada clúster de IBM Cloud Kubernetes Service (`ibm_container_cluster`) tenga una configuración de monitorización de **IBM Cloud Monitoring** (`ibm_ob_monitoring_config`) vinculada. + +La monitorización de un clúster de Kubernetes es un requisito crítico para la operatividad y estabilidad. Permite obtener métricas en tiempo real sobre el uso de CPU, memoria, tráfico de red y latencia de los servicios. Sin esta visibilidad, los equipos de operaciones no pueden detectar cuellos de botella, prever la saturación de recursos mediante escalado automático, ni responder proactivamente a incidentes de salud en la infraestructura. + +## Lógica de la Regla + +La política implementa una lógica de correlación cruzada en el documento de Terraform. Identifica cualquier recurso `ibm_container_cluster` que no sea referenciado en el atributo `scope` de ningún recurso `ibm_ob_monitoring_config`. La vinculación se valida cuando el nombre o el CRN del clúster se incluye en el ámbito de la configuración de monitorización. + +## Casos de Fallo Detectados + +A continuación se describe el escenario que esta política detectará. + +--- + +### Caso Único: Configuración de Monitorización Ausente para un Clúster + +* **Descripción:** El clúster se aprovisiona sin los agentes o la vinculación necesaria para enviar métricas al servicio de monitorización centralizado de IBM Cloud. +* **Ejemplo de Código Terraform Problemático:** + ```terraform + resource "ibm_container_cluster" "cluster_unmonitored" { + name = "production-iks-cluster" + datacenter = "dal10" + machine_type = "b3c.4x16" + hardware = "shared" + # No existe un recurso ibm_ob_monitoring_config apuntando a este clúster + } + ``` +* **Ubicación de la Alerta:** La alerta señalará directamente al bloque de código del recurso `ibm_container_cluster` huérfano de monitorización. + +## Recursos Involucrados + +* `ibm_container_cluster` +* `ibm_ob_monitoring_config` + +## Solución + +Para solucionar esta alerta, debe existir un recurso `ibm_ob_monitoring_config` que conecte el clúster con una instancia de monitorización (basada en Sysdig). + +```terraform +resource "ibm_ob_monitoring_config" "monitoring_setup" { + # Relación directa con el clúster + scope = ibm_container_cluster.my_cluster.crn + instance = ibm_resource_instance.sysdig_instance.guid +} + +resource "ibm_resource_instance" "sysdig_instance" { + name = "cluster-metrics-aggregator" + service = "sysdig-monitor" + plan = "graduated-tier" + location = "us-south" +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_iks_cluster_monitoring_disabled/metadata.json b/assets/queries/terraform/ibm/ibm_iks_cluster_monitoring_disabled/metadata.json new file mode 100644 index 00000000000..5b72713ac11 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_iks_cluster_monitoring_disabled/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "51bac252-399d-49d0-bfa5-0bd52194fed9", + "queryName": "IKS Cluster Monitoring Disabled", + "severity": "MEDIUM", + "category": "Observability", + "descriptionText": "Ensures that every IBM Cloud Kubernetes Service (IKS) cluster has an associated monitoring service configuration. Monitoring is essential for observing cluster health, performance, and resource utilization.", + "descriptionUrl": "https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/ob_monitoring_config", + "platform": "Terraform", + "descriptionID": "51bac252", + "cloudProvider": "ibm", + "cwe": "CWE-778", + "riskScore": 5.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_iks_cluster_monitoring_disabled/query.rego b/assets/queries/terraform/ibm/ibm_iks_cluster_monitoring_disabled/query.rego new file mode 100644 index 00000000000..a0006913788 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_iks_cluster_monitoring_disabled/query.rego @@ -0,0 +1,22 @@ +package Cx + +# REGLA: Detectar clústeres de IKS sin configuración de monitorización asociada. +CxPolicy[result] { + doc := input.document[i] + _ := doc.resource.ibm_container_cluster[cluster_name] + + matching_configs := [config | + config := input.document[_].resource.ibm_ob_monitoring_config[_] + contains(config.scope, cluster_name) + ] + + count(matching_configs) == 0 + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.ibm_container_cluster.%s", [cluster_name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "ibm_container_cluster should have an associated ibm_ob_monitoring_config resource", + "keyActualValue": "ibm_container_cluster does not have an associated ibm_ob_monitoring_config resource", + } +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_iks_cluster_monitoring_disabled/test/negative1.tf b/assets/queries/terraform/ibm/ibm_iks_cluster_monitoring_disabled/test/negative1.tf new file mode 100644 index 00000000000..51f8af06e4f --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_iks_cluster_monitoring_disabled/test/negative1.tf @@ -0,0 +1,11 @@ +resource "ibm_container_cluster" "aks_with_monitoring" { + name = "visible-cluster" + datacenter = "dal10" + machine_type = "b3c.4x16" + hardware = "shared" +} + +resource "ibm_ob_monitoring_config" "monitoring_ok" { + scope = ibm_container_cluster.aks_with_monitoring.crn + instance = "crn:v1:bluemix:public:sysdig-monitor:..." +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_iks_cluster_monitoring_disabled/test/positive1.tf b/assets/queries/terraform/ibm/ibm_iks_cluster_monitoring_disabled/test/positive1.tf new file mode 100644 index 00000000000..7e884c04be2 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_iks_cluster_monitoring_disabled/test/positive1.tf @@ -0,0 +1,8 @@ +resource "ibm_container_cluster" "aks_no_monitoring" { + name = "blind-cluster" + datacenter = "dal10" + machine_type = "b3c.4x16" + hardware = "shared" + public_vlan_id = "vlan1" + private_vlan_id = "vlan2" +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_iks_cluster_monitoring_disabled/test/positive_expected_result.json b/assets/queries/terraform/ibm/ibm_iks_cluster_monitoring_disabled/test/positive_expected_result.json new file mode 100644 index 00000000000..86cd6bc3981 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_iks_cluster_monitoring_disabled/test/positive_expected_result.json @@ -0,0 +1,8 @@ +[ + { + "queryName": "IKS Cluster Monitoring Disabled", + "severity": "MEDIUM", + "line": 1, + "fileName": "positive1.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_instance_os_disk_encryption_manual/README.md b/assets/queries/terraform/ibm/ibm_instance_os_disk_encryption_manual/README.md new file mode 100644 index 00000000000..b0f3d5b5695 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_instance_os_disk_encryption_manual/README.md @@ -0,0 +1,51 @@ +# Regla KICS: IBM Instance OS Disk Encryption (Manual) + +## Descripción General + +Esta regla informativa (INFO) audita las instancias de servidor virtual (**VSI**) de la infraestructura VPC (`ibm_is_instance`) para verificar que el cifrado del disco de arranque esté bajo el control del cliente. + +Por defecto, IBM Cloud cifra el volumen de arranque (boot volume) de cada instancia utilizando claves gestionadas por el proveedor. Sin embargo, para cumplir con estándares de seguridad avanzados como **FIPS 140-2**, o normativas financieras y gubernamentales, se requiere el uso de **Customer Managed Keys (CMK)** o **Keep Your Own Key (KYOK)**. + +Al proporcionar el CRN de una clave de **Key Protect** o **Hyper Protect Crypto Services** en la configuración de la instancia, el cliente obtiene la capacidad de revocar el acceso a los datos del sistema operativo de forma instantánea. + +## Lógica de la Regla + +La política evalúa el recurso `ibm_is_instance` bajo dos escenarios: +1. **Ausencia de Configuración de Volumen:** Si el bloque `boot_volume` no está presente, la instancia se crea con parámetros por defecto (cifrado gestionado por IBM). +2. **Ausencia de Clave de Cifrado:** Si el bloque `boot_volume` existe pero el parámetro `encryption` no está definido, se considera que el volumen no está utilizando claves gestionadas por el cliente. + +## Casos de Fallo Detectados + +A continuación se describen los escenarios que esta política detectará. + +--- + +### Caso 1: Cifrado por Defecto (Bloque Ausente) +* **Descripción:** No se define configuración de disco de arranque, delegando el cifrado a IBM Cloud. +* **Ubicación de la Alerta:** Bloque del recurso `ibm_is_instance`. + +### Caso 2: Cifrado por Defecto (Atributo Ausente) +* **Descripción:** Se configura el volumen de arranque pero se omite la clave de cifrado. +* **Ubicación de la Alerta:** Atributo `boot_volume`. + +## Recurso Involucrado + +* `ibm_is_instance` + +## Solución + +Defina el bloque `boot_volume` e incluya el CRN de su clave raíz de Key Protect en el argumento `encryption`. + +```terraform +resource "ibm_is_instance" "secure_vsi" { + name = "secure-production-server" + image = "r006-723f851b-4043-42e5-9467-33a39e802375" + profile = "bx2-2x8" + + boot_volume { + name = "os-disk-secure" + encryption = "crn:v1:bluemix:public:kms:us-south:a/aaaa:bbbb:key:cccc" + } + + # ... otros parámetros de red y vpc ... +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_instance_os_disk_encryption_manual/metadata.json b/assets/queries/terraform/ibm/ibm_instance_os_disk_encryption_manual/metadata.json new file mode 100644 index 00000000000..3a961663e59 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_instance_os_disk_encryption_manual/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "86f1560c-74cb-48a3-ad5a-7da25cab4d03", + "queryName": "IBM Instance OS Disk Encryption (Manual)", + "severity": "INFO", + "category": "Encryption", + "descriptionText": "The Virtual Server Instance boot volume (OS Disk) relies on default provider-managed encryption. It is not configured with 'boot_volume.encryption', which is required for Customer Managed Keys (CMK), BYOK, or KYOK.", + "descriptionUrl": "https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/is_instance", + "platform": "Terraform", + "descriptionID": "86f1560c", + "cloudProvider": "ibm", + "cwe": "CWE-312", + "riskScore": 0.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_instance_os_disk_encryption_manual/query.rego b/assets/queries/terraform/ibm/ibm_instance_os_disk_encryption_manual/query.rego new file mode 100644 index 00000000000..0ad2c6fd738 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_instance_os_disk_encryption_manual/query.rego @@ -0,0 +1,39 @@ +package Cx + +ensure_array(x) = x { is_array(x) } +ensure_array(x) = [x] { not is_array(x) } + +# CASO 1: Bloque 'boot_volume' totalmente ausente. +CxPolicy[result] { + doc := input.document[i] + instance := doc.resource.ibm_is_instance[name] + + not instance.boot_volume + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.ibm_is_instance.%s", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "'boot_volume' block should be defined with 'encryption' set to a CRN", + "keyActualValue": "'boot_volume' block is missing (using default encryption)", + } +} + +# CASO 2: Bloque 'boot_volume' presente, pero falta 'encryption'. +CxPolicy[result] { + doc := input.document[i] + instance := doc.resource.ibm_is_instance[name] + + boot_volumes := ensure_array(instance.boot_volume) + boot_vol := boot_volumes[_] + + object.get(boot_vol, "encryption", "undefined") == "undefined" + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.ibm_is_instance.%s.boot_volume", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "'boot_volume.encryption' attribute should be defined with a Key Protect/HPCS CRN", + "keyActualValue": "'encryption' is missing in boot_volume (using default encryption)", + } +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_instance_os_disk_encryption_manual/test/negative1.tf b/assets/queries/terraform/ibm/ibm_instance_os_disk_encryption_manual/test/negative1.tf new file mode 100644 index 00000000000..7d0952a2787 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_instance_os_disk_encryption_manual/test/negative1.tf @@ -0,0 +1,10 @@ +resource "ibm_is_instance" "vsi_secure" { + name = "vsi-secure" + image = "r006-12345678" + profile = "bx2-2x8" + + boot_volume { + name = "boot-disk-cmk" + encryption = "crn:v1:bluemix:public:kms:us-south:a/test:test:key:test" + } +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_instance_os_disk_encryption_manual/test/positive1.tf b/assets/queries/terraform/ibm/ibm_instance_os_disk_encryption_manual/test/positive1.tf new file mode 100644 index 00000000000..717981e213b --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_instance_os_disk_encryption_manual/test/positive1.tf @@ -0,0 +1,8 @@ +resource "ibm_is_instance" "vsi_no_boot_block" { + name = "vsi-default" + image = "r006-12345678" + profile = "bx2-2x8" + vpc = "vpc-id" + zone = "us-south-1" + keys = ["key-id"] +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_instance_os_disk_encryption_manual/test/positive2.tf b/assets/queries/terraform/ibm/ibm_instance_os_disk_encryption_manual/test/positive2.tf new file mode 100644 index 00000000000..fdce8605450 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_instance_os_disk_encryption_manual/test/positive2.tf @@ -0,0 +1,10 @@ +resource "ibm_is_instance" "vsi_no_encryption" { + name = "vsi-unprotected-boot" + image = "r006-12345678" + profile = "bx2-2x8" + + boot_volume { + name = "boot-disk-standard" + # FALLO: Falta atributo encryption + } +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_instance_os_disk_encryption_manual/test/positive_expected_result.json b/assets/queries/terraform/ibm/ibm_instance_os_disk_encryption_manual/test/positive_expected_result.json new file mode 100644 index 00000000000..789fc08718a --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_instance_os_disk_encryption_manual/test/positive_expected_result.json @@ -0,0 +1,14 @@ +[ + { + "queryName": "IBM Instance OS Disk Encryption (Manual)", + "severity": "INFO", + "line": 1, + "fileName": "positive1.tf" + }, + { + "queryName": "IBM Instance OS Disk Encryption (Manual)", + "severity": "INFO", + "line": 6, + "fileName": "positive2.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_kms_key_rotation_disabled/README.md b/assets/queries/terraform/ibm/ibm_kms_key_rotation_disabled/README.md new file mode 100644 index 00000000000..743a0644829 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_kms_key_rotation_disabled/README.md @@ -0,0 +1,63 @@ +# Regla KICS: Rotación Automática de Claves en IBM Key Protect + +## Descripción General + +Esta regla de KICS para Terraform asegura que todas las claves gestionadas por el cliente (`ibm_kms_key`) en **IBM Key Protect** tengan una política de rotación automática habilitada y configurada correctamente. + +La rotación periódica de claves criptográficas es una práctica de seguridad fundamental que responde al principio de **Criptografía Ágil**. El objetivo principal es limitar el "radio de explosión" (blast radius): al rotar la clave periódicamente, se reduce drásticamente la cantidad de datos que podrían ser descifrados en el hipotético caso de que el material de una clave específica se viera comprometido. Además, forzar rotaciones regulares garantiza que los procesos operativos sean capaces de manejar el ciclo de vida de las claves sin depender de una única clave estática de duración indefinida. + +## Lógica de la Regla + +La política audita el recurso `ibm_kms_key` cubriendo tres escenarios de riesgo: +1. **Ausencia de Bloque:** Detecta si no se ha definido el bloque `rotation_policy`. +2. **Atributo Faltante:** Detecta si el bloque existe pero omite el intervalo de tiempo. +3. **Valor Inválido/Cero:** Detecta si el intervalo se ha configurado en `0`, lo cual desactiva funcionalmente la rotación. + +## Casos de Fallo Detectados + +A continuación se describen los tres escenarios que esta política detectará. + +--- + +### Caso 1: Bloque `rotation_policy` Ausente +* **Descripción:** El recurso de clave existe pero no contiene ninguna instrucción de rotación automática. +* **Ejemplo de Código Terraform:** + ```terraform + resource "ibm_kms_key" "key_static" { + instance_id = "instance-id" + key_name = "static-key" + standard_key = false + } + ``` +* **Ubicación de la Alerta:** Bloque del recurso `ibm_kms_key`. + +--- + +### Caso 2: Atributo `rotation_interval_month` Ausente +* **Descripción:** Se define la política pero no se especifica cada cuántos meses debe ocurrir la rotación. +* **Ubicación de la Alerta:** Bloque `rotation_policy`. + +--- + +### Caso 3: Intervalo de Rotación en Cero +* **Descripción:** El intervalo está configurado en `0`, lo que inhabilita la automatización del ciclo de vida. +* **Ubicación de la Alerta:** Atributo `rotation_interval_month`. + +## Recurso Involucrado + +* `ibm_kms_key` + +## Solución + +Defina un bloque `rotation_policy` con un intervalo válido de entre 1 y 12 meses. + +```terraform +resource "ibm_kms_key" "key_compliant" { + instance_id = ibm_resource_instance.kms_instance.guid + key_name = "secure-rotating-key" + standard_key = false + + rotation_policy { + rotation_interval_month = 6 # Rota cada semestre + } +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_kms_key_rotation_disabled/metadata.json b/assets/queries/terraform/ibm/ibm_kms_key_rotation_disabled/metadata.json new file mode 100644 index 00000000000..be6ef0a17ba --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_kms_key_rotation_disabled/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "cdaf415c-d1b9-4d15-8e6f-e27165b49d55", + "queryName": "KMS Key Rotation Disabled", + "severity": "HIGH", + "category": "Encryption", + "descriptionText": "Ensures that cryptographic keys in IBM Key Protect have an automated rotation policy enabled. Rotating keys periodically limits the amount of data exposed if a single key is compromised.", + "descriptionUrl": "https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/kms_key#rotation_policy", + "platform": "Terraform", + "descriptionID": "cdaf415c", + "cloudProvider": "ibm", + "cwe": "CWE-320", + "riskScore": 9.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_kms_key_rotation_disabled/query.rego b/assets/queries/terraform/ibm/ibm_kms_key_rotation_disabled/query.rego new file mode 100644 index 00000000000..1549c0c0b34 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_kms_key_rotation_disabled/query.rego @@ -0,0 +1,47 @@ +package Cx + +# REGLA 1: El bloque 'rotation_policy' está completamente ausente. +CxPolicy[result] { + key := input.document[i].resource.ibm_kms_key[key_name] + + not key.rotation_policy + + result := { + "documentId": input.document[i].id, + "searchKey": sprintf("resource.ibm_kms_key.%s", [key_name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "'rotation_policy' block should be present and configured", + "keyActualValue": "'rotation_policy' block is missing", + } +} + +# REGLA 2: El bloque 'rotation_policy' existe, pero le falta 'rotation_interval_month'. +CxPolicy[result] { + key := input.document[i].resource.ibm_kms_key[key_name] + + policy := key.rotation_policy + not policy.rotation_interval_month + + result := { + "documentId": input.document[i].id, + "searchKey": sprintf("resource.ibm_kms_key.%s.rotation_policy", [key_name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "'rotation_interval_month' should be defined within the rotation policy", + "keyActualValue": "'rotation_interval_month' is missing", + } +} + +# REGLA 3: 'rotation_interval_month' está explícitamente configurado como 0. +CxPolicy[result] { + key := input.document[i].resource.ibm_kms_key[key_name] + + key.rotation_policy.rotation_interval_month == 0 + + result := { + "documentId": input.document[i].id, + "searchKey": sprintf("resource.ibm_kms_key.%s.rotation_policy.rotation_interval_month", [key_name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "'rotation_interval_month' should be a value between 1 and 12", + "keyActualValue": "'rotation_interval_month' is set to 0, disabling rotation", + } +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_kms_key_rotation_disabled/test/negative1.tf b/assets/queries/terraform/ibm/ibm_kms_key_rotation_disabled/test/negative1.tf new file mode 100644 index 00000000000..a15ea09d425 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_kms_key_rotation_disabled/test/negative1.tf @@ -0,0 +1,9 @@ +resource "ibm_kms_key" "key_secure" { + instance_id = "guid-123" + key_name = "key-compliant" + standard_key = false + + rotation_policy { + rotation_interval_month = 12 + } +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_kms_key_rotation_disabled/test/positive1.tf b/assets/queries/terraform/ibm/ibm_kms_key_rotation_disabled/test/positive1.tf new file mode 100644 index 00000000000..25da7b1e36f --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_kms_key_rotation_disabled/test/positive1.tf @@ -0,0 +1,5 @@ +resource "ibm_kms_key" "key_no_policy" { + instance_id = "guid-123" + key_name = "key-fails-1" + standard_key = false +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_kms_key_rotation_disabled/test/positive2.tf b/assets/queries/terraform/ibm/ibm_kms_key_rotation_disabled/test/positive2.tf new file mode 100644 index 00000000000..0f08f497188 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_kms_key_rotation_disabled/test/positive2.tf @@ -0,0 +1,9 @@ +resource "ibm_kms_key" "key_empty_policy" { + instance_id = "guid-123" + key_name = "key-fails-2" + standard_key = false + + rotation_policy { + # Falta rotation_interval_month + } +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_kms_key_rotation_disabled/test/positive3.tf b/assets/queries/terraform/ibm/ibm_kms_key_rotation_disabled/test/positive3.tf new file mode 100644 index 00000000000..a7d95812dbf --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_kms_key_rotation_disabled/test/positive3.tf @@ -0,0 +1,9 @@ +resource "ibm_kms_key" "key_zero_policy" { + instance_id = "guid-123" + key_name = "key-fails-3" + standard_key = false + + rotation_policy { + rotation_interval_month = 0 + } +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_kms_key_rotation_disabled/test/positive_expected_result.json b/assets/queries/terraform/ibm/ibm_kms_key_rotation_disabled/test/positive_expected_result.json new file mode 100644 index 00000000000..c44e043a51f --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_kms_key_rotation_disabled/test/positive_expected_result.json @@ -0,0 +1,20 @@ +[ + { + "queryName": "KMS Key Rotation Disabled", + "severity": "HIGH", + "line": 1, + "fileName": "positive1.tf" + }, + { + "queryName": "KMS Key Rotation Disabled", + "severity": "HIGH", + "line": 6, + "fileName": "positive2.tf" + }, + { + "queryName": "KMS Key Rotation Disabled", + "severity": "HIGH", + "line": 7, + "fileName": "positive3.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_logdna_archiving_disabled/README.md b/assets/queries/terraform/ibm/ibm_logdna_archiving_disabled/README.md new file mode 100644 index 00000000000..8d1ae843581 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_logdna_archiving_disabled/README.md @@ -0,0 +1,53 @@ +# Regla KICS: Archivado Habilitado para IBM LogDNA + +## Descripción General + +Esta regla de KICS para Terraform asegura que cada instancia de **IBM LogDNA** (`ibm_logdna_instance`) tenga el archivado de eventos configurado mediante el recurso `ibm_logdna_archive`. + +La retención de logs a largo plazo es una exigencia crítica en marcos de cumplimiento como **PCI DSS**, **SOC 2**, o **HIPAA**. El archivado permite mover los registros de la capa de búsqueda activa a una capa de almacenamiento de bajo costo (IBM Cloud Object Storage), garantizando que los datos estén disponibles para auditorías o investigaciones forenses meses o años después de su generación, incluso si la instancia operativa de Log Analysis ha purgado los datos de su caché de búsqueda. + +## Lógica de la Regla + +La política realiza un escaneo de correlación cruzada en el proyecto de Terraform. Identifica cualquier recurso `ibm_logdna_instance` que no sea referenciado en el atributo `instance_id` de ningún recurso `ibm_logdna_archive`. El fallo se dispara ante la **omisión completa** del recurso de archivado, lo que implica un riesgo de pérdida definitiva de datos tras el periodo de retención del plan. + +## Casos de Fallo Detectados + +A continuación se describe el escenario que esta política detectará. + +--- + +### Caso Único: Recurso `ibm_logdna_archive` Ausente + +* **Descripción:** Se provisiona una instancia de logs para gestionar la actividad de la plataforma o aplicaciones, pero no se define un destino de persistencia para el archivado. +* **Ejemplo de Código Terraform Problemático:** + ```terraform + resource "ibm_logdna_instance" "my_logs" { + name = "prod-logs" + plan = "7-day" + target_resource_instance_id = ibm_resource_instance.logdna_inst.id + } + + # ERROR: Falta el recurso ibm_logdna_archive que apunte a 'my_logs' + ``` +* **Ubicación de la Alerta:** Bloque del recurso `ibm_logdna_instance` huérfano de archivado. + +## Recursos Involucrados + +* `ibm_logdna_instance` +* `ibm_logdna_archive` + +## Solución + +Asegúrese de vincular cada instancia de LogDNA con un recurso `ibm_logdna_archive` que apunte a un bucket de IBM Cloud Object Storage. + +```terraform +resource "ibm_logdna_archive" "archive_setup" { + instance_id = ibm_logdna_instance.my_logs.id + + cos { + api_key = var.ibmcloud_api_key + bucket = "my-archiving-bucket" + endpoint = "s3.private.us-south.cloud-object-storage.appdomain.cloud" + instance_crn = ibm_resource_instance.cos_inst.id + } +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_logdna_archiving_disabled/metadata.json b/assets/queries/terraform/ibm/ibm_logdna_archiving_disabled/metadata.json new file mode 100644 index 00000000000..5239adf0903 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_logdna_archiving_disabled/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "54c5b041-1902-4e19-b414-5473f1b349d2", + "queryName": "LogDNA Archiving Is Disabled", + "severity": "MEDIUM", + "category": "Observability", + "descriptionText": "Ensures that every IBM LogDNA instance has archiving configured through an 'ibm_logdna_archive' resource. Archiving is critical for long-term log retention, compliance, and forensic analysis.", + "descriptionUrl": "https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/logdna_archive", + "platform": "Terraform", + "descriptionID": "54c5b041", + "cloudProvider": "ibm", + "cwe": "CWE-223", + "riskScore": 5.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_logdna_archiving_disabled/query.rego b/assets/queries/terraform/ibm/ibm_logdna_archiving_disabled/query.rego new file mode 100644 index 00000000000..1a58771346c --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_logdna_archiving_disabled/query.rego @@ -0,0 +1,22 @@ +package Cx + +# REGLA: Detectar instancias de LogDNA que no tienen un recurso de archivado asociado. +CxPolicy[result] { + doc := input.document[i] + _ := doc.resource.ibm_logdna_instance[instance_name] + + matching_archives := [archive | + archive := input.document[_].resource.ibm_logdna_archive[_] + contains(archive.instance_id, instance_name) + ] + + count(matching_archives) == 0 + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.ibm_logdna_instance.%s", [instance_name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "ibm_logdna_instance should have an associated ibm_logdna_archive resource", + "keyActualValue": "ibm_logdna_instance does not have an associated ibm_logdna_archive resource", + } +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_logdna_archiving_disabled/test/negative1.tf b/assets/queries/terraform/ibm/ibm_logdna_archiving_disabled/test/negative1.tf new file mode 100644 index 00000000000..9fb2f0f9d87 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_logdna_archiving_disabled/test/negative1.tf @@ -0,0 +1,15 @@ +resource "ibm_logdna_instance" "logs_with_archive" { + name = "logs-with-archiver" + plan = "lite" + target_resource_instance_id = "crn:v1:bluemix:public:logdna:..." +} + +resource "ibm_logdna_archive" "archiver_ok" { + instance_id = ibm_logdna_instance.logs_with_archive.id + cos { + api_key = "secret" + bucket = "my-bucket" + endpoint = "s3.us-south.cloud-object-storage.appdomain.cloud" + instance_crn = "crn:v1:bluemix:public:cloud-object-storage:..." + } +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_logdna_archiving_disabled/test/positive1.tf b/assets/queries/terraform/ibm/ibm_logdna_archiving_disabled/test/positive1.tf new file mode 100644 index 00000000000..15d09051bac --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_logdna_archiving_disabled/test/positive1.tf @@ -0,0 +1,5 @@ +resource "ibm_logdna_instance" "logs_without_archive" { + name = "logs-only-test" + plan = "lite" + target_resource_instance_id = "crn:v1:bluemix:public:logdna:..." +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_logdna_archiving_disabled/test/positive_expected_result.json b/assets/queries/terraform/ibm/ibm_logdna_archiving_disabled/test/positive_expected_result.json new file mode 100644 index 00000000000..836d3c1acc4 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_logdna_archiving_disabled/test/positive_expected_result.json @@ -0,0 +1,8 @@ +[ + { + "queryName": "LogDNA Archiving Is Disabled", + "severity": "MEDIUM", + "line": 1, + "fileName": "positive1.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_logdna_view_without_alert/README.md b/assets/queries/terraform/ibm/ibm_logdna_view_without_alert/README.md new file mode 100644 index 00000000000..0277252afde --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_logdna_view_without_alert/README.md @@ -0,0 +1,51 @@ +# Regla KICS: Alertas Definidas para Vistas de IBM LogDNA + +## Descripción General + +Esta regla de severidad **BAJA** de KICS para Terraform asegura que cada vista personalizada de **IBM Log Analysis (LogDNA)** (`ibm_logdna_view`) tenga al menos una alerta (`ibm_logdna_alert`) configurada. + +Las vistas personalizadas se diseñan para filtrar y aislar eventos críticos, como errores de sistema, intentos de acceso fallidos o picos de tráfico. Sin embargo, la monitorización solo es efectiva si es proactiva. Si una vista existe pero carece de una alerta vinculada, el equipo de operaciones o seguridad depende de la revisión manual periódica de los logs, lo que aumenta drásticamente el tiempo de respuesta ante incidentes (MTTR). + +## Lógica de la Regla + +La política realiza una correlación cruzada dentro de los archivos de Terraform. Identifica cualquier recurso `ibm_logdna_view` cuyo nombre no sea referenciado en el atributo `view` de ningún recurso `ibm_logdna_alert`. La regla se activa ante la **omisión del recurso de alerta**, señalando que la vista no tiene un canal de notificación activo. + +## Casos de Fallo Detectados + +A continuación se describe el escenario que esta política detectará. + +--- + +### Caso Único: Vista de LogDNA sin Alerta Asociada + +* **Descripción:** Se define una vista específica para errores críticos o auditoría, pero no se inyecta un recurso de alerta para notificar a los responsables. +* **Ejemplo de Código Terraform Problemático:** + ```terraform + resource "ibm_logdna_view" "security_audit_view" { + name = "Security-Audit-Trail" + query = "app:iam-service action:login-failed" + # Error: No hay un recurso ibm_logdna_alert asociado a esta vista + } + ``` +* **Ubicación de la Alerta:** Bloque del recurso `ibm_logdna_view` huérfano de notificación. + +## Recursos Involucrados + +* `ibm_logdna_view` +* `ibm_logdna_alert` + +## Solución + +Asocie un recurso `ibm_logdna_alert` a la vista utilizando el nombre de la misma en el parámetro `view`. + +```terraform +resource "ibm_logdna_alert" "security_alert" { + name = "IAM-Failure-Alert" + view = ibm_logdna_view.security_audit_view.name + + notification_channel { + email { + recipients = ["security-ops@tu-empresa.com"] + } + } +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_logdna_view_without_alert/metadata.json b/assets/queries/terraform/ibm/ibm_logdna_view_without_alert/metadata.json new file mode 100644 index 00000000000..848cfc8ebee --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_logdna_view_without_alert/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "a5107e68-028f-4b4d-9fc0-f23e3b73e448", + "queryName": "LogDNA View Without Alert", + "severity": "LOW", + "category": "Observability", + "descriptionText": "Ensures that every IBM LogDNA custom view ('ibm_logdna_view') has at least one associated alert ('ibm_logdna_alert'). Views are created to filter critical events, and without an alert, notifications for these events will not be sent.", + "descriptionUrl": "https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/logdna_alert", + "platform": "Terraform", + "descriptionID": "a5107e68", + "cloudProvider": "ibm", + "cwe": "CWE-778", + "riskScore": 1.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_logdna_view_without_alert/query.rego b/assets/queries/terraform/ibm/ibm_logdna_view_without_alert/query.rego new file mode 100644 index 00000000000..b49190804fb --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_logdna_view_without_alert/query.rego @@ -0,0 +1,22 @@ +package Cx + +# REGLA: Detectar vistas de LogDNA que no tienen ninguna alerta asociada. +CxPolicy[result] { + doc := input.document[i] + view := doc.resource.ibm_logdna_view[view_name] + + matching_alerts := [alert | + alert := input.document[_].resource.ibm_logdna_alert[_] + contains(alert.view, view_name) + ] + + count(matching_alerts) == 0 + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.ibm_logdna_view.%s", [view_name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "ibm_logdna_view should have an associated ibm_logdna_alert", + "keyActualValue": "ibm_logdna_view does not have an associated ibm_logdna_alert", + } +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_logdna_view_without_alert/test/negative1.tf b/assets/queries/terraform/ibm/ibm_logdna_view_without_alert/test/negative1.tf new file mode 100644 index 00000000000..8518c7b77c2 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_logdna_view_without_alert/test/negative1.tf @@ -0,0 +1,15 @@ +resource "ibm_logdna_view" "audit_view" { + name = "Audit-Events" + query = "service:iam" +} + +resource "ibm_logdna_alert" "audit_alert" { + name = "Audit-Alert-Policy" + view = ibm_logdna_view.audit_view.name + + notification_channel { + email { + recipients = ["admin@example.com"] + } + } +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_logdna_view_without_alert/test/positive1.tf b/assets/queries/terraform/ibm/ibm_logdna_view_without_alert/test/positive1.tf new file mode 100644 index 00000000000..2dc3d3ec911 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_logdna_view_without_alert/test/positive1.tf @@ -0,0 +1,4 @@ +resource "ibm_logdna_view" "error_view" { + name = "Critical-Errors-Only" + query = "level:error" +} \ No newline at end of file diff --git a/assets/queries/terraform/ibm/ibm_logdna_view_without_alert/test/positive_expected_result.json b/assets/queries/terraform/ibm/ibm_logdna_view_without_alert/test/positive_expected_result.json new file mode 100644 index 00000000000..dfaed345f44 --- /dev/null +++ b/assets/queries/terraform/ibm/ibm_logdna_view_without_alert/test/positive_expected_result.json @@ -0,0 +1,8 @@ +[ + { + "queryName": "LogDNA View Without Alert", + "severity": "LOW", + "line": 1, + "fileName": "positive1.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_cloud_guard_problem_event_rule_missing/README.md b/assets/queries/terraform/oci/oci_cloud_guard_problem_event_rule_missing/README.md new file mode 100644 index 00000000000..7c3926dad45 --- /dev/null +++ b/assets/queries/terraform/oci/oci_cloud_guard_problem_event_rule_missing/README.md @@ -0,0 +1,95 @@ +# Regla KICS: Notificación para Problemas Detectados por Cloud Guard en OCI + +## Descripción General + +Esta regla de KICS para Terraform asegura que exista una regla de eventos (`oci_events_rule`) configurada para generar notificaciones cuando OCI Cloud Guard detecta un nuevo "problema". + +Habilitar Cloud Guard es solo el primer paso; su efectividad real depende de la capacidad del equipo de seguridad para responder a sus hallazgos. Si los problemas que Cloud Guard detecta no generan notificaciones automáticas, las alertas críticas pueden pasar desapercibidas durante un tiempo considerable, retrasando la mitigación de riesgos y aumentando la exposición. + +## Lógica de la Regla + +La política implementa una búsqueda **global** en todo el proyecto Terraform (analizando todos los archivos `.tf` simultáneamente). + +Inspecciona todos los recursos `oci_events_rule` definidos en la infraestructura para verificar si **al menos uno** de ellos cumple las siguientes condiciones: +1. Está habilitado (`is_enabled = true`). +2. Su condición de filtrado incluye el tipo de evento específico: `com.oraclecloud.cloudguard.problem`. + +Si no se encuentra ninguna regla que cumpla ambos requisitos en todo el proyecto, se genera una alerta. + +## Caso de Fallo Detectado + +A continuación se describe el escenario que esta política detectará. + +--- +### Caso Único: Regla de Eventos para Problemas de Cloud Guard Ausente o Mal Configurada + +* **Descripción:** Esta regla se activa si en toda la configuración del proyecto no se encuentra ningún recurso `oci_events_rule` habilitado que capture el evento de Cloud Guard. Esto incluye casos donde la regla no existe, o existe pero monitoriza eventos diferentes. +* **Ubicación de la Alerta:** La alerta se anclará al bloque `provider "oci" {}`, indicando que falta una configuración global de seguridad. + +* **Ejemplo de Código Terraform Problemático:** + ```terraform + # El proyecto puede tener otras reglas de eventos, pero ninguna para + # notificar los problemas detectados por Cloud Guard. + + resource "oci_events_rule" "example_rule_wrong_event" { + display_name = "rule-for-instance-events" + compartment_id = var.compartment_id + is_enabled = true + + # FALLO: Esta condición monitoriza el lanzamiento de instancias, + # pero ignora los problemas de seguridad de Cloud Guard. + condition = "{\"eventType\":[\"com.oraclecloud.compute.instance.launch.end\"]}" + + actions { + actions { + action_type = "ONS" + topic_id = oci_ons_notification_topic.test_topic.id + } + } + } + ``` + +## Recurso Involucrado + +* `oci_events_rule` + +## Solución + +Para solucionar el problema detectado, asegúrate de que tu configuración de Terraform incluya al menos un recurso `oci_events_rule` habilitado y configurado específicamente para el evento de problema de Cloud Guard. + +Se recomienda filtrar por niveles de riesgo (CRITICAL, HIGH) para evitar ruido, como se muestra a continuación: + +```terraform +# RECURSO REQUERIDO PARA LA SOLUCIÓN +resource "oci_events_rule" "cloud_guard_problem_rule" { + display_name = "notify-on-cloud-guard-problems" + description = "Sends a notification when Cloud Guard detects a new problem" + compartment_id = var.tenancy_ocid + is_enabled = true + + # La condición busca el evento específico y filtra por severidad. + condition = jsonencode({ + eventType = [ + "com.oraclecloud.cloudguard.problem" + ], + data = { + "riskLevel" = [ + "CRITICAL", + "HIGH" + ] + } + }) + + actions { + actions { + action_type = "ONS" + topic_id = oci_ons_notification_topic.security_alerts_topic.id + } + } +} + +# Recurso auxiliar: Tópico de notificación para enviar la alerta. +resource "oci_ons_notification_topic" "security_alerts_topic" { + compartment_id = var.compartment_id + name = "security-alerts-topic" +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_cloud_guard_problem_event_rule_missing/metadata.json b/assets/queries/terraform/oci/oci_cloud_guard_problem_event_rule_missing/metadata.json new file mode 100644 index 00000000000..30679dbec92 --- /dev/null +++ b/assets/queries/terraform/oci/oci_cloud_guard_problem_event_rule_missing/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "3ce366ab-e97d-4040-aa67-5a08dad595fb", + "queryName": "Event Rule for Cloud Guard Problems is Missing", + "severity": "MEDIUM", + "category": "Observability", + "descriptionText": "Ensures that an OCI event rule is configured to create notifications for problems detected by Cloud Guard. Without a notification rule, critical security findings from Cloud Guard may go unnoticed, delaying response to potential threats.", + "descriptionUrl": "https://registry.terraform.io/providers/oracle/oci/latest/docs/resources/events_rule", + "platform": "Terraform", + "descriptionID": "3ce366ab", + "cloudProvider": "oci", + "cwe": "CWE-778", + "riskScore": 3.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_cloud_guard_problem_event_rule_missing/query.rego b/assets/queries/terraform/oci/oci_cloud_guard_problem_event_rule_missing/query.rego new file mode 100644 index 00000000000..242b379619d --- /dev/null +++ b/assets/queries/terraform/oci/oci_cloud_guard_problem_event_rule_missing/query.rego @@ -0,0 +1,24 @@ +package Cx + +CxPolicy[result] { + doc := input.document[i] + _ := doc.provider.oci + + expected_event_type := "com.oraclecloud.cloudguard.problem" + + rules_with_correct_event := [rule | + rule := input.document[_].resource.oci_events_rule[_] + rule.is_enabled == true + contains(rule.condition, expected_event_type) + ] + + count(rules_with_correct_event) == 0 + + result := { + "documentId": doc.id, + "searchKey": "provider.oci", + "issueType": "MissingAttribute", + "keyExpectedValue": "An 'oci_events_rule' for Cloud Guard problems should exist in the project", + "keyActualValue": "No 'oci_events_rule' is configured to audit Cloud Guard problems", + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_cloud_guard_problem_event_rule_missing/test/negative1.tf b/assets/queries/terraform/oci/oci_cloud_guard_problem_event_rule_missing/test/negative1.tf new file mode 100644 index 00000000000..b418e886130 --- /dev/null +++ b/assets/queries/terraform/oci/oci_cloud_guard_problem_event_rule_missing/test/negative1.tf @@ -0,0 +1,17 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_events_rule" "cg_correct" { + display_name = "CloudGuardCorrect" + is_enabled = true + condition = "{\"eventType\":[\"com.oraclecloud.cloudguard.problem\"]}" + + actions { + actions { + action_type = "ONS" + is_enabled = true + topic_id = "ocid1.onstopic..." + } + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_cloud_guard_problem_event_rule_missing/test/positive1.tf b/assets/queries/terraform/oci/oci_cloud_guard_problem_event_rule_missing/test/positive1.tf new file mode 100644 index 00000000000..3a0b3bccec0 --- /dev/null +++ b/assets/queries/terraform/oci/oci_cloud_guard_problem_event_rule_missing/test/positive1.tf @@ -0,0 +1,9 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_core_instance" "example" { + availability_domain = "AD-1" + compartment_id = "ocid1.compartment..." + shape = "VM.Standard2.1" +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_cloud_guard_problem_event_rule_missing/test/positive2.tf b/assets/queries/terraform/oci/oci_cloud_guard_problem_event_rule_missing/test/positive2.tf new file mode 100644 index 00000000000..b0c092d9f6c --- /dev/null +++ b/assets/queries/terraform/oci/oci_cloud_guard_problem_event_rule_missing/test/positive2.tf @@ -0,0 +1,17 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_events_rule" "cg_disabled" { + display_name = "CloudGuardDisabled" + is_enabled = false + condition = "{\"eventType\":[\"com.oraclecloud.cloudguard.problem\"]}" + + actions { + actions { + action_type = "ONS" + is_enabled = true + topic_id = "ocid1.onstopic..." + } + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_cloud_guard_problem_event_rule_missing/test/positive3.tf b/assets/queries/terraform/oci/oci_cloud_guard_problem_event_rule_missing/test/positive3.tf new file mode 100644 index 00000000000..99de9217c22 --- /dev/null +++ b/assets/queries/terraform/oci/oci_cloud_guard_problem_event_rule_missing/test/positive3.tf @@ -0,0 +1,17 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_events_rule" "bucket_event" { + display_name = "BucketEvent" + is_enabled = true + condition = "{\"eventType\":[\"com.oraclecloud.objectstorage.createbucket\"]}" + + actions { + actions { + action_type = "ONS" + is_enabled = true + topic_id = "ocid1.onstopic..." + } + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_cloud_guard_problem_event_rule_missing/test/positive_expected_result.json b/assets/queries/terraform/oci/oci_cloud_guard_problem_event_rule_missing/test/positive_expected_result.json new file mode 100644 index 00000000000..0c87e86299f --- /dev/null +++ b/assets/queries/terraform/oci/oci_cloud_guard_problem_event_rule_missing/test/positive_expected_result.json @@ -0,0 +1,20 @@ +[ + { + "queryName": "Event Rule for Cloud Guard Problems is Missing", + "severity": "MEDIUM", + "line": 1, + "fileName": "positive1.tf" + }, + { + "queryName": "Event Rule for Cloud Guard Problems is Missing", + "severity": "MEDIUM", + "line": 1, + "fileName": "positive2.tf" + }, + { + "queryName": "Event Rule for Cloud Guard Problems is Missing", + "severity": "MEDIUM", + "line": 1, + "fileName": "positive3.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_cloud_guard_root_compartment_disabled/README.md b/assets/queries/terraform/oci/oci_cloud_guard_root_compartment_disabled/README.md new file mode 100644 index 00000000000..b9a7f33354c --- /dev/null +++ b/assets/queries/terraform/oci/oci_cloud_guard_root_compartment_disabled/README.md @@ -0,0 +1,76 @@ +# Regla KICS: Cloud Guard Habilitado en el Compartimento Raíz de OCI + +## Descripción General + +Esta regla de KICS para Terraform asegura que el servicio OCI Cloud Guard esté habilitado (`status = "ENABLED"`) y configurado para operar a nivel del compartimento raíz (*tenancy*). + +Cloud Guard es el servicio de gestión de la postura de seguridad (CSPM) nativo de OCI. Proporciona una visión centralizada de la seguridad, detecta configuraciones incorrectas y actividades anómalas, y puede automatizar la respuesta a problemas. Para obtener la máxima visibilidad y protección, IBM y Oracle recomiendan activarlo en el compartimento raíz, ya que sus políticas se heredan a todos los compartimentos hijos. + +## Lógica de la Regla + +La política se divide en tres reglas para cubrir todos los escenarios de mala configuración: +1. El recurso `oci_cloud_guard_configuration` no existe. +2. El recurso existe, pero su `status` no es `ENABLED`. +3. El recurso existe y está habilitado, pero no está asignado al compartimento raíz. + +## Casos de Fallo Detectados + +A continuación se describen los tres escenarios que esta política detectará. + +--- +### Caso 1: Recurso `oci_cloud_guard_configuration` Ausente + +* **Descripción:** Esta regla se activa si no existe ningún recurso `oci_cloud_guard_configuration` en toda la configuración, lo que significa que Cloud Guard no está siendo gestionado por Terraform. +* **Ejemplo de Código Terraform Problemático:** + ```terraform + # El proyecto no define ninguna configuración para Cloud Guard. + ``` +* **Ubicación de la Alerta:** La alerta se anclará al bloque `provider "oci" {}`. + +--- +### Caso 2: `status` de Cloud Guard no es `ENABLED` + +* **Descripción:** Esta regla detecta un recurso `oci_cloud_guard_configuration` que existe, pero cuyo `status` es diferente de `"ENABLED"` (por ejemplo, `"DISABLED"`). +* **Ejemplo de Código Terraform Problemático:** + ```terraform + resource "oci_cloud_guard_configuration" "cg_config_disabled" { + compartment_id = var.tenancy_ocid + reporting_region = "us-ashburn-1" + status = "DISABLED" # <-- ¡PROBLEMA! + } + ``` +* **Ubicación de la Alerta:** La alerta señalará directamente a la línea `status = "DISABLED"`. + +--- +### Caso 3: Cloud Guard no está en el Compartimento Raíz + +* **Descripción:** Esta regla detecta un recurso `oci_cloud_guard_configuration` que está habilitado (`status = "ENABLED"`), pero su `compartment_id` no es el del *tenancy* (raíz). La regla identifica esto de forma heurística, comprobando que el OCID del compartimento no contenga la palabra `tenancy`. +* **Ejemplo de Código Terraform Problemático:** + ```terraform + resource "oci_cloud_guard_configuration" "cg_config_wrong_compartment" { + # Apunta a un compartimento hijo en lugar del raíz. + compartment_id = var.child_compartment_ocid # <-- ¡PROBLEMA! + + reporting_region = "us-ashburn-1" + status = "ENABLED" + } + ``` +* **Ubicación de la Alerta:** La alerta señalará directamente a la línea `compartment_id`. + +## Recurso Involucrado + +* `oci_cloud_guard_configuration` + +## Solución + +Para solucionar los problemas detectados, asegúrate de que exista un único recurso `oci_cloud_guard_configuration` con el `status` en `"ENABLED"` y el `compartment_id` apuntando al OCID del *tenancy* (compartimento raíz). + +```terraform +resource "oci_cloud_guard_configuration" "cg_config_correct" { + # El compartment_id debe ser el del tenancy para una cobertura completa. + compartment_id = var.tenancy_ocid + + reporting_region = "us-ashburn-1" + status = "ENABLED" +} +``` \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_cloud_guard_root_compartment_disabled/metadata.json b/assets/queries/terraform/oci/oci_cloud_guard_root_compartment_disabled/metadata.json new file mode 100644 index 00000000000..2fe76f5d26d --- /dev/null +++ b/assets/queries/terraform/oci/oci_cloud_guard_root_compartment_disabled/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "3e784705-b3c6-4197-9a04-0b751b1a83d8", + "queryName": "Cloud Guard Not Enabled at Root Compartment", + "severity": "HIGH", + "category": "Security Services", + "descriptionText": "Ensures that OCI Cloud Guard is enabled at the tenancy's root compartment. Cloud Guard is a cloud-native security posture management service that helps monitor, identify, and maintain a strong security posture on Oracle Cloud.", + "descriptionUrl": "https://registry.terraform.io/providers/oracle/oci/latest/docs/resources/cloud_guard_configuration", + "platform": "Terraform", + "descriptionID": "3e784705", + "cloudProvider": "oci", + "cwe": "CWE-16", + "riskScore": 6.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_cloud_guard_root_compartment_disabled/query.rego b/assets/queries/terraform/oci/oci_cloud_guard_root_compartment_disabled/query.rego new file mode 100644 index 00000000000..50852570508 --- /dev/null +++ b/assets/queries/terraform/oci/oci_cloud_guard_root_compartment_disabled/query.rego @@ -0,0 +1,52 @@ +package Cx + +# REGLA 1: No existe ningún recurso 'oci_cloud_guard_configuration'. +CxPolicy[result] { + doc := input.document[i] + _ := doc.provider.oci + + all_cloud_guards := [cg | + cg := input.document[_].resource.oci_cloud_guard_configuration[_] + ] + + count(all_cloud_guards) == 0 + + result := { + "documentId": doc.id, + "searchKey": "provider.oci", + "issueType": "MissingAttribute", + "keyExpectedValue": "Resource 'oci_cloud_guard_configuration' should exist to enable Cloud Guard", + "keyActualValue": "Resource 'oci_cloud_guard_configuration' is missing", + } +} + +# REGLA 2: Cloud Guard existe, pero su 'status' no es 'ENABLED'. +CxPolicy[result] { + cg := input.document[i].resource.oci_cloud_guard_configuration[cg_name] + + cg.status != "ENABLED" + + result := { + "documentId": input.document[i].id, + "searchKey": sprintf("resource.oci_cloud_guard_configuration.%s.status", [cg_name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "'status' attribute should be 'ENABLED'", + "keyActualValue": sprintf("'status' attribute is '%s'", [cg.status]), + } +} + +# REGLA 3: Cloud Guard existe y está habilitado, pero no en el compartimento raíz. +CxPolicy[result] { + cg := input.document[i].resource.oci_cloud_guard_configuration[cg_name] + + cg.status == "ENABLED" + not contains(lower(cg.compartment_id), "tenancy") + + result := { + "documentId": input.document[i].id, + "searchKey": sprintf("resource.oci_cloud_guard_configuration.%s.compartment_id", [cg_name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "'compartment_id' should be the tenancy (root compartment) OCID", + "keyActualValue": "'compartment_id' is not the tenancy OCID", + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_cloud_guard_root_compartment_disabled/test/negative1.tf b/assets/queries/terraform/oci/oci_cloud_guard_root_compartment_disabled/test/negative1.tf new file mode 100644 index 00000000000..062b0c647e3 --- /dev/null +++ b/assets/queries/terraform/oci/oci_cloud_guard_root_compartment_disabled/test/negative1.tf @@ -0,0 +1,10 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_cloud_guard_configuration" "cg_correct" { + # CORRECTO: Contiene la palabra "tenancy" + compartment_id = var.tenancy_ocid + reporting_region = "us-ashburn-1" + status = "ENABLED" +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_cloud_guard_root_compartment_disabled/test/positive1.tf b/assets/queries/terraform/oci/oci_cloud_guard_root_compartment_disabled/test/positive1.tf new file mode 100644 index 00000000000..f4602b1ed95 --- /dev/null +++ b/assets/queries/terraform/oci/oci_cloud_guard_root_compartment_disabled/test/positive1.tf @@ -0,0 +1,9 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_identity_compartment" "example" { + compartment_id = "ocid1.tenancy..." + name = "example_compartment" + description = "Just a compartment" +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_cloud_guard_root_compartment_disabled/test/positive2.tf b/assets/queries/terraform/oci/oci_cloud_guard_root_compartment_disabled/test/positive2.tf new file mode 100644 index 00000000000..94e15af8b95 --- /dev/null +++ b/assets/queries/terraform/oci/oci_cloud_guard_root_compartment_disabled/test/positive2.tf @@ -0,0 +1,9 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_cloud_guard_configuration" "cg_disabled" { + compartment_id = var.tenancy_ocid + reporting_region = "us-ashburn-1" + status = "DISABLED" # FALLO +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_cloud_guard_root_compartment_disabled/test/positive3.tf b/assets/queries/terraform/oci/oci_cloud_guard_root_compartment_disabled/test/positive3.tf new file mode 100644 index 00000000000..ee69e810d6b --- /dev/null +++ b/assets/queries/terraform/oci/oci_cloud_guard_root_compartment_disabled/test/positive3.tf @@ -0,0 +1,10 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_cloud_guard_configuration" "cg_child" { + # FALLO: Apunta a un compartimento hijo, no al tenancy + compartment_id = var.child_compartment_id + reporting_region = "us-ashburn-1" + status = "ENABLED" +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_cloud_guard_root_compartment_disabled/test/positive_expected_result.json b/assets/queries/terraform/oci/oci_cloud_guard_root_compartment_disabled/test/positive_expected_result.json new file mode 100644 index 00000000000..6aa60561436 --- /dev/null +++ b/assets/queries/terraform/oci/oci_cloud_guard_root_compartment_disabled/test/positive_expected_result.json @@ -0,0 +1,20 @@ +[ + { + "queryName": "Cloud Guard Not Enabled at Root Compartment", + "severity": "HIGH", + "line": 1, + "fileName": "positive1.tf" + }, + { + "queryName": "Cloud Guard Not Enabled at Root Compartment", + "severity": "HIGH", + "line": 8, + "fileName": "positive2.tf" + }, + { + "queryName": "Cloud Guard Not Enabled at Root Compartment", + "severity": "HIGH", + "line": 7, + "fileName": "positive3.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_compute_legacy_metadata_enabled/README.md b/assets/queries/terraform/oci/oci_compute_legacy_metadata_enabled/README.md new file mode 100644 index 00000000000..1755079010f --- /dev/null +++ b/assets/queries/terraform/oci/oci_compute_legacy_metadata_enabled/README.md @@ -0,0 +1,71 @@ +# Regla KICS: Endpoints de Metadatos Legacy Deshabilitados en Instancias de OCI + +## Descripción General + +Esta regla de KICS para Terraform asegura que todas las instancias de cómputo de OCI (`oci_core_instance`) tengan los endpoints del servicio de metadatos legacy (IMDSv1) deshabilitados. + +El servicio de metadatos de instancia (IMDS) permite a una instancia obtener información sobre sí misma. La versión 1 (legacy) es vulnerable a ataques de tipo SSRF (Server-Side Request Forgery). La versión 2 (IMDSv2) introduce protecciones que mitigan este riesgo. Deshabilitar los endpoints legacy y forzar el uso de IMDSv2 es una práctica de seguridad fundamental para proteger las instancias. + +## Lógica de la Regla + +La política verifica la presencia y configuración del atributo `are_legacy_imds_endpoints_disabled` dentro del bloque `agent_config`. + +## Casos de Fallo Detectados + +A continuación se describen los tres escenarios que esta política detectará. + +--- +### Caso 1: Bloque `agent_config` Ausente + +* **Descripción:** El recurso `oci_core_instance` no define el bloque `agent_config`. Por defecto, esto implica que la configuración de seguridad no está aplicada. +* **Ejemplo:** + ```terraform + resource "oci_core_instance" "test" { + # Falta el bloque agent_config + } + ``` +* **Ubicación de la Alerta:** Bloque del recurso `oci_core_instance`. + +--- +### Caso 2: Atributo Ausente en `agent_config` + +* **Descripción:** El bloque `agent_config` existe, pero no contiene el atributo `are_legacy_imds_endpoints_disabled`. El valor por defecto es `false` (inseguro). +* **Ejemplo:** + ```terraform + resource "oci_core_instance" "test" { + agent_config { + # Falta el atributo are_legacy_imds_endpoints_disabled + is_monitoring_disabled = false + } + } + ``` +* **Ubicación de la Alerta:** Bloque `agent_config`. + +--- +### Caso 3: Atributo Configurado como `false` + +* **Descripción:** El atributo `are_legacy_imds_endpoints_disabled` está presente pero explícitamente configurado como `false`, habilitando los endpoints legacy inseguros. +* **Ejemplo:** + ```terraform + resource "oci_core_instance" "test" { + agent_config { + are_legacy_imds_endpoints_disabled = false # <-- ¡PROBLEMA! + } + } + ``` +* **Ubicación de la Alerta:** Línea del atributo `are_legacy_imds_endpoints_disabled`. + +## Recurso Involucrado + +* `oci_core_instance` + +## Solución + +```terraform +resource "oci_core_instance" "test_instance_correct" { + # ... otros atributos ... + + agent_config { + are_legacy_imds_endpoints_disabled = true + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_compute_legacy_metadata_enabled/metadata.json b/assets/queries/terraform/oci/oci_compute_legacy_metadata_enabled/metadata.json new file mode 100644 index 00000000000..096fbcb75b1 --- /dev/null +++ b/assets/queries/terraform/oci/oci_compute_legacy_metadata_enabled/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "96b464dc-a778-4b99-8634-c9eb9bdecf89", + "queryName": "Compute Instance Legacy Metadata Enabled", + "severity": "MEDIUM", + "category": "Insecure Configurations", + "descriptionText": "Ensures that OCI Compute Instances have legacy metadata service endpoints (IMDSv1) disabled. Enforcing the use of IMDSv2 is a security best practice to mitigate Server-Side Request Forgery (SSRF) vulnerabilities.", + "descriptionUrl": "https://registry.terraform.io/providers/oracle/oci/latest/docs/resources/core_instance#are_legacy_imds_endpoints_disabled", + "platform": "Terraform", + "descriptionID": "96b464dc", + "cloudProvider": "oci", + "cwe": "CWE-918", + "riskScore": 3.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_compute_legacy_metadata_enabled/query.rego b/assets/queries/terraform/oci/oci_compute_legacy_metadata_enabled/query.rego new file mode 100644 index 00000000000..6810f9c2967 --- /dev/null +++ b/assets/queries/terraform/oci/oci_compute_legacy_metadata_enabled/query.rego @@ -0,0 +1,48 @@ +package Cx + +# REGLA 1: Falta el bloque 'agent_config' por completo. +CxPolicy[result] { + instance := input.document[i].resource.oci_core_instance[instance_name] + + not instance.agent_config + + result := { + "documentId": input.document[i].id, + "searchKey": sprintf("resource.oci_core_instance.%s", [instance_name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "'agent_config' block should be defined", + "keyActualValue": "'agent_config' block is missing", + } +} + +# REGLA 2: Existe 'agent_config', pero falta el atributo dentro. +CxPolicy[result] { + instance := input.document[i].resource.oci_core_instance[instance_name] + agent_config := instance.agent_config + + object.get(agent_config, "are_legacy_imds_endpoints_disabled", null) == null + + result := { + "documentId": input.document[i].id, + # AQUI ESTA EL CAMBIO: Apuntamos al bloque agent_config + "searchKey": sprintf("resource.oci_core_instance.%s.agent_config", [instance_name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "'are_legacy_imds_endpoints_disabled' should be present and set to 'true'", + "keyActualValue": "'are_legacy_imds_endpoints_disabled' is missing inside 'agent_config'", + } +} + +# REGLA 3: El atributo existe pero es 'false'. +CxPolicy[result] { + instance := input.document[i].resource.oci_core_instance[instance_name] + + instance.agent_config.are_legacy_imds_endpoints_disabled == false + + result := { + "documentId": input.document[i].id, + "searchKey": sprintf("resource.oci_core_instance.%s.agent_config.are_legacy_imds_endpoints_disabled", [instance_name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "'are_legacy_imds_endpoints_disabled' attribute should be 'true'", + "keyActualValue": "'are_legacy_imds_endpoints_disabled' attribute is 'false'", + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_compute_legacy_metadata_enabled/test/negative1.tf b/assets/queries/terraform/oci/oci_compute_legacy_metadata_enabled/test/negative1.tf new file mode 100644 index 00000000000..548be50c429 --- /dev/null +++ b/assets/queries/terraform/oci/oci_compute_legacy_metadata_enabled/test/negative1.tf @@ -0,0 +1,10 @@ +resource "oci_core_instance" "negative1" { + availability_domain = "AD-1" + compartment_id = "ocid1.compartment..." + shape = "VM.Standard2.1" + + agent_config { + # CORRECTO + are_legacy_imds_endpoints_disabled = true + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_compute_legacy_metadata_enabled/test/positive1.tf b/assets/queries/terraform/oci/oci_compute_legacy_metadata_enabled/test/positive1.tf new file mode 100644 index 00000000000..0eb1571db93 --- /dev/null +++ b/assets/queries/terraform/oci/oci_compute_legacy_metadata_enabled/test/positive1.tf @@ -0,0 +1,6 @@ +resource "oci_core_instance" "positive1" { + availability_domain = "AD-1" + compartment_id = "ocid1.compartment..." + shape = "VM.Standard2.1" + # FALLO: Falta agent_config (Caso 1) +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_compute_legacy_metadata_enabled/test/positive2.tf b/assets/queries/terraform/oci/oci_compute_legacy_metadata_enabled/test/positive2.tf new file mode 100644 index 00000000000..2cbe5ac1ad0 --- /dev/null +++ b/assets/queries/terraform/oci/oci_compute_legacy_metadata_enabled/test/positive2.tf @@ -0,0 +1,10 @@ +resource "oci_core_instance" "positive2" { + availability_domain = "AD-1" + compartment_id = "ocid1.compartment..." + shape = "VM.Standard2.1" + + agent_config { + # FALLO: Falta atributo (Caso 2) + is_monitoring_disabled = false + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_compute_legacy_metadata_enabled/test/positive3.tf b/assets/queries/terraform/oci/oci_compute_legacy_metadata_enabled/test/positive3.tf new file mode 100644 index 00000000000..e1e52522238 --- /dev/null +++ b/assets/queries/terraform/oci/oci_compute_legacy_metadata_enabled/test/positive3.tf @@ -0,0 +1,10 @@ +resource "oci_core_instance" "positive3" { + availability_domain = "AD-1" + compartment_id = "ocid1.compartment..." + shape = "VM.Standard2.1" + + agent_config { + # FALLO: Valor incorrecto (Caso 3) + are_legacy_imds_endpoints_disabled = false + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_compute_legacy_metadata_enabled/test/positive_expected_result.json b/assets/queries/terraform/oci/oci_compute_legacy_metadata_enabled/test/positive_expected_result.json new file mode 100644 index 00000000000..5ec1f031f8e --- /dev/null +++ b/assets/queries/terraform/oci/oci_compute_legacy_metadata_enabled/test/positive_expected_result.json @@ -0,0 +1,20 @@ +[ + { + "queryName": "Compute Instance Legacy Metadata Enabled", + "severity": "MEDIUM", + "line": 1, + "fileName": "positive1.tf" + }, + { + "queryName": "Compute Instance Legacy Metadata Enabled", + "severity": "MEDIUM", + "line": 6, + "fileName": "positive2.tf" + }, + { + "queryName": "Compute Instance Legacy Metadata Enabled", + "severity": "MEDIUM", + "line": 8, + "fileName": "positive3.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_compute_secure_boot_disabled/README.md b/assets/queries/terraform/oci/oci_compute_secure_boot_disabled/README.md new file mode 100644 index 00000000000..5ed4f78d123 --- /dev/null +++ b/assets/queries/terraform/oci/oci_compute_secure_boot_disabled/README.md @@ -0,0 +1,73 @@ +# Regla KICS: Secure Boot Habilitado en Instancias de Cómputo de OCI + +## Descripción General + +Esta regla de KICS para Terraform asegura que todas las instancias de cómputo de OCI (`oci_core_instance`) que lo soporten tengan la funcionalidad de "Arranque Seguro" (Secure Boot) habilitada. + +Secure Boot es un estándar de seguridad desarrollado por la industria para asegurar que un dispositivo arranque utilizando únicamente software de confianza del fabricante del equipo original (OEM). Cuando el PC arranca, el firmware comprueba la firma de cada pieza de software de arranque, incluido el sistema operativo. Si las firmas son válidas, el PC arranca y el firmware cede el control al sistema operativo. Habilitarlo protege contra malware a nivel de arranque como los rootkits. + +## Lógica de la Regla + +La política verifica la presencia y configuración del atributo `is_secure_boot_enabled` dentro del bloque `shape_config`. + +## Casos de Fallo Detectados + +A continuación se describen los tres escenarios que esta política detectará. + +--- +### Caso 1: Bloque `shape_config` Ausente + +* **Descripción:** El recurso `oci_core_instance` no define el bloque `shape_config`. Por defecto, esto implica que Secure Boot está deshabilitado. +* **Ejemplo:** + ```terraform + resource "oci_core_instance" "test" { + # Falta el bloque shape_config + } + ``` +* **Ubicación de la Alerta:** Bloque del recurso `oci_core_instance`. + +--- +### Caso 2: Atributo Ausente en `shape_config` + +* **Descripción:** El bloque `shape_config` existe, pero no contiene el atributo `is_secure_boot_enabled`. El valor por defecto es `false`. +* **Ejemplo:** + ```terraform + resource "oci_core_instance" "test" { + shape_config { + # Falta is_secure_boot_enabled + ocpus = 1 + } + } + ``` +* **Ubicación de la Alerta:** Bloque `shape_config`. + +--- +### Caso 3: Atributo Configurado como `false` + +* **Descripción:** El atributo `is_secure_boot_enabled` está presente pero explícitamente configurado como `false`. +* **Ejemplo:** + ```terraform + resource "oci_core_instance" "test" { + shape_config { + is_secure_boot_enabled = false # <-- ¡PROBLEMA! + } + } + ``` +* **Ubicación de la Alerta:** Línea del atributo `is_secure_boot_enabled`. + +## Recurso Involucrado + +* `oci_core_instance` + +## Solución + +Para solucionar los problemas detectados, asegúrate de que cada recurso `oci_core_instance` incluya un bloque `shape_config` con el atributo `is_secure_boot_enabled` configurado en `true`. + +```terraform +resource "oci_core_instance" "test_instance_correct" { + # ... otros atributos ... + + shape_config { + is_secure_boot_enabled = true + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_compute_secure_boot_disabled/metadata.json b/assets/queries/terraform/oci/oci_compute_secure_boot_disabled/metadata.json new file mode 100644 index 00000000000..cf5f14dbb12 --- /dev/null +++ b/assets/queries/terraform/oci/oci_compute_secure_boot_disabled/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "1f24d72d-c8be-403f-92b8-84ed910fe96e", + "queryName": "Compute Instance Secure Boot Disabled", + "severity": "MEDIUM", + "category": "Insecure Configurations", + "descriptionText": "Ensures that OCI Compute Instances have Secure Boot enabled. Secure Boot is a feature of UEFI firmware that helps prevent unauthorized boot loaders and operating systems from loading during the startup process, protecting against rootkits and boot-level malware.", + "descriptionUrl": "https://registry.terraform.io/providers/oracle/oci/latest/docs/resources/core_instance#is_secure_boot_enabled", + "platform": "Terraform", + "descriptionID": "1f24d72d", + "cloudProvider": "oci", + "cwe": "CWE-427", + "riskScore": 3.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_compute_secure_boot_disabled/query.rego b/assets/queries/terraform/oci/oci_compute_secure_boot_disabled/query.rego new file mode 100644 index 00000000000..1f132489d88 --- /dev/null +++ b/assets/queries/terraform/oci/oci_compute_secure_boot_disabled/query.rego @@ -0,0 +1,48 @@ +package Cx + +# REGLA 1: Falta el bloque 'shape_config' por completo. +CxPolicy[result] { + instance := input.document[i].resource.oci_core_instance[instance_name] + + not instance.shape_config + + result := { + "documentId": input.document[i].id, + "searchKey": sprintf("resource.oci_core_instance.%s", [instance_name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "'shape_config' block should be defined", + "keyActualValue": "'shape_config' block is missing", + } +} + +# REGLA 2: Existe 'shape_config', pero falta el atributo dentro. +CxPolicy[result] { + instance := input.document[i].resource.oci_core_instance[instance_name] + shape_config := instance.shape_config + + object.get(shape_config, "is_secure_boot_enabled", null) == null + + result := { + "documentId": input.document[i].id, + # Apuntamos al bloque shape_config + "searchKey": sprintf("resource.oci_core_instance.%s.shape_config", [instance_name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "'is_secure_boot_enabled' should be present inside 'shape_config' and set to 'true'", + "keyActualValue": "'is_secure_boot_enabled' is missing inside 'shape_config'", + } +} + +# REGLA 3: El atributo existe pero es 'false'. +CxPolicy[result] { + instance := input.document[i].resource.oci_core_instance[instance_name] + + instance.shape_config.is_secure_boot_enabled == false + + result := { + "documentId": input.document[i].id, + "searchKey": sprintf("resource.oci_core_instance.%s.shape_config.is_secure_boot_enabled", [instance_name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "'is_secure_boot_enabled' attribute should be 'true'", + "keyActualValue": "'is_secure_boot_enabled' attribute is 'false'", + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_compute_secure_boot_disabled/test/negative1.tf b/assets/queries/terraform/oci/oci_compute_secure_boot_disabled/test/negative1.tf new file mode 100644 index 00000000000..744a095c1e3 --- /dev/null +++ b/assets/queries/terraform/oci/oci_compute_secure_boot_disabled/test/negative1.tf @@ -0,0 +1,10 @@ +resource "oci_core_instance" "negative1" { + availability_domain = "AD-1" + compartment_id = "ocid1.compartment..." + shape = "VM.Standard2.1" + + shape_config { + # CORRECTO + is_secure_boot_enabled = true + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_compute_secure_boot_disabled/test/positive1.tf b/assets/queries/terraform/oci/oci_compute_secure_boot_disabled/test/positive1.tf new file mode 100644 index 00000000000..e13edbd871a --- /dev/null +++ b/assets/queries/terraform/oci/oci_compute_secure_boot_disabled/test/positive1.tf @@ -0,0 +1,6 @@ +resource "oci_core_instance" "positive1" { + availability_domain = "AD-1" + compartment_id = "ocid1.compartment..." + shape = "VM.Standard2.1" + # FALLO: Falta shape_config (Caso 1) +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_compute_secure_boot_disabled/test/positive2.tf b/assets/queries/terraform/oci/oci_compute_secure_boot_disabled/test/positive2.tf new file mode 100644 index 00000000000..d2385663e68 --- /dev/null +++ b/assets/queries/terraform/oci/oci_compute_secure_boot_disabled/test/positive2.tf @@ -0,0 +1,10 @@ +resource "oci_core_instance" "positive2" { + availability_domain = "AD-1" + compartment_id = "ocid1.compartment..." + shape = "VM.Standard2.1" + + shape_config { + # FALLO: Falta is_secure_boot_enabled (Caso 2) + ocpus = 1 + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_compute_secure_boot_disabled/test/positive3.tf b/assets/queries/terraform/oci/oci_compute_secure_boot_disabled/test/positive3.tf new file mode 100644 index 00000000000..b16528bffe7 --- /dev/null +++ b/assets/queries/terraform/oci/oci_compute_secure_boot_disabled/test/positive3.tf @@ -0,0 +1,10 @@ +resource "oci_core_instance" "positive3" { + availability_domain = "AD-1" + compartment_id = "ocid1.compartment..." + shape = "VM.Standard2.1" + + shape_config { + # FALLO: Configurado a false (Caso 3) + is_secure_boot_enabled = false + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_compute_secure_boot_disabled/test/positive_expected_result.json b/assets/queries/terraform/oci/oci_compute_secure_boot_disabled/test/positive_expected_result.json new file mode 100644 index 00000000000..27215cd5562 --- /dev/null +++ b/assets/queries/terraform/oci/oci_compute_secure_boot_disabled/test/positive_expected_result.json @@ -0,0 +1,20 @@ +[ + { + "queryName": "Compute Instance Secure Boot Disabled", + "severity": "MEDIUM", + "line": 1, + "fileName": "positive1.tf" + }, + { + "queryName": "Compute Instance Secure Boot Disabled", + "severity": "MEDIUM", + "line": 6, + "fileName": "positive2.tf" + }, + { + "queryName": "Compute Instance Secure Boot Disabled", + "severity": "MEDIUM", + "line": 8, + "fileName": "positive3.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_default_tags_not_defined/README.md b/assets/queries/terraform/oci/oci_default_tags_not_defined/README.md new file mode 100644 index 00000000000..5d457d791fe --- /dev/null +++ b/assets/queries/terraform/oci/oci_default_tags_not_defined/README.md @@ -0,0 +1,69 @@ +# Regla KICS: Tags por Defecto Definidos en OCI + +## Descripción General + +Esta regla de KICS para Terraform verifica que se haya definido una política de etiquetado por defecto en la configuración de OCI. La gestión de esta política se realiza a través del recurso `oci_identity_tag_default`. + +El uso de tags por defecto es una práctica de gobernanza fundamental en la nube. Permite asegurar que todos los recursos creados dentro de un compartimento específico reciban automáticamente un conjunto predefinido de etiquetas. Esto es esencial para la gestión de costes, la automatización de procesos, el control de acceso y la auditoría. + +## Lógica de la Regla + +La política implementa una única regla para verificar la existencia de al menos un recurso `oci_identity_tag_default` en toda la configuración de Terraform. Si no se encuentra ninguno, se asume que no se está gestionando una política de etiquetado por defecto. + +## Caso de Fallo Detectado + +A continuación se describe el escenario que esta política detectará. + +--- +### Caso Único: Recurso `oci_identity_tag_default` Ausente + +* **Descripción:** Esta regla se activa si en toda la configuración de Terraform no se encuentra ni un solo recurso de tipo `oci_identity_tag_default`. +* **Ejemplo de Código Terraform Problemático:** + ```terraform + # El proyecto contiene varios recursos de OCI, pero ninguno define + # una política de tags por defecto. + + resource "oci_core_vcn" "example_vcn" { + compartment_id = var.compartment_id + # ... + } + ``` +* **Ubicación de la Alerta:** La alerta será general y se anclará al bloque `provider "oci" {}` para indicar que falta un recurso `oci_identity_tag_default` en la configuración global. + +## Recurso Involucrado + +* `oci_identity_tag_default` + +## Solución + +Para solucionar el problema detectado, asegúrate de que tu configuración de Terraform incluya al menos un recurso `oci_identity_tag_default`. Este recurso requiere que primero se defina un `oci_identity_tag_namespace` y un `oci_identity_tag`. + +```terraform +# Es necesario definir un espacio de nombres para los tags. +resource "oci_identity_tag_namespace" "example_ns" { + compartment_id = var.compartment_id + description = "Namespace for default tags" + name = "example-namespace" +} + +# Es necesario definir el tag que se usará por defecto. +resource "oci_identity_tag" "example_tag" { + tag_namespace_id = oci_identity_tag_namespace.example_ns.id + description = "Tag for cost center" + name = "CostCenter" +} + +# RECURSO REQUERIDO PARA LA SOLUCIÓN +resource "oci_identity_tag_default" "example_default_tag" { + compartment_id = var.compartment_id + + # ID del tag que se aplicará por defecto. + tag_definition_id = oci_identity_tag.example_tag.id + + # Valor que se aplicará por defecto. + value = "IT-DEPT-123" + + # Asegura que los usuarios no puedan sobreescribir este valor por defecto. + is_required = true +} +``` \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_default_tags_not_defined/metadata.json b/assets/queries/terraform/oci/oci_default_tags_not_defined/metadata.json new file mode 100644 index 00000000000..6fc5306fe52 --- /dev/null +++ b/assets/queries/terraform/oci/oci_default_tags_not_defined/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "4c294256-f477-41d5-8c7f-f31c9e48092e", + "queryName": "Default Tags Not Defined", + "severity": "LOW", + "category": "Best Practices", + "descriptionText": "Ensures that a default tagging policy is defined using the 'oci_identity_tag_default' resource. Default tags are crucial for governance as they ensure all resources within a compartment are automatically tagged for cost tracking, automation, and access control.", + "descriptionUrl": "https://registry.terraform.io/providers/oracle/oci/latest/docs/resources/identity_tag_default", + "platform": "Terraform", + "descriptionID": "4c294256", + "cloudProvider": "oci", + "cwe": "CWE-16", + "riskScore": 1.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_default_tags_not_defined/query.rego b/assets/queries/terraform/oci/oci_default_tags_not_defined/query.rego new file mode 100644 index 00000000000..7d02815c15c --- /dev/null +++ b/assets/queries/terraform/oci/oci_default_tags_not_defined/query.rego @@ -0,0 +1,20 @@ +package Cx + +CxPolicy[result] { + doc := input.document[i] + _ := doc.provider.oci + + all_default_tags := [tag | + tag := input.document[_].resource.oci_identity_tag_default[_] + ] + + count(all_default_tags) == 0 + + result := { + "documentId": doc.id, + "searchKey": "provider.oci", + "issueType": "MissingAttribute", + "keyExpectedValue": "At least one 'oci_identity_tag_default' resource should exist to define a default tagging policy", + "keyActualValue": "No 'oci_identity_tag_default' resource was found in the configuration", + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_default_tags_not_defined/test/negative1.tf b/assets/queries/terraform/oci/oci_default_tags_not_defined/test/negative1.tf new file mode 100644 index 00000000000..4962573eb83 --- /dev/null +++ b/assets/queries/terraform/oci/oci_default_tags_not_defined/test/negative1.tf @@ -0,0 +1,22 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_identity_tag_namespace" "example_ns" { + compartment_id = "ocid1.compartment..." + description = "Namespace" + name = "example-namespace" +} + +resource "oci_identity_tag" "example_tag" { + tag_namespace_id = oci_identity_tag_namespace.example_ns.id + description = "Tag" + name = "CostCenter" +} + +resource "oci_identity_tag_default" "example_default_tag" { + compartment_id = "ocid1.compartment..." + tag_definition_id = oci_identity_tag.example_tag.id + value = "IT-DEPT-123" + is_required = true +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_default_tags_not_defined/test/positive1.tf b/assets/queries/terraform/oci/oci_default_tags_not_defined/test/positive1.tf new file mode 100644 index 00000000000..df88a71ceed --- /dev/null +++ b/assets/queries/terraform/oci/oci_default_tags_not_defined/test/positive1.tf @@ -0,0 +1,8 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_core_vcn" "simple_vcn" { + compartment_id = "ocid1.compartment..." + cidr_block = "10.0.0.0/16" +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_default_tags_not_defined/test/positive_expected_result.json b/assets/queries/terraform/oci/oci_default_tags_not_defined/test/positive_expected_result.json new file mode 100644 index 00000000000..cba73e9f9be --- /dev/null +++ b/assets/queries/terraform/oci/oci_default_tags_not_defined/test/positive_expected_result.json @@ -0,0 +1,8 @@ +[ + { + "queryName": "Default Tags Not Defined", + "severity": "LOW", + "line": 1, + "fileName": "positive1.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_iam_group_change_event_rule_missing/README.md b/assets/queries/terraform/oci/oci_iam_group_change_event_rule_missing/README.md new file mode 100644 index 00000000000..76f87609c67 --- /dev/null +++ b/assets/queries/terraform/oci/oci_iam_group_change_event_rule_missing/README.md @@ -0,0 +1,122 @@ +# Regla KICS: Notificación para Cambios en Grupos de IAM en OCI + +## Descripción General + +Esta regla de KICS para Terraform asegura que exista una regla de eventos (`oci_events_rule`) configurada para monitorizar y generar alertas ante cualquier cambio (creación, actualización o eliminación) en los grupos de IAM de la cuenta de OCI. + +Los grupos de IAM son la base de la gestión de permisos en OCI. Un atacante o un actor interno malicioso podría crear un nuevo grupo con altos privilegios, añadir usuarios a un grupo de administradores, o eliminar un grupo existente para causar una denegación de servicio. Monitorizar estos cambios en tiempo real es una medida de seguridad esencial para detectar escaladas de privilegios no autorizadas o manipulaciones en los controles de acceso. + +## Lógica de la Regla + +La política realiza un análisis exhaustivo en busca de reglas que capturen los siguientes tres tipos de eventos críticos de IAM: +1. `com.oraclecloud.identity.creategroup` +2. `com.oraclecloud.identity.updategroup` +3. `com.oraclecloud.identity.deletegroup` + +La validación se divide en tres comprobaciones específicas: +1. **Existencia Global:** Verifica si existe al menos una regla en todo el proyecto que monitorice eventos de grupos IAM. +2. **Integridad de la Condición:** Si la regla existe, verifica que incluya **los tres** tipos de eventos mencionados anteriormente. +3. **Estado de la Regla:** Verifica que la regla, si está correctamente configurada, se encuentre habilitada (`is_enabled = true`). + +## Casos de Fallo Detectados + +A continuación se describen los tres escenarios específicos que esta política detectará. + +--- +### Caso 1: Regla de Eventos para Grupos IAM Totalmente Ausente + +* **Descripción:** Esta regla se activa si en toda la configuración de Terraform no se encuentra ningún recurso `oci_events_rule` que haga referencia a eventos de grupos de IAM. Esto indica una falta total de auditoría sobre estos recursos. +* **Ejemplo de Código Terraform Problemático:** + ```terraform + # El proyecto define recursos de infraestructura, pero carece de reglas de + # eventos para seguridad de IAM. + + provider "oci" { + region = "us-ashburn-1" + } + ``` +* **Ubicación de la Alerta:** La alerta será general y se anclará al bloque `provider "oci" {}`. + +--- +### Caso 2: Regla de Eventos Incompleta (Faltan Tipos de Eventos) + +* **Descripción:** Se ha detectado una regla `oci_events_rule` destinada a monitorizar grupos, pero su configuración es incompleta. La propiedad `condition` incluye algunos eventos (por ejemplo, solo la creación), pero omite otros críticos (como la eliminación o actualización). +* **Ejemplo de Código Terraform Problemático:** + ```terraform + resource "oci_events_rule" "iam_group_partial" { + display_name = "audit-iam-create" + is_enabled = true + + # FALLO: Solo monitoriza la creación, ignorando actualizaciones y borrados. + condition = jsonencode({ + "eventType": ["com.oraclecloud.identity.creategroup"] + }) + + actions { + # ... configuración de acciones ... + } + } + ``` +* **Ubicación de la Alerta:** La alerta señalará específicamente al atributo `condition` dentro del recurso `oci_events_rule` afectado. + +--- +### Caso 3: Regla de Eventos Deshabilitada + +* **Descripción:** Existe una regla `oci_events_rule` correctamente configurada que incluye todos los eventos de grupos de IAM necesarios, pero se encuentra explícitamente deshabilitada (`is_enabled = false`), por lo que no está protegiendo el entorno. +* **Ejemplo de Código Terraform Problemático:** + ```terraform + resource "oci_events_rule" "iam_group_disabled" { + display_name = "audit-iam-all" + + # FALLO: La regla está bien definida pero apagada. + is_enabled = false + + condition = jsonencode({ + "eventType": [ + "com.oraclecloud.identity.creategroup", + "com.oraclecloud.identity.updategroup", + "com.oraclecloud.identity.deletegroup" + ] + }) + } + ``` +* **Ubicación de la Alerta:** La alerta señalará al atributo `is_enabled` dentro del recurso. + +## Recurso Involucrado + +* `oci_events_rule` + +## Solución + +Para solucionar cualquiera de los problemas detectados, asegúrate de configurar un recurso `oci_events_rule` que incluya los tres eventos requeridos y esté habilitado. + +```terraform +# RECURSO REQUERIDO PARA LA SOLUCIÓN +resource "oci_events_rule" "iam_group_change_rule" { + display_name = "audit-rule-for-iam-group-changes" + description = "Alerts on any creation, update, or deletion of IAM groups" + compartment_id = var.tenancy_ocid # Los eventos de IAM suelen ser a nivel de Tenancy + is_enabled = true + + # La condición debe incluir explícitamente los tres tipos de eventos. + condition = jsonencode({ + eventType = [ + "com.oraclecloud.identity.creategroup", + "com.oraclecloud.identity.updategroup", + "com.oraclecloud.identity.deletegroup" + ] + }) + + actions { + actions { + action_type = "ONS" + topic_id = oci_ons_notification_topic.security_alerts_topic.id + } + } +} + +# Recurso auxiliar: Tópico de notificación para enviar la alerta. +resource "oci_ons_notification_topic" "security_alerts_topic" { + compartment_id = var.compartment_id + name = "security-alerts-topic" +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_iam_group_change_event_rule_missing/metadata.json b/assets/queries/terraform/oci/oci_iam_group_change_event_rule_missing/metadata.json new file mode 100644 index 00000000000..3e1725b4d05 --- /dev/null +++ b/assets/queries/terraform/oci/oci_iam_group_change_event_rule_missing/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "3f6ba6dd-fa6d-45b4-8b27-120b7ebb4d0f", + "queryName": "Event Rule for IAM Group Changes is Missing", + "severity": "MEDIUM", + "category": "Access Control", + "descriptionText": "Ensures that an OCI event rule is configured to monitor and alert on changes to IAM groups. Auditing the creation, modification, and deletion of user groups is a critical security measure to detect unauthorized privilege escalation or tampering with access controls.", + "descriptionUrl": "https://registry.terraform.io/providers/oracle/oci/latest/docs/resources/events_rule", + "platform": "Terraform", + "descriptionID": "3f6ba6dd", + "cloudProvider": "oci", + "cwe": "CWE-778", + "riskScore": 3.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_iam_group_change_event_rule_missing/query.rego b/assets/queries/terraform/oci/oci_iam_group_change_event_rule_missing/query.rego new file mode 100644 index 00000000000..d8e3f3902f3 --- /dev/null +++ b/assets/queries/terraform/oci/oci_iam_group_change_event_rule_missing/query.rego @@ -0,0 +1,77 @@ +package Cx + +expected_event_types := [ + "com.oraclecloud.identity.creategroup", + "com.oraclecloud.identity.updategroup", + "com.oraclecloud.identity.deletegroup" +] + +# REGLA 1: Missing (Global) +# No existe NINGUNA regla en el proyecto que monitoree eventos de IAM (ni siquiera parcialmente). +CxPolicy[result] { + doc := input.document[i] + _ := doc.provider.oci + + any_iam_rule := [rule | + rule := input.document[_].resource.oci_events_rule[_] + + event := expected_event_types[_] + contains(rule.condition, event) + ] + + count(any_iam_rule) == 0 + + result := { + "documentId": doc.id, + "searchKey": "provider.oci", + "issueType": "MissingAttribute", + "keyExpectedValue": "An 'oci_events_rule' for IAM group changes should exist", + "keyActualValue": "No 'oci_events_rule' found for IAM group changes", + } +} + +# REGLA 2: Incomplete (Local) +# La regla existe y mira eventos de IAM, pero le falta alguno de los 3 requeridos. +CxPolicy[result] { + rule := input.document[i].resource.oci_events_rule[name] + + matches := [event | + event := expected_event_types[_] + contains(rule.condition, event) + ] + + count(matches) > 0 + count(matches) < count(expected_event_types) + + missing_count := count(expected_event_types) - count(matches) + + result := { + "documentId": input.document[i].id, + "searchKey": sprintf("resource.oci_events_rule.%s.condition", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "The rule condition should include all 3 IAM group events (create, update, delete)", + "keyActualValue": sprintf("The rule is missing %d IAM group event(s)", [missing_count]), + } +} + +# REGLA 3: Disabled (Local) +# La regla tiene todos los eventos correctos, pero está deshabilitada. +CxPolicy[result] { + rule := input.document[i].resource.oci_events_rule[name] + + matches := [event | + event := expected_event_types[_] + contains(rule.condition, event) + ] + count(matches) == count(expected_event_types) + + rule.is_enabled == false + + result := { + "documentId": input.document[i].id, + "searchKey": sprintf("resource.oci_events_rule.%s.is_enabled", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "'is_enabled' should be true", + "keyActualValue": "'is_enabled' is false", + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_iam_group_change_event_rule_missing/test/negative1.tf b/assets/queries/terraform/oci/oci_iam_group_change_event_rule_missing/test/negative1.tf new file mode 100644 index 00000000000..3c72c48f58f --- /dev/null +++ b/assets/queries/terraform/oci/oci_iam_group_change_event_rule_missing/test/negative1.tf @@ -0,0 +1,23 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_events_rule" "iam_group_changes" { + display_name = "IAMGroupChanges" + is_enabled = true + + condition = < **Fallo**. + * Si el atributo no está definido -> **Fallo** (se asume configuración insegura/desconocida). + +2. **Para IAM Clásico (`oci_identity_authentication_policy`):** + * Genera siempre una alerta **Manual (INFO)**, indicando al auditor que debe verificar la consola de OCI. + +## Casos de Fallo Detectados + +### Caso 1: Expiración Excesiva (Identity Domains) +* **Descripción:** La política define `password_expires_after` con un valor mayor a 365 días. +* **Ubicación:** Atributo `password_expires_after`. + +### Caso 2: Atributo Faltante (Identity Domains) +* **Descripción:** Falta el atributo `password_expires_after`. +* **Ubicación:** Recurso `oci_identity_domains_password_policy`. + +### Caso 3: Recurso Legacy (Manual Check) +* **Descripción:** Se utiliza `oci_identity_authentication_policy`. +* **Ubicación:** Recurso `oci_identity_authentication_policy`. + +## Solución + +Para **Identity Domains**, establece la caducidad en 365 días o menos. + +```terraform +resource "oci_identity_domains_password_policy" "secure_policy" { + # ... otros atributos ... + idcs_endpoint = var.idcs_endpoint + + # Caducidad de contraseña en 90 días (Recomendado) + password_expires_after = 90 + + schemas = ["urn:ietf:params:scim:schemas:oracle:idcs:extension:passwordState:User"] +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_iam_password_expiration_manual/metadata.json b/assets/queries/terraform/oci/oci_iam_password_expiration_manual/metadata.json new file mode 100644 index 00000000000..448d734df94 --- /dev/null +++ b/assets/queries/terraform/oci/oci_iam_password_expiration_manual/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "7f47496b-8cdc-4476-9161-6c58038ef920", + "queryName": "OCI IAM Password Expiration (365 days)", + "severity": "INFO", + "category": "Identity and Access Management", + "descriptionText": "CIS Benchmark recommends that passwords expire within 365 days. Verify 'password_expires_after' is set to 365 or less in 'oci_identity_domains_password_policy'. Legacy policies require manual verification.", + "descriptionUrl": "https://registry.terraform.io/providers/oracle/oci/latest/docs/resources/identity_domains_password_policy", + "platform": "Terraform", + "descriptionID": "7f47496b", + "cloudProvider": "oci", + "cwe": "CWE-261", + "riskScore": 0.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_iam_password_expiration_manual/query.rego b/assets/queries/terraform/oci/oci_iam_password_expiration_manual/query.rego new file mode 100644 index 00000000000..61245d9ec1e --- /dev/null +++ b/assets/queries/terraform/oci/oci_iam_password_expiration_manual/query.rego @@ -0,0 +1,47 @@ +package Cx + +# CASO 1: Identity Domains - Expiración (password_expires_after) > 365. +CxPolicy[result] { + doc := input.document[i] + policy := doc.resource.oci_identity_domains_password_policy[name] + + policy.password_expires_after > 365 + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.oci_identity_domains_password_policy.%s.password_expires_after", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "'password_expires_after' should be <= 365", + "keyActualValue": sprintf("'password_expires_after' is %d", [policy.password_expires_after]), + } +} + +# CASO 2: Identity Domains - Atributo faltante. +CxPolicy[result] { + doc := input.document[i] + policy := doc.resource.oci_identity_domains_password_policy[name] + + object.get(policy, "password_expires_after", "undefined") == "undefined" + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.oci_identity_domains_password_policy.%s", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "'password_expires_after' should be defined (<= 365)", + "keyActualValue": "'password_expires_after' is missing", + } +} + +# CASO 3: IAM Clásico (Legacy) - Manual. +CxPolicy[result] { + doc := input.document[i] + _ := doc.resource.oci_identity_authentication_policy[name] + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.oci_identity_authentication_policy.%s", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "Password expiration should be enforced (<= 365 days)", + "keyActualValue": "Legacy resource does not support password expiration config in Terraform. Manual console check required.", + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_iam_password_expiration_manual/test/negative1.tf b/assets/queries/terraform/oci/oci_iam_password_expiration_manual/test/negative1.tf new file mode 100644 index 00000000000..bfbf8914113 --- /dev/null +++ b/assets/queries/terraform/oci/oci_iam_password_expiration_manual/test/negative1.tf @@ -0,0 +1,7 @@ +resource "oci_identity_domains_password_policy" "correct_policy" { + idcs_endpoint = "https://idcs-..." + name = "CorrectPolicy" + # CORRECTO: <= 365 + password_expires_after = 90 + schemas = ["urn:ietf:params:scim:schemas:oracle:idcs:extension:passwordState:User"] +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_iam_password_expiration_manual/test/positive1.tf b/assets/queries/terraform/oci/oci_iam_password_expiration_manual/test/positive1.tf new file mode 100644 index 00000000000..55fbf6400cb --- /dev/null +++ b/assets/queries/terraform/oci/oci_iam_password_expiration_manual/test/positive1.tf @@ -0,0 +1,7 @@ +resource "oci_identity_domains_password_policy" "long_expiration" { + idcs_endpoint = "https://idcs-..." + name = "LongExpirationPolicy" + # FALLO: > 365 + password_expires_after = 400 + schemas = ["urn:ietf:params:scim:schemas:oracle:idcs:extension:passwordState:User"] +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_iam_password_expiration_manual/test/positive2.tf b/assets/queries/terraform/oci/oci_iam_password_expiration_manual/test/positive2.tf new file mode 100644 index 00000000000..2f876028117 --- /dev/null +++ b/assets/queries/terraform/oci/oci_iam_password_expiration_manual/test/positive2.tf @@ -0,0 +1,7 @@ +resource "oci_identity_domains_password_policy" "missing_attr" { + idcs_endpoint = "https://idcs-..." + name = "MissingAttrPolicy" + # FALLO: Falta el atributo + password_min_length = 14 + schemas = ["urn:ietf:params:scim:schemas:oracle:idcs:extension:passwordState:User"] +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_iam_password_expiration_manual/test/positive3.tf b/assets/queries/terraform/oci/oci_iam_password_expiration_manual/test/positive3.tf new file mode 100644 index 00000000000..22742fb6d8c --- /dev/null +++ b/assets/queries/terraform/oci/oci_iam_password_expiration_manual/test/positive3.tf @@ -0,0 +1,7 @@ +resource "oci_identity_authentication_policy" "legacy_policy" { + compartment_id = "ocid1.tenancy..." + # FALLO: Legacy + password_policy { + minimum_password_length = 14 + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_iam_password_expiration_manual/test/positive_expected_result.json b/assets/queries/terraform/oci/oci_iam_password_expiration_manual/test/positive_expected_result.json new file mode 100644 index 00000000000..dd9cd8a4534 --- /dev/null +++ b/assets/queries/terraform/oci/oci_iam_password_expiration_manual/test/positive_expected_result.json @@ -0,0 +1,20 @@ +[ + { + "queryName": "OCI IAM Password Expiration (365 days)", + "severity": "INFO", + "line": 5, + "fileName": "positive1.tf" + }, + { + "queryName": "OCI IAM Password Expiration (365 days)", + "severity": "INFO", + "line": 1, + "fileName": "positive2.tf" + }, + { + "queryName": "OCI IAM Password Expiration (365 days)", + "severity": "INFO", + "line": 1, + "fileName": "positive3.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_iam_password_policy_length/README.md b/assets/queries/terraform/oci/oci_iam_password_policy_length/README.md new file mode 100644 index 00000000000..35bfc9531ab --- /dev/null +++ b/assets/queries/terraform/oci/oci_iam_password_policy_length/README.md @@ -0,0 +1,55 @@ +# Regla KICS: OCI IAM Password Minimum Length + +## Descripción General + +Esta regla automatizada (MEDIUM) audita el recurso `oci_identity_authentication_policy`. + +Para garantizar una protección robusta contra ataques de fuerza bruta, el **CIS Oracle Cloud Infrastructure Benchmark** recomienda establecer la longitud mínima de la contraseña en **14 caracteres** o más. + +El valor predeterminado de OCI (si no se especifica) suele ser de 12 caracteres, lo cual es insuficiente para los estándares actuales. Esta regla alerta si: +1. La longitud se define explícitamente menor a 14. +2. Falta el atributo de longitud (usa default). +3. Falta el bloque de política de contraseñas completo (usa default). + +## Lógica de la Regla + +1. Identifica el recurso `oci_identity_authentication_policy`. +2. Verifica la existencia del bloque `password_policy`. + * Si no existe el bloque -> **Fallo** (se usa configuración insegura por defecto). +3. Si el bloque existe, verifica el atributo `minimum_password_length`: + * Si el atributo falta -> **Fallo** (se usa configuración insegura por defecto). + * Si el atributo es `< 14` -> **Fallo**. + +## Casos de Fallo Detectados + +### Caso 1: Longitud Insuficiente +* **Descripción:** Se ha configurado explícitamente `minimum_password_length` con un valor bajo (ej. 8 o 12). +* **Ubicación de la Alerta:** Atributo `minimum_password_length`. + +### Caso 2: Atributo Faltante +* **Descripción:** El bloque existe, pero falta `minimum_password_length`, aplicando el default del proveedor. +* **Ubicación de la Alerta:** Bloque `password_policy`. + +### Caso 3: Bloque Ausente +* **Descripción:** No se ha definido `password_policy` en absoluto. +* **Ubicación de la Alerta:** Recurso `oci_identity_authentication_policy`. + +## Recurso Involucrado +* `oci_identity_authentication_policy` + +## Solución + +Define el bloque `password_policy` y establece explícitamente la longitud en al menos 14. + +```terraform +resource "oci_identity_authentication_policy" "secure_policy" { + compartment_id = var.tenancy_ocid + + password_policy { + minimum_password_length = 14 + is_lowercase_characters_required = true + is_uppercase_characters_required = true + is_numeric_characters_required = true + is_special_characters_required = true + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_iam_password_policy_length/metadata.json b/assets/queries/terraform/oci/oci_iam_password_policy_length/metadata.json new file mode 100644 index 00000000000..df4b0cbef87 --- /dev/null +++ b/assets/queries/terraform/oci/oci_iam_password_policy_length/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "ef7a0923-a4e6-4aec-921b-1b4e45463fb6", + "queryName": "OCI IAM Password Minimum Length", + "severity": "MEDIUM", + "category": "Identity and Access Management", + "descriptionText": "The IAM password policy allows passwords shorter than 14 characters. According to CIS Benchmarks, the 'minimum_password_length' in 'oci_identity_authentication_policy' should be set to 14 or greater.", + "descriptionUrl": "https://registry.terraform.io/providers/oracle/oci/latest/docs/resources/identity_authentication_policy", + "platform": "Terraform", + "descriptionID": "ef7a0923", + "cloudProvider": "oci", + "cwe": "CWE-521", + "riskScore": 3.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_iam_password_policy_length/query.rego b/assets/queries/terraform/oci/oci_iam_password_policy_length/query.rego new file mode 100644 index 00000000000..8346e24f0bc --- /dev/null +++ b/assets/queries/terraform/oci/oci_iam_password_policy_length/query.rego @@ -0,0 +1,62 @@ +package Cx + +ensure_array(x) = x { is_array(x) } +ensure_array(x) = [x] { is_object(x) } + +# CASO 1: El bloque 'password_policy' EXISTE, pero el valor es menor a 14. +CxPolicy[result] { + doc := input.document[i] + resource := doc.resource.oci_identity_authentication_policy[name] + + resource.password_policy + policies := ensure_array(resource.password_policy) + policy := policies[_] + + policy.minimum_password_length + + to_number(policy.minimum_password_length) < 14 + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.oci_identity_authentication_policy.%s.password_policy.minimum_password_length", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "'minimum_password_length' should be 14 or greater", + "keyActualValue": sprintf("'minimum_password_length' is %d", [to_number(policy.minimum_password_length)]), + } +} + +# CASO 2: El bloque 'password_policy' EXISTE, pero FALTA el atributo 'minimum_password_length'. +CxPolicy[result] { + doc := input.document[i] + resource := doc.resource.oci_identity_authentication_policy[name] + + resource.password_policy + policies := ensure_array(resource.password_policy) + policy := policies[_] + + object.get(policy, "minimum_password_length", "undefined") == "undefined" + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.oci_identity_authentication_policy.%s.password_policy", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "'minimum_password_length' should be explicitly defined (>= 14)", + "keyActualValue": "'minimum_password_length' is missing (using default)", + } +} + +# CASO 3: El bloque 'password_policy' NO EXISTE en absoluto. +CxPolicy[result] { + doc := input.document[i] + resource := doc.resource.oci_identity_authentication_policy[name] + + not resource.password_policy + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.oci_identity_authentication_policy.%s", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "'password_policy' block should be defined with 'minimum_password_length' >= 14", + "keyActualValue": "'password_policy' block is missing", + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_iam_password_policy_length/test/negative1.tf b/assets/queries/terraform/oci/oci_iam_password_policy_length/test/negative1.tf new file mode 100644 index 00000000000..b302379143f --- /dev/null +++ b/assets/queries/terraform/oci/oci_iam_password_policy_length/test/negative1.tf @@ -0,0 +1,9 @@ +resource "oci_identity_authentication_policy" "secure_policy" { + compartment_id = "ocid1.tenancy..." + + password_policy { + # CORRECTO: >= 14 + minimum_password_length = 14 + is_lowercase_characters_required = true + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_iam_password_policy_length/test/positive1.tf b/assets/queries/terraform/oci/oci_iam_password_policy_length/test/positive1.tf new file mode 100644 index 00000000000..4b2edc6c686 --- /dev/null +++ b/assets/queries/terraform/oci/oci_iam_password_policy_length/test/positive1.tf @@ -0,0 +1,9 @@ +resource "oci_identity_authentication_policy" "weak_policy" { + compartment_id = "ocid1.tenancy..." + + password_policy { + # FALLO: 8 es menor que 14 + minimum_password_length = 8 + is_lowercase_characters_required = true + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_iam_password_policy_length/test/positive2.tf b/assets/queries/terraform/oci/oci_iam_password_policy_length/test/positive2.tf new file mode 100644 index 00000000000..02c3737ba9b --- /dev/null +++ b/assets/queries/terraform/oci/oci_iam_password_policy_length/test/positive2.tf @@ -0,0 +1,8 @@ +resource "oci_identity_authentication_policy" "missing_attr" { + compartment_id = "ocid1.tenancy..." + + password_policy { + # FALLO: Falta minimum_password_length + is_lowercase_characters_required = true + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_iam_password_policy_length/test/positive3.tf b/assets/queries/terraform/oci/oci_iam_password_policy_length/test/positive3.tf new file mode 100644 index 00000000000..6d4ed4fa8d6 --- /dev/null +++ b/assets/queries/terraform/oci/oci_iam_password_policy_length/test/positive3.tf @@ -0,0 +1,4 @@ +resource "oci_identity_authentication_policy" "missing_block" { + compartment_id = "ocid1.tenancy..." + # FALLO: No hay bloque password_policy +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_iam_password_policy_length/test/positive_expected_result.json b/assets/queries/terraform/oci/oci_iam_password_policy_length/test/positive_expected_result.json new file mode 100644 index 00000000000..7eba7d84d20 --- /dev/null +++ b/assets/queries/terraform/oci/oci_iam_password_policy_length/test/positive_expected_result.json @@ -0,0 +1,20 @@ +[ + { + "queryName": "OCI IAM Password Minimum Length", + "severity": "MEDIUM", + "line": 6, + "fileName": "positive1.tf" + }, + { + "queryName": "OCI IAM Password Minimum Length", + "severity": "MEDIUM", + "line": 4, + "fileName": "positive2.tf" + }, + { + "queryName": "OCI IAM Password Minimum Length", + "severity": "MEDIUM", + "line": 1, + "fileName": "positive3.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_iam_password_reuse_manual/README.md b/assets/queries/terraform/oci/oci_iam_password_reuse_manual/README.md new file mode 100644 index 00000000000..45c14e44e54 --- /dev/null +++ b/assets/queries/terraform/oci/oci_iam_password_reuse_manual/README.md @@ -0,0 +1,55 @@ +# Regla KICS: OCI IAM Password Reuse Prevention (Manual) + +## Descripción General + +Esta regla informativa (INFO) audita las políticas de contraseñas en **Oracle Cloud Infrastructure (OCI)** para asegurar que se impida la reutilización de credenciales antiguas. + +El **CIS Oracle Cloud Infrastructure Benchmark** recomienda configurar el historial de contraseñas para recordar las últimas **24** contraseñas utilizadas, evitando así que los usuarios alternen entre un conjunto pequeño de contraseñas conocidas. + +La regla maneja dos escenarios distintos dependiendo de la arquitectura de identidad utilizada en OCI: + +1. **Identity Domains (Moderno):** Audita el recurso `oci_identity_domains_password_policy`. El atributo correcto para controlar el historial es `num_passwords_in_history`. +2. **IAM Clásico (Legacy):** Audita el recurso `oci_identity_authentication_policy`. Este recurso antiguo no permite configurar el historial de contraseñas a través de Terraform, por lo que requiere validación manual en la consola. + +## Lógica de la Regla + +1. **Para Identity Domains (`oci_identity_domains_password_policy`):** + * Verifica el atributo `num_passwords_in_history`. + * Si el valor es menor a 24 -> **Fallo**. + * Si el atributo no está definido -> **Fallo** (se asume configuración insegura/desconocida). + +2. **Para IAM Clásico (`oci_identity_authentication_policy`):** + * Genera siempre una alerta **Manual (INFO)**, indicando al auditor que debe verificar la consola de OCI. + +## Casos de Fallo Detectados + +### Caso 1: Historial Insuficiente (Identity Domains) +* **Descripción:** La política define `num_passwords_in_history` con un valor menor a 24 (ej. 5 o 10). +* **Ubicación de la Alerta:** Atributo `num_passwords_in_history`. + +### Caso 2: Atributo Faltante (Identity Domains) +* **Descripción:** Falta el atributo `num_passwords_in_history`. +* **Ubicación de la Alerta:** Recurso `oci_identity_domains_password_policy`. + +### Caso 3: Recurso Legacy (Manual Check) +* **Descripción:** Se utiliza `oci_identity_authentication_policy`. +* **Ubicación de la Alerta:** Recurso `oci_identity_authentication_policy`. + +## Recursos Involucrados +* `oci_identity_domains_password_policy` +* `oci_identity_authentication_policy` + +## Solución + +Para **Identity Domains**, establece el historial en 24 o más. + +```terraform +resource "oci_identity_domains_password_policy" "secure_policy" { + idcs_endpoint = var.idcs_endpoint + display_name = "SecurePasswordPolicy" + + # Historial de contraseñas (CIS recomienda >= 24) + num_passwords_in_history = 24 + + schemas = ["urn:ietf:params:scim:schemas:oracle:idcs:extension:passwordState:User"] +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_iam_password_reuse_manual/metadata.json b/assets/queries/terraform/oci/oci_iam_password_reuse_manual/metadata.json new file mode 100644 index 00000000000..9c7e71e0bdd --- /dev/null +++ b/assets/queries/terraform/oci/oci_iam_password_reuse_manual/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "fc42ff43-3c72-41d1-b5fb-3beebd4efe15", + "queryName": "OCI IAM Password Reuse Prevention (Manual)", + "severity": "INFO", + "category": "Identity and Access Management", + "descriptionText": "CIS Benchmark recommends preventing password reuse for at least the last 24 passwords. Check 'num_passwords_in_history' in 'oci_identity_domains_password_policy'. For legacy 'oci_identity_authentication_policy', this requires manual console verification.", + "descriptionUrl": "https://registry.terraform.io/providers/oracle/oci/latest/docs/resources/identity_domains_password_policy", + "platform": "Terraform", + "descriptionID": "fc42ff43", + "cloudProvider": "oci", + "cwe": "CWE-261", + "riskScore": 0.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_iam_password_reuse_manual/query.rego b/assets/queries/terraform/oci/oci_iam_password_reuse_manual/query.rego new file mode 100644 index 00000000000..2eee491d363 --- /dev/null +++ b/assets/queries/terraform/oci/oci_iam_password_reuse_manual/query.rego @@ -0,0 +1,47 @@ +package Cx + +# CASO 1: Identity Domains - Historial insuficiente (< 24). +CxPolicy[result] { + doc := input.document[i] + policy := doc.resource.oci_identity_domains_password_policy[name] + + policy.num_passwords_in_history < 24 + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.oci_identity_domains_password_policy.%s.num_passwords_in_history", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "'num_passwords_in_history' should be >= 24", + "keyActualValue": sprintf("Current history size is %d", [policy.num_passwords_in_history]), + } +} + +# CASO 2: Identity Domains - Atributo faltante. +CxPolicy[result] { + doc := input.document[i] + policy := doc.resource.oci_identity_domains_password_policy[name] + + object.get(policy, "num_passwords_in_history", "undefined") == "undefined" + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.oci_identity_domains_password_policy.%s", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "'num_passwords_in_history' should be defined (>= 24)", + "keyActualValue": "'num_passwords_in_history' is missing", + } +} + +# CASO 3: IAM Clásico (Legacy) - Manual. +CxPolicy[result] { + doc := input.document[i] + _ := doc.resource.oci_identity_authentication_policy[name] + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.oci_identity_authentication_policy.%s", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "Password reuse prevention should be enabled (History >= 24)", + "keyActualValue": "Legacy resource does not support password history settings in Terraform. Manual console check required.", + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_iam_password_reuse_manual/test/negative1.tf b/assets/queries/terraform/oci/oci_iam_password_reuse_manual/test/negative1.tf new file mode 100644 index 00000000000..e551ec5346e --- /dev/null +++ b/assets/queries/terraform/oci/oci_iam_password_reuse_manual/test/negative1.tf @@ -0,0 +1,9 @@ +resource "oci_identity_domains_password_policy" "secure_policy" { + idcs_endpoint = "https://idcs-..." + name = "SecurePolicy" + + # CORRECTO: >= 24 + num_passwords_in_history = 24 + + schemas = ["urn:ietf:params:scim:schemas:oracle:idcs:extension:passwordState:User"] +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_iam_password_reuse_manual/test/positive1.tf b/assets/queries/terraform/oci/oci_iam_password_reuse_manual/test/positive1.tf new file mode 100644 index 00000000000..8087a0e07ff --- /dev/null +++ b/assets/queries/terraform/oci/oci_iam_password_reuse_manual/test/positive1.tf @@ -0,0 +1,9 @@ +resource "oci_identity_domains_password_policy" "weak_history" { + idcs_endpoint = "https://idcs-..." + name = "WeakHistoryPolicy" + + # FALLO: 5 es menor que 24 + num_passwords_in_history = 5 + + schemas = ["urn:ietf:params:scim:schemas:oracle:idcs:extension:passwordState:User"] +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_iam_password_reuse_manual/test/positive2.tf b/assets/queries/terraform/oci/oci_iam_password_reuse_manual/test/positive2.tf new file mode 100644 index 00000000000..c57df4600ae --- /dev/null +++ b/assets/queries/terraform/oci/oci_iam_password_reuse_manual/test/positive2.tf @@ -0,0 +1,8 @@ +resource "oci_identity_domains_password_policy" "missing_attr" { + idcs_endpoint = "https://idcs-..." + name = "MissingAttrPolicy" + + # FALLO: Falta num_passwords_in_history + password_min_length = 14 + schemas = ["urn:ietf:params:scim:schemas:oracle:idcs:extension:passwordState:User"] +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_iam_password_reuse_manual/test/positive3.tf b/assets/queries/terraform/oci/oci_iam_password_reuse_manual/test/positive3.tf new file mode 100644 index 00000000000..63251d16ce5 --- /dev/null +++ b/assets/queries/terraform/oci/oci_iam_password_reuse_manual/test/positive3.tf @@ -0,0 +1,8 @@ +resource "oci_identity_authentication_policy" "legacy_policy" { + compartment_id = "ocid1.tenancy..." + + # FALLO: Recurso Legacy requiere chequeo manual + password_policy { + minimum_password_length = 14 + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_iam_password_reuse_manual/test/positive_expected_result.json b/assets/queries/terraform/oci/oci_iam_password_reuse_manual/test/positive_expected_result.json new file mode 100644 index 00000000000..0376d3837d8 --- /dev/null +++ b/assets/queries/terraform/oci/oci_iam_password_reuse_manual/test/positive_expected_result.json @@ -0,0 +1,20 @@ +[ + { + "queryName": "OCI IAM Password Reuse Prevention (Manual)", + "severity": "INFO", + "line": 6, + "fileName": "positive1.tf" + }, + { + "queryName": "OCI IAM Password Reuse Prevention (Manual)", + "severity": "INFO", + "line": 1, + "fileName": "positive2.tf" + }, + { + "queryName": "OCI IAM Password Reuse Prevention (Manual)", + "severity": "INFO", + "line": 1, + "fileName": "positive3.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_iam_policy_change_event_rule_missing/README.md b/assets/queries/terraform/oci/oci_iam_policy_change_event_rule_missing/README.md new file mode 100644 index 00000000000..dfd823e140c --- /dev/null +++ b/assets/queries/terraform/oci/oci_iam_policy_change_event_rule_missing/README.md @@ -0,0 +1,65 @@ +# Regla KICS: Notificación para Cambios en Políticas de IAM en OCI + +## Descripción General + +Esta regla de KICS para Terraform asegura que exista una regla de eventos (`oci_events_rule`) configurada para monitorizar y generar alertas ante cualquier cambio (creación, actualización o eliminación) en las políticas de IAM de la cuenta de OCI. + +Las políticas de IAM son el mecanismo central que define "quién puede hacer qué" en OCI. Un cambio no autorizado en una política puede conceder permisos excesivos, eliminar controles de seguridad o abrir la puerta a un atacante para que tome el control de los recursos. La monitorización en tiempo real de estos cambios es, por tanto, una de las auditorías de seguridad más importantes. + +## Lógica de la Regla + +La política valida que exista una configuración activa para capturar los tres eventos críticos de políticas: +1. `com.oraclecloud.identity.createpolicy` +2. `com.oraclecloud.identity.updatepolicy` +3. `com.oraclecloud.identity.deletepolicy` + +## Casos de Fallo Detectados + +--- +### Caso 1: Regla Ausente (Missing) +* **Descripción:** No se encuentra ninguna regla de eventos que monitorice cambios en políticas de IAM en todo el proyecto. +* **Ubicación:** Bloque `provider "oci"`. + +--- +### Caso 2: Regla Incompleta (Incomplete) +* **Descripción:** Existe una regla que monitoriza algunos eventos de políticas (ej. `createpolicy`), pero omite otros críticos (ej. `deletepolicy`). +* **Ubicación:** Atributo `condition` del recurso. + +--- +### Caso 3: Regla Deshabilitada (Disabled) +* **Descripción:** Existe una regla completa con todos los eventos, pero está explícitamente deshabilitada (`is_enabled = false`). +* **Ubicación:** Atributo `is_enabled`. + +## Recurso Involucrado + +* `oci_events_rule` + +## Solución + +```terraform +resource "oci_events_rule" "iam_policy_change_rule" { + display_name = "audit-rule-for-iam-policy-changes" + description = "Alerts on any creation, update, or deletion of IAM policies" + compartment_id = var.tenancy_ocid + is_enabled = true + + condition = jsonencode({ + eventType = [ + "com.oraclecloud.identity.createpolicy", + "com.oraclecloud.identity.updatepolicy", + "com.oraclecloud.identity.deletepolicy" + ] + }) + + actions { + actions { + action_type = "ONS" + topic_id = oci_ons_notification_topic.security_alerts_topic.id + } + } +} + +resource "oci_ons_notification_topic" "security_alerts_topic" { + compartment_id = var.compartment_id + name = "security-alerts-topic" +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_iam_policy_change_event_rule_missing/metadata.json b/assets/queries/terraform/oci/oci_iam_policy_change_event_rule_missing/metadata.json new file mode 100644 index 00000000000..2dd9f4cfdbd --- /dev/null +++ b/assets/queries/terraform/oci/oci_iam_policy_change_event_rule_missing/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "a151d9a0-d626-447f-beab-11d53aeb0f2b", + "queryName": "Event Rule for IAM Policy Changes is Missing", + "severity": "HIGH", + "category": "Access Control", + "descriptionText": "Ensures that an OCI event rule is configured to monitor and alert on changes to IAM policies. Auditing the creation, modification, and deletion of IAM policies is a critical security measure to detect unauthorized changes to permissions within the tenancy.", + "descriptionUrl": "https://registry.terraform.io/providers/oracle/oci/latest/docs/resources/events_rule", + "platform": "Terraform", + "descriptionID": "a151d9a0", + "cloudProvider": "oci", + "cwe": "CWE-778", + "riskScore": 4.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_iam_policy_change_event_rule_missing/query.rego b/assets/queries/terraform/oci/oci_iam_policy_change_event_rule_missing/query.rego new file mode 100644 index 00000000000..b51144a73ee --- /dev/null +++ b/assets/queries/terraform/oci/oci_iam_policy_change_event_rule_missing/query.rego @@ -0,0 +1,76 @@ +package Cx + +expected_event_types := [ + "com.oraclecloud.identity.createpolicy", + "com.oraclecloud.identity.updatepolicy", + "com.oraclecloud.identity.deletepolicy" +] + +# REGLA 1: Missing (Global) +# No existe NINGUNA regla en el proyecto que monitoree eventos de Políticas IAM. +CxPolicy[result] { + doc := input.document[i] + _ := doc.provider.oci + + any_policy_rule := [rule | + rule := input.document[_].resource.oci_events_rule[_] + event := expected_event_types[_] + contains(rule.condition, event) + ] + + count(any_policy_rule) == 0 + + result := { + "documentId": doc.id, + "searchKey": "provider.oci", + "issueType": "MissingAttribute", + "keyExpectedValue": "An 'oci_events_rule' for IAM Policy changes should exist", + "keyActualValue": "No 'oci_events_rule' found for IAM Policy changes", + } +} + +# REGLA 2: Incomplete (Local) +# La regla existe y mira eventos de Políticas, pero le falta alguno de los 3 requeridos. +CxPolicy[result] { + rule := input.document[i].resource.oci_events_rule[name] + + matches := [event | + event := expected_event_types[_] + contains(rule.condition, event) + ] + + count(matches) > 0 + count(matches) < count(expected_event_types) + + missing_count := count(expected_event_types) - count(matches) + + result := { + "documentId": input.document[i].id, + "searchKey": sprintf("resource.oci_events_rule.%s.condition", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "The rule condition should include all 3 IAM Policy events (create, update, delete)", + "keyActualValue": sprintf("The rule is missing %d IAM Policy event(s)", [missing_count]), + } +} + +# REGLA 3: Disabled (Local) +# La regla tiene todos los eventos correctos, pero está deshabilitada. +CxPolicy[result] { + rule := input.document[i].resource.oci_events_rule[name] + + matches := [event | + event := expected_event_types[_] + contains(rule.condition, event) + ] + count(matches) == count(expected_event_types) + + rule.is_enabled == false + + result := { + "documentId": input.document[i].id, + "searchKey": sprintf("resource.oci_events_rule.%s.is_enabled", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "'is_enabled' should be true", + "keyActualValue": "'is_enabled' is false", + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_iam_policy_change_event_rule_missing/test/negative1.tf b/assets/queries/terraform/oci/oci_iam_policy_change_event_rule_missing/test/negative1.tf new file mode 100644 index 00000000000..9ffa462982d --- /dev/null +++ b/assets/queries/terraform/oci/oci_iam_policy_change_event_rule_missing/test/negative1.tf @@ -0,0 +1,25 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_events_rule" "correct_rule" { + display_name = "CorrectPolicyRule" + compartment_id = "ocid1.tenancy.oc1.." + is_enabled = true + + condition = jsonencode({ + "eventType": [ + "com.oraclecloud.identity.createpolicy", + "com.oraclecloud.identity.updatepolicy", + "com.oraclecloud.identity.deletepolicy" + ] + }) + + actions { + actions { + action_type = "ONS" + topic_id = "ocid1.onstopic.oc1.." + is_enabled = true + } + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_iam_policy_change_event_rule_missing/test/positive1.tf b/assets/queries/terraform/oci/oci_iam_policy_change_event_rule_missing/test/positive1.tf new file mode 100644 index 00000000000..b72e7fdfbf1 --- /dev/null +++ b/assets/queries/terraform/oci/oci_iam_policy_change_event_rule_missing/test/positive1.tf @@ -0,0 +1,10 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_identity_policy" "test_policy" { + name = "test-policy" + description = "Policy for testing" + compartment_id = "ocid1.tenancy.oc1.." + statements = ["Allow group Administrators to manage all-resources in tenancy"] +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_iam_policy_change_event_rule_missing/test/positive2.tf b/assets/queries/terraform/oci/oci_iam_policy_change_event_rule_missing/test/positive2.tf new file mode 100644 index 00000000000..48dcc79c9b4 --- /dev/null +++ b/assets/queries/terraform/oci/oci_iam_policy_change_event_rule_missing/test/positive2.tf @@ -0,0 +1,25 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_events_rule" "incomplete_rule" { + display_name = "IncompletePolicyRule" + compartment_id = "ocid1.tenancy.oc1.." + is_enabled = true + + # FALLO: Falta "deletepolicy" + condition = jsonencode({ + "eventType": [ + "com.oraclecloud.identity.createpolicy", + "com.oraclecloud.identity.updatepolicy" + ] + }) + + actions { + actions { + action_type = "ONS" + topic_id = "ocid1.onstopic.oc1.." + is_enabled = true + } + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_iam_policy_change_event_rule_missing/test/positive3.tf b/assets/queries/terraform/oci/oci_iam_policy_change_event_rule_missing/test/positive3.tf new file mode 100644 index 00000000000..6b515b92330 --- /dev/null +++ b/assets/queries/terraform/oci/oci_iam_policy_change_event_rule_missing/test/positive3.tf @@ -0,0 +1,26 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_events_rule" "disabled_rule" { + display_name = "DisabledPolicyRule" + compartment_id = "ocid1.tenancy.oc1.." + description = "Rule with all events but disabled" + + condition = jsonencode({ + "eventType": [ + "com.oraclecloud.identity.createpolicy", + "com.oraclecloud.identity.updatepolicy", + "com.oraclecloud.identity.deletepolicy" + ] + }) + + actions { + actions { + action_type = "ONS" + topic_id = "ocid1.onstopic.oc1.." + } + } + + is_enabled = false +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_iam_policy_change_event_rule_missing/test/positive_expected_result.json b/assets/queries/terraform/oci/oci_iam_policy_change_event_rule_missing/test/positive_expected_result.json new file mode 100644 index 00000000000..3653af125a1 --- /dev/null +++ b/assets/queries/terraform/oci/oci_iam_policy_change_event_rule_missing/test/positive_expected_result.json @@ -0,0 +1,20 @@ +[ + { + "queryName": "Event Rule for IAM Policy Changes is Missing", + "severity": "HIGH", + "line": 2, + "fileName": "positive1.tf" + }, + { + "queryName": "Event Rule for IAM Policy Changes is Missing", + "severity": "HIGH", + "line": 11, + "fileName": "positive2.tf" + }, + { + "queryName": "Event Rule for IAM Policy Changes is Missing", + "severity": "HIGH", + "line": 25, + "fileName": "positive3.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_iam_service_admins_manual/README.md b/assets/queries/terraform/oci/oci_iam_service_admins_manual/README.md new file mode 100644 index 00000000000..3fedb6262a9 --- /dev/null +++ b/assets/queries/terraform/oci/oci_iam_service_admins_manual/README.md @@ -0,0 +1,46 @@ +# Regla KICS: OCI Service Level Admins (Manual) + +## Descripción General + +Esta regla informativa (INFO) audita las políticas de **OCI IAM** (`oci_identity_policy`). + +Según el Benchmark CIS para Oracle Cloud, se debe evitar el uso excesivo del grupo de administradores predeterminado (que tiene acceso total a la tenencia). En su lugar, se deben crear **Administradores de Nivel de Servicio** (Service Level Admins). Esto implica crear grupos y políticas específicas para gestionar verticales tecnológicas, por ejemplo: +* `NetworkAdmins`: `manage virtual-network-family` +* `ComputeAdmins`: `manage instance-family` +* `StorageAdmins`: `manage object-family` + +Esta regla detecta cualquier política que otorgue el verbo `manage` para que un auditor verifique que está correctamente acotada (es decir, que no sea `manage all-resources` a menos que sea estrictamente necesario). + +## Lógica de la Regla + +1. Identifica recursos `oci_identity_policy`. +2. Analiza el array `statements`. +3. Si una sentencia contiene la palabra exacta `manage` (usando detección de límite de palabra y sin distinguir mayúsculas/minúsculas), genera una alerta para su revisión manual. + +## Casos de Fallo Detectados + +### Caso 1: Privilegios de Gestión (Manage) + +* **Descripción:** Se detectó una política que otorga control total (`manage`) sobre algún recurso o familia de recursos. +* **Acción:** Verificar que el grupo asignado y el recurso gestionado ("family") corresponden a una segregación de funciones adecuada. +* **Ubicación de la Alerta:** Atributo `statements` en `oci_identity_policy`. + +## Recurso Involucrado + +* `oci_identity_policy` + +## Solución + +Define políticas granulares por familia de servicios. + +```terraform +resource "oci_identity_policy" "network_admin_policy" { + name = "NetworkAdminPolicy" + description = "Policy for Network Admins" + compartment_id = var.tenancy_ocid + + statements = [ + "Allow group NetworkAdmins to manage virtual-network-family in tenancy", + "Allow group NetworkAdmins to read all-resources in tenancy" + ] +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_iam_service_admins_manual/metadata.json b/assets/queries/terraform/oci/oci_iam_service_admins_manual/metadata.json new file mode 100644 index 00000000000..ecb116d27ae --- /dev/null +++ b/assets/queries/terraform/oci/oci_iam_service_admins_manual/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "0401f017-91ee-4019-842a-f940d08b22aa", + "queryName": "OCI Service Level Admins (Manual)", + "severity": "INFO", + "category": "Identity and Access Management", + "descriptionText": "CIS controls recommend creating specific Service Level Administrators (e.g., Network Admin, Storage Admin) rather than relying solely on the default Tenancy Administrator. Review this policy to ensure it delegates 'manage' permissions for specific resource families to appropriate groups, implementing Least Privilege.", + "descriptionUrl": "https://registry.terraform.io/providers/oracle/oci/latest/docs/resources/identity_policy", + "platform": "Terraform", + "cloudProvider": "oci", + "cwe": "CWE-276", + "descriptionID": "0401f017", + "riskScore": 0.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_iam_service_admins_manual/query.rego b/assets/queries/terraform/oci/oci_iam_service_admins_manual/query.rego new file mode 100644 index 00000000000..5e2ce35faa0 --- /dev/null +++ b/assets/queries/terraform/oci/oci_iam_service_admins_manual/query.rego @@ -0,0 +1,19 @@ +package Cx + +# REGLA: Auditoría Manual de Políticas de Gestión (Service Admins). +CxPolicy[result] { + doc := input.document[i] + policy := doc.resource.oci_identity_policy[name] + + statement := policy.statements[_] + + regex.match("(?i)\\bmanage\\b", statement) + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.oci_identity_policy.%s.statements", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "Policy should grant 'manage' on specific families to specific groups (Manual Review)", + "keyActualValue": sprintf("Policy statement grants management privileges: '%s'. Manual review required.", [statement]), + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_iam_service_admins_manual/test/negative1.tf b/assets/queries/terraform/oci/oci_iam_service_admins_manual/test/negative1.tf new file mode 100644 index 00000000000..0f5443c23d9 --- /dev/null +++ b/assets/queries/terraform/oci/oci_iam_service_admins_manual/test/negative1.tf @@ -0,0 +1,14 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_identity_policy" "auditors" { + name = "AuditPolicy" + description = "Read only access" + compartment_id = "ocid1.tenancy..." + + statements = [ + "Allow group Auditors to read all-resources in tenancy", + "Allow group Auditors to inspect users in tenancy" + ] +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_iam_service_admins_manual/test/negative2.tf b/assets/queries/terraform/oci/oci_iam_service_admins_manual/test/negative2.tf new file mode 100644 index 00000000000..bd5c0be5230 --- /dev/null +++ b/assets/queries/terraform/oci/oci_iam_service_admins_manual/test/negative2.tf @@ -0,0 +1,13 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_identity_policy" "project_managers" { + name = "ProjectManagersPolicy" + compartment_id = "ocid1.tenancy..." + + statements = [ + "Allow group ProjectManagers to read instance-family in compartment ProjectA", + "Allow group KeyManagementUsers to use keys in tenancy" + ] +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_iam_service_admins_manual/test/positive1.tf b/assets/queries/terraform/oci/oci_iam_service_admins_manual/test/positive1.tf new file mode 100644 index 00000000000..3d98f40f89f --- /dev/null +++ b/assets/queries/terraform/oci/oci_iam_service_admins_manual/test/positive1.tf @@ -0,0 +1,23 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_identity_policy" "network_admins" { + name = "NetworkAdmins" + description = "Admins for VCN" + compartment_id = "ocid1.tenancy..." + + statements = [ + "Allow group NetworkAdmins to manage virtual-network-family in tenancy" + ] +} + +resource "oci_identity_policy" "super_admin" { + name = "SuperAdmins" + description = "God mode" + compartment_id = "ocid1.tenancy..." + + statements = [ + "Allow group SuperUsers to MANAGE all-resources in tenancy" + ] +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_iam_service_admins_manual/test/positive_expected_result.json b/assets/queries/terraform/oci/oci_iam_service_admins_manual/test/positive_expected_result.json new file mode 100644 index 00000000000..c85bd5b053a --- /dev/null +++ b/assets/queries/terraform/oci/oci_iam_service_admins_manual/test/positive_expected_result.json @@ -0,0 +1,14 @@ +[ + { + "queryName": "OCI Service Level Admins (Manual)", + "severity": "INFO", + "line": 10, + "fileName": "positive1.tf" + }, + { + "queryName": "OCI Service Level Admins (Manual)", + "severity": "INFO", + "line": 20, + "fileName": "positive1.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_iam_user_change_event_rule_missing/README.md b/assets/queries/terraform/oci/oci_iam_user_change_event_rule_missing/README.md new file mode 100644 index 00000000000..641c35d5c0a --- /dev/null +++ b/assets/queries/terraform/oci/oci_iam_user_change_event_rule_missing/README.md @@ -0,0 +1,57 @@ +# Regla KICS: Notificación para Cambios en Usuarios de IAM en OCI + +## Descripción General + +Esta regla de KICS para Terraform asegura que exista una regla de eventos (`oci_events_rule`) configurada para monitorizar y generar alertas ante cualquier cambio en el ciclo de vida de los usuarios de IAM en la cuenta de OCI. + +La gestión de identidades de usuario es una superficie de ataque principal. Un atacante podría crear un usuario nuevo y oculto, modificar un usuario existente para escalar privilegios, o deshabilitar cuentas legítimas para causar una denegación de servicio. + +## Lógica de la Regla + +La política verifica que exista configuración activa para capturar los cinco tipos de eventos que constituyen el ciclo de vida de un usuario de IAM: +1. `com.oraclecloud.identity.createuser` +2. `com.oraclecloud.identity.updateuser` +3. `com.oraclecloud.identity.deleteuser` +4. `com.oraclecloud.identity.enableuser` +5. `com.oraclecloud.identity.disableuser` + +## Casos de Fallo Detectados + +### Caso 1: Regla Ausente (Missing) +* **Descripción:** No existe ninguna regla configurada para usuarios en el proyecto. +* **Ubicación:** Bloque `provider "oci"`. + +### Caso 2: Regla Incompleta (Incomplete) +* **Descripción:** Existe una regla para usuarios, pero le faltan eventos (ej. monitorea `create` pero olvida `disable`). +* **Ubicación:** Atributo `condition`. + +### Caso 3: Regla Deshabilitada (Disabled) +* **Descripción:** Existe una regla relevante pero está explícitamente deshabilitada (`is_enabled = false`). +* **Ubicación:** Atributo `is_enabled` dentro del recurso `oci_events_rule`. + +## Solución + +```terraform +resource "oci_events_rule" "iam_user_change_rule" { + display_name = "audit-rule-for-iam-user-changes" + description = "Alerts on any lifecycle change of IAM users" + compartment_id = var.tenancy_ocid + is_enabled = true + + condition = jsonencode({ + eventType = [ + "com.oraclecloud.identity.createuser", + "com.oraclecloud.identity.updateuser", + "com.oraclecloud.identity.deleteuser", + "com.oraclecloud.identity.enableuser", + "com.oraclecloud.identity.disableuser" + ] + }) + + actions { + actions { + action_type = "ONS" + topic_id = oci_ons_notification_topic.security_alerts_topic.id + } + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_iam_user_change_event_rule_missing/metadata.json b/assets/queries/terraform/oci/oci_iam_user_change_event_rule_missing/metadata.json new file mode 100644 index 00000000000..abb44be36ee --- /dev/null +++ b/assets/queries/terraform/oci/oci_iam_user_change_event_rule_missing/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "b1405757-feb9-4218-b685-ff53775ddf48", + "queryName": "Event Rule for IAM User Changes is Missing", + "severity": "HIGH", + "category": "Access Control", + "descriptionText": "Ensures that an OCI event rule is configured to monitor and alert on changes to IAM users. Auditing the full lifecycle of user accounts (creation, modification, deletion, enable/disable) is a critical security measure to detect unauthorized account creation or privilege changes.", + "descriptionUrl": "https://registry.terraform.io/providers/oracle/oci/latest/docs/resources/events_rule", + "platform": "Terraform", + "descriptionID": "b1405757", + "cloudProvider": "oci", + "cwe": "CWE-778", + "riskScore": 6.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_iam_user_change_event_rule_missing/query.rego b/assets/queries/terraform/oci/oci_iam_user_change_event_rule_missing/query.rego new file mode 100644 index 00000000000..af9fd41e8e7 --- /dev/null +++ b/assets/queries/terraform/oci/oci_iam_user_change_event_rule_missing/query.rego @@ -0,0 +1,78 @@ +package Cx + +expected_event_types := [ + "com.oraclecloud.identity.createuser", + "com.oraclecloud.identity.updateuser", + "com.oraclecloud.identity.deleteuser", + "com.oraclecloud.identity.enableuser", + "com.oraclecloud.identity.disableuser" +] + +# REGLA 1: Missing (Global) +# No existe NINGUNA regla en el proyecto que monitoree eventos de usuarios. +CxPolicy[result] { + doc := input.document[i] + _ := doc.provider.oci + + any_user_rule := [rule | + rule := input.document[_].resource.oci_events_rule[_] + event := expected_event_types[_] + contains(rule.condition, event) + ] + + count(any_user_rule) == 0 + + result := { + "documentId": doc.id, + "searchKey": "provider.oci", + "issueType": "MissingAttribute", + "keyExpectedValue": "An 'oci_events_rule' for IAM User changes (create, update, delete, enable, disable) should exist", + "keyActualValue": "No 'oci_events_rule' found for IAM User changes", + } +} + +# REGLA 2: Incomplete (Local) +# La regla existe y es relevante, pero le falta alguno de los 5 eventos. +CxPolicy[result] { + rule := input.document[i].resource.oci_events_rule[name] + + matches := [event | + event := expected_event_types[_] + contains(rule.condition, event) + ] + + count(matches) > 0 + count(matches) < count(expected_event_types) + + missing_count := count(expected_event_types) - count(matches) + + result := { + "documentId": input.document[i].id, + "searchKey": sprintf("resource.oci_events_rule.%s.condition", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "The rule condition should include all 5 IAM User events", + "keyActualValue": sprintf("The rule is missing %d IAM User event(s)", [missing_count]), + } +} + +# REGLA 3: Disabled (Local) +# Si es una regla de Usuarios (relevante) y está apagada -> FALLO CRÍTICO. +CxPolicy[result] { + rule := input.document[i].resource.oci_events_rule[name] + + matches := [event | + event := expected_event_types[_] + contains(rule.condition, event) + ] + count(matches) > 0 + + rule.is_enabled == false + + result := { + "documentId": input.document[i].id, + "searchKey": sprintf("resource.oci_events_rule.%s.is_enabled", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "'is_enabled' should be true", + "keyActualValue": "'is_enabled' is false", + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_iam_user_change_event_rule_missing/test/negative1.tf b/assets/queries/terraform/oci/oci_iam_user_change_event_rule_missing/test/negative1.tf new file mode 100644 index 00000000000..db6c7e4a0c3 --- /dev/null +++ b/assets/queries/terraform/oci/oci_iam_user_change_event_rule_missing/test/negative1.tf @@ -0,0 +1,27 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_events_rule" "correct_user_rule" { + display_name = "CorrectUserRule" + compartment_id = "ocid1.tenancy..." + is_enabled = true + + condition = jsonencode({ + "eventType": [ + "com.oraclecloud.identity.createuser", + "com.oraclecloud.identity.updateuser", + "com.oraclecloud.identity.deleteuser", + "com.oraclecloud.identity.enableuser", + "com.oraclecloud.identity.disableuser" + ] + }) + + actions { + actions { + action_type = "ONS" + topic_id = "ocid1.onstopic..." + is_enabled = true + } + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_iam_user_change_event_rule_missing/test/positive1.tf b/assets/queries/terraform/oci/oci_iam_user_change_event_rule_missing/test/positive1.tf new file mode 100644 index 00000000000..df3bc9c3799 --- /dev/null +++ b/assets/queries/terraform/oci/oci_iam_user_change_event_rule_missing/test/positive1.tf @@ -0,0 +1,8 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_identity_user" "test_user" { + name = "test-user" + description = "Test user" +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_iam_user_change_event_rule_missing/test/positive2.tf b/assets/queries/terraform/oci/oci_iam_user_change_event_rule_missing/test/positive2.tf new file mode 100644 index 00000000000..41ad4ef1343 --- /dev/null +++ b/assets/queries/terraform/oci/oci_iam_user_change_event_rule_missing/test/positive2.tf @@ -0,0 +1,25 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_events_rule" "incomplete_user_rule" { + display_name = "IncompleteUserRule" + compartment_id = "ocid1.tenancy..." + is_enabled = true + + # FALLO: Faltan enableuser y disableuser + condition = jsonencode({ + "eventType": [ + "com.oraclecloud.identity.createuser", + "com.oraclecloud.identity.updateuser", + "com.oraclecloud.identity.deleteuser" + ] + }) + + actions { + actions { + action_type = "ONS" + topic_id = "ocid1.onstopic..." + } + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_iam_user_change_event_rule_missing/test/positive3.tf b/assets/queries/terraform/oci/oci_iam_user_change_event_rule_missing/test/positive3.tf new file mode 100644 index 00000000000..395f73b60a9 --- /dev/null +++ b/assets/queries/terraform/oci/oci_iam_user_change_event_rule_missing/test/positive3.tf @@ -0,0 +1,28 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_events_rule" "disabled_user_rule" { + display_name = "DisabledUserRule" + compartment_id = "ocid1.tenancy..." + description = "Has all events but is disabled" + + condition = jsonencode({ + "eventType": [ + "com.oraclecloud.identity.createuser", + "com.oraclecloud.identity.updateuser", + "com.oraclecloud.identity.deleteuser", + "com.oraclecloud.identity.enableuser", + "com.oraclecloud.identity.disableuser" + ] + }) + + actions { + actions { + action_type = "ONS" + topic_id = "ocid1.onstopic..." + } + } + + is_enabled = false +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_iam_user_change_event_rule_missing/test/positive_expected_result.json b/assets/queries/terraform/oci/oci_iam_user_change_event_rule_missing/test/positive_expected_result.json new file mode 100644 index 00000000000..2c81ca8e5ee --- /dev/null +++ b/assets/queries/terraform/oci/oci_iam_user_change_event_rule_missing/test/positive_expected_result.json @@ -0,0 +1,20 @@ +[ + { + "queryName": "Event Rule for IAM User Changes is Missing", + "severity": "HIGH", + "line": 1, + "fileName": "positive1.tf" + }, + { + "queryName": "Event Rule for IAM User Changes is Missing", + "severity": "HIGH", + "line": 11, + "fileName": "positive2.tf" + }, + { + "queryName": "Event Rule for IAM User Changes is Missing", + "severity": "HIGH", + "line": 27, + "fileName": "positive3.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_idp_change_event_rule_missing/README.md b/assets/queries/terraform/oci/oci_idp_change_event_rule_missing/README.md new file mode 100644 index 00000000000..aee2de0105c --- /dev/null +++ b/assets/queries/terraform/oci/oci_idp_change_event_rule_missing/README.md @@ -0,0 +1,47 @@ +# Regla KICS: Notificación para Cambios en el Proveedor de Identidad de OCI + +## Descripción General + +Esta regla de KICS para Terraform asegura que exista una regla de eventos (`oci_events_rule`) configurada para monitorizar y generar alertas ante cambios en los proveedores de identidad (Identity Providers) de la cuenta de OCI. + +Los proveedores de identidad son un componente crítico de la seguridad, ya que controlan la federación de usuarios. Un atacante que consiga modificar o añadir un proveedor de identidad podría redirigir los inicios de sesión o federar un dominio malicioso para obtener acceso a la cuenta. Por lo tanto, cualquier cambio en esta configuración debe ser notificado y auditado inmediatamente. + +## Lógica de la Regla + +La política verifica que exista configuración activa para capturar el siguiente evento crítico: +* `com.oraclecloud.identitycontrolplane.updateidentityprovider` + +## Casos de Fallo Detectados + +### Caso 1: Regla Ausente (Missing) +* **Descripción:** No existe ninguna regla configurada para monitorear Identity Providers en el proyecto. +* **Ubicación de la Alerta:** Bloque `provider "oci"`. + +### Caso 2: Regla Deshabilitada (Disabled) +* **Descripción:** Existe una regla que monitorea el evento correcto, pero está explícitamente deshabilitada (`is_enabled = false`). +* **Ubicación de la Alerta:** Atributo `is_enabled` dentro del recurso `oci_events_rule`. + +## Solución + +Para solucionar el problema, asegúrate de tener una regla habilitada con el evento correcto: + +```terraform +resource "oci_events_rule" "iam_idp_change_rule" { + display_name = "audit-rule-for-identity-provider-changes" + description = "Alerts on any modification to Identity Providers" + compartment_id = var.tenancy_ocid + is_enabled = true + + condition = jsonencode({ + eventType = [ + "com.oraclecloud.identitycontrolplane.updateidentityprovider" + ] + }) + + actions { + actions { + action_type = "ONS" + topic_id = oci_ons_notification_topic.security_alerts_topic.id + } + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_idp_change_event_rule_missing/metadata.json b/assets/queries/terraform/oci/oci_idp_change_event_rule_missing/metadata.json new file mode 100644 index 00000000000..12d91b5fcb1 --- /dev/null +++ b/assets/queries/terraform/oci/oci_idp_change_event_rule_missing/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "0a077b15-dc77-4490-810b-4e34f1b55500", + "queryName": "Event Rule for Identity Provider Changes is Missing", + "severity": "MEDIUM", + "category": "Access Control", + "descriptionText": "Ensures that an OCI event rule is configured to monitor and alert on changes to Identity Providers. Monitoring these changes is critical for detecting potentially malicious activity, such as the addition of an unauthorized external identity provider.", + "descriptionUrl": "https://registry.terraform.io/providers/oracle/oci/latest/docs/resources/events_rule", + "platform": "Terraform", + "descriptionID": "0a077b15", + "cloudProvider": "oci", + "cwe": "CWE-778", + "riskScore": 3.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_idp_change_event_rule_missing/query.rego b/assets/queries/terraform/oci/oci_idp_change_event_rule_missing/query.rego new file mode 100644 index 00000000000..5b650d7bc6a --- /dev/null +++ b/assets/queries/terraform/oci/oci_idp_change_event_rule_missing/query.rego @@ -0,0 +1,43 @@ +package Cx + +expected_event := "com.oraclecloud.identitycontrolplane.updateidentityprovider" + +# REGLA 1: Missing (Global) +# No existe NINGUNA regla en el proyecto que monitoree el evento de Identity Provider. +CxPolicy[result] { + doc := input.document[i] + _ := doc.provider.oci + + any_idp_rule := [rule | + rule := input.document[_].resource.oci_events_rule[_] + contains(rule.condition, expected_event) + ] + + count(any_idp_rule) == 0 + + result := { + "documentId": doc.id, + "searchKey": "provider.oci", + "issueType": "MissingAttribute", + "keyExpectedValue": "An 'oci_events_rule' for Identity Provider changes should exist", + "keyActualValue": "No 'oci_events_rule' found for Identity Provider changes", + } +} + +# REGLA 2: Disabled (Local) +# La regla existe y monitorea IdP, pero está deshabilitada. +CxPolicy[result] { + rule := input.document[i].resource.oci_events_rule[name] + + contains(rule.condition, expected_event) + + rule.is_enabled == false + + result := { + "documentId": input.document[i].id, + "searchKey": sprintf("resource.oci_events_rule.%s.is_enabled", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "'is_enabled' should be true", + "keyActualValue": "'is_enabled' is false", + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_idp_change_event_rule_missing/test/negative1.tf b/assets/queries/terraform/oci/oci_idp_change_event_rule_missing/test/negative1.tf new file mode 100644 index 00000000000..93206fbcabf --- /dev/null +++ b/assets/queries/terraform/oci/oci_idp_change_event_rule_missing/test/negative1.tf @@ -0,0 +1,23 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_events_rule" "correct_idp_rule" { + display_name = "CorrectIdPRule" + compartment_id = "ocid1.tenancy..." + is_enabled = true + + condition = jsonencode({ + "eventType": [ + "com.oraclecloud.identitycontrolplane.updateidentityprovider" + ] + }) + + actions { + actions { + action_type = "ONS" + topic_id = "ocid1.onstopic..." + is_enabled = true + } + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_idp_change_event_rule_missing/test/positive1.tf b/assets/queries/terraform/oci/oci_idp_change_event_rule_missing/test/positive1.tf new file mode 100644 index 00000000000..3ae5013b74c --- /dev/null +++ b/assets/queries/terraform/oci/oci_idp_change_event_rule_missing/test/positive1.tf @@ -0,0 +1,20 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_events_rule" "other_rule" { + display_name = "InstanceRule" + compartment_id = "ocid1.tenancy..." + is_enabled = true + + condition = jsonencode({ + "eventType": ["com.oraclecloud.compute.instance.launch.end"] + }) + + actions { + actions { + action_type = "ONS" + topic_id = "ocid1.onstopic..." + } + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_idp_change_event_rule_missing/test/positive2.tf b/assets/queries/terraform/oci/oci_idp_change_event_rule_missing/test/positive2.tf new file mode 100644 index 00000000000..d17dc30b7d6 --- /dev/null +++ b/assets/queries/terraform/oci/oci_idp_change_event_rule_missing/test/positive2.tf @@ -0,0 +1,24 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_events_rule" "disabled_idp_rule" { + display_name = "DisabledIdPRule" + compartment_id = "ocid1.tenancy..." + description = "Monitors IdP but is disabled" + + condition = jsonencode({ + "eventType": [ + "com.oraclecloud.identitycontrolplane.updateidentityprovider" + ] + }) + + actions { + actions { + action_type = "ONS" + topic_id = "ocid1.onstopic..." + } + } + + is_enabled = false +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_idp_change_event_rule_missing/test/positive_expected_result.json b/assets/queries/terraform/oci/oci_idp_change_event_rule_missing/test/positive_expected_result.json new file mode 100644 index 00000000000..5159aacc2cc --- /dev/null +++ b/assets/queries/terraform/oci/oci_idp_change_event_rule_missing/test/positive_expected_result.json @@ -0,0 +1,14 @@ +[ + { + "queryName": "Event Rule for Identity Provider Changes is Missing", + "severity": "MEDIUM", + "line": 1, + "fileName": "positive1.tf" + }, + { + "queryName": "Event Rule for Identity Provider Changes is Missing", + "severity": "MEDIUM", + "line": 23, + "fileName": "positive2.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_idp_group_mapping_change_event_rule_missing/README.md b/assets/queries/terraform/oci/oci_idp_group_mapping_change_event_rule_missing/README.md new file mode 100644 index 00000000000..79e56c40b98 --- /dev/null +++ b/assets/queries/terraform/oci/oci_idp_group_mapping_change_event_rule_missing/README.md @@ -0,0 +1,45 @@ +# Regla KICS: Notificación para Cambios de Mapeo de Grupos de IdP en OCI + +## Descripción General + +Esta regla de KICS para Terraform asegura que exista una regla de eventos (`oci_events_rule`) configurada para monitorizar y generar alertas ante cambios en los mapeos de grupos de los proveedores de identidad (IdP) de la cuenta de OCI. + +El mapeo de grupos de un IdP es lo que traduce la pertenencia a un grupo en un proveedor de identidad externo (como Azure AD, Okta, etc.) a la pertenencia a un grupo de IAM en OCI. Un cambio no autorizado en estos mapeos podría escalar privilegios de forma masiva y silenciosa. + +## Lógica de la Regla + +La política verifica que exista configuración activa para capturar el siguiente evento crítico: +* `com.oraclecloud.identitycontrolplane.updateidpgroupmapping` + +## Casos de Fallo Detectados + +### Caso 1: Regla Ausente (Missing) +* **Descripción:** No existe ninguna regla configurada para monitorear cambios en mapeos de grupos de IdP. +* **Ubicación de la Alerta:** Bloque `provider "oci"`. + +### Caso 2: Regla Deshabilitada (Disabled) +* **Descripción:** Existe una regla que monitorea el evento correcto, pero está explícitamente deshabilitada (`is_enabled = false`). +* **Ubicación de la Alerta:** Atributo `is_enabled` dentro del recurso `oci_events_rule`. + +## Solución + +```terraform +resource "oci_events_rule" "iam_idp_group_mapping_change_rule" { + display_name = "audit-rule-for-idp-group-mapping-changes" + description = "Alerts on any modification to IdP group mappings" + compartment_id = var.tenancy_ocid + is_enabled = true + + condition = jsonencode({ + eventType = [ + "com.oraclecloud.identitycontrolplane.updateidpgroupmapping" + ] + }) + + actions { + actions { + action_type = "ONS" + topic_id = oci_ons_notification_topic.security_alerts_topic.id + } + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_idp_group_mapping_change_event_rule_missing/metadata.json b/assets/queries/terraform/oci/oci_idp_group_mapping_change_event_rule_missing/metadata.json new file mode 100644 index 00000000000..cbd08451173 --- /dev/null +++ b/assets/queries/terraform/oci/oci_idp_group_mapping_change_event_rule_missing/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "1f106aa1-a994-4a11-afbc-109f31947d6a", + "queryName": "Event Rule for IdP Group Mapping Changes is Missing", + "severity": "MEDIUM", + "category": "Access Control", + "descriptionText": "Ensures that an OCI event rule is configured to monitor and alert on changes to Identity Provider (IdP) group mappings. Auditing these changes is vital for detecting unauthorized modifications to user group permissions.", + "descriptionUrl": "https://registry.terraform.io/providers/oracle/oci/latest/docs/resources/events_rule", + "platform": "Terraform", + "descriptionID": "1f106aa1", + "cloudProvider": "oci", + "cwe": "CWE-778", + "riskScore": 3.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_idp_group_mapping_change_event_rule_missing/query.rego b/assets/queries/terraform/oci/oci_idp_group_mapping_change_event_rule_missing/query.rego new file mode 100644 index 00000000000..18e7c22d871 --- /dev/null +++ b/assets/queries/terraform/oci/oci_idp_group_mapping_change_event_rule_missing/query.rego @@ -0,0 +1,43 @@ +package Cx + +expected_event := "com.oraclecloud.identitycontrolplane.updateidpgroupmapping" + +# REGLA 1: Missing (Global) +# No existe NINGUNA regla en el proyecto que monitoree el evento de IdP Group Mapping. +CxPolicy[result] { + doc := input.document[i] + _ := doc.provider.oci + + any_mapping_rule := [rule | + rule := input.document[_].resource.oci_events_rule[_] + contains(rule.condition, expected_event) + ] + + count(any_mapping_rule) == 0 + + result := { + "documentId": doc.id, + "searchKey": "provider.oci", + "issueType": "MissingAttribute", + "keyExpectedValue": "An 'oci_events_rule' for IdP group mapping changes should exist", + "keyActualValue": "No 'oci_events_rule' found for IdP group mapping changes", + } +} + +# REGLA 2: Disabled (Local) +# La regla existe y monitorea el mapeo, pero está deshabilitada. +CxPolicy[result] { + rule := input.document[i].resource.oci_events_rule[name] + + contains(rule.condition, expected_event) + + rule.is_enabled == false + + result := { + "documentId": input.document[i].id, + "searchKey": sprintf("resource.oci_events_rule.%s.is_enabled", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "'is_enabled' should be true", + "keyActualValue": "'is_enabled' is false", + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_idp_group_mapping_change_event_rule_missing/test/negative1.tf b/assets/queries/terraform/oci/oci_idp_group_mapping_change_event_rule_missing/test/negative1.tf new file mode 100644 index 00000000000..9c48a70b701 --- /dev/null +++ b/assets/queries/terraform/oci/oci_idp_group_mapping_change_event_rule_missing/test/negative1.tf @@ -0,0 +1,23 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_events_rule" "correct_mapping_rule" { + display_name = "CorrectMappingRule" + compartment_id = "ocid1.tenancy..." + is_enabled = true + + condition = jsonencode({ + "eventType": [ + "com.oraclecloud.identitycontrolplane.updateidpgroupmapping" + ] + }) + + actions { + actions { + action_type = "ONS" + topic_id = "ocid1.onstopic..." + is_enabled = true + } + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_idp_group_mapping_change_event_rule_missing/test/positive1.tf b/assets/queries/terraform/oci/oci_idp_group_mapping_change_event_rule_missing/test/positive1.tf new file mode 100644 index 00000000000..765a4165e1b --- /dev/null +++ b/assets/queries/terraform/oci/oci_idp_group_mapping_change_event_rule_missing/test/positive1.tf @@ -0,0 +1,20 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_events_rule" "other_rule" { + display_name = "NetworkRule" + compartment_id = "ocid1.tenancy..." + is_enabled = true + + condition = jsonencode({ + "eventType": ["com.oraclecloud.virtualnetwork.createvcn"] + }) + + actions { + actions { + action_type = "ONS" + topic_id = "ocid1.onstopic..." + } + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_idp_group_mapping_change_event_rule_missing/test/positive2.tf b/assets/queries/terraform/oci/oci_idp_group_mapping_change_event_rule_missing/test/positive2.tf new file mode 100644 index 00000000000..c4cfa1d649a --- /dev/null +++ b/assets/queries/terraform/oci/oci_idp_group_mapping_change_event_rule_missing/test/positive2.tf @@ -0,0 +1,24 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_events_rule" "disabled_mapping_rule" { + display_name = "DisabledMappingRule" + compartment_id = "ocid1.tenancy..." + description = "Monitors IdP Mapping but is disabled" + + condition = jsonencode({ + "eventType": [ + "com.oraclecloud.identitycontrolplane.updateidpgroupmapping" + ] + }) + + actions { + actions { + action_type = "ONS" + topic_id = "ocid1.onstopic..." + } + } + + is_enabled = false +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_idp_group_mapping_change_event_rule_missing/test/positive_expected_result.json b/assets/queries/terraform/oci/oci_idp_group_mapping_change_event_rule_missing/test/positive_expected_result.json new file mode 100644 index 00000000000..fc56ddcb96d --- /dev/null +++ b/assets/queries/terraform/oci/oci_idp_group_mapping_change_event_rule_missing/test/positive_expected_result.json @@ -0,0 +1,14 @@ +[ + { + "queryName": "Event Rule for IdP Group Mapping Changes is Missing", + "severity": "MEDIUM", + "line": 1, + "fileName": "positive1.tf" + }, + { + "queryName": "Event Rule for IdP Group Mapping Changes is Missing", + "severity": "MEDIUM", + "line": 23, + "fileName": "positive2.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_instance_transit_encryption/README.md b/assets/queries/terraform/oci/oci_instance_transit_encryption/README.md new file mode 100644 index 00000000000..3d6ee853be2 --- /dev/null +++ b/assets/queries/terraform/oci/oci_instance_transit_encryption/README.md @@ -0,0 +1,54 @@ +# Regla KICS: OCI Instance In-Transit Encryption Disabled + +## Descripción General + +Esta regla de severidad **MEDIA** audita el recurso `oci_core_instance` para verificar que el cifrado en tránsito esté habilitado. + +En OCI, la opción `is_pv_encryption_in_transit_enabled` dentro de `launch_options` habilita el cifrado de los datos transferidos entre la instancia de computación y los volúmenes de arranque o de bloques paravirtualizados. Esto asegura la confidencialidad de los datos mientras viajan por la red interna del centro de datos. + +## Lógica de la Regla + +La regla verifica tres escenarios distintos: + +1. **Valor Incorrecto:** El bloque `launch_options` existe y el atributo `is_pv_encryption_in_transit_enabled` está explícitamente en `false`. +2. **Atributo Faltante:** El bloque `launch_options` existe, pero falta el atributo `is_pv_encryption_in_transit_enabled` (lo que puede aplicar un valor por defecto inseguro). +3. **Bloque Faltante:** No existe el bloque `launch_options` en la definición del recurso. + +## Casos de Fallo Detectados + +### Caso 1: Cifrado Deshabilitado Explícitamente +* **Descripción:** Se ha configurado `is_pv_encryption_in_transit_enabled = false`. +* **Ubicación de la Alerta:** Atributo `is_pv_encryption_in_transit_enabled`. + +### Caso 2: Atributo Faltante en Opciones +* **Descripción:** El bloque `launch_options` está definido, pero no se especifica el cifrado, confiando en el valor predeterminado del proveedor. +* **Ubicación de la Alerta:** Bloque `launch_options`. + +### Caso 3: Bloque de Opciones Ausente +* **Descripción:** La instancia no define `launch_options`, por lo que no se está forzando el cifrado en tránsito. +* **Ubicación de la Alerta:** Recurso `oci_core_instance`. + +## Recurso Involucrado +* `oci_core_instance` + +## Solución + +Habilita explícitamente el cifrado en tránsito dentro de las opciones de lanzamiento. + +```terraform +resource "oci_core_instance" "secure_instance" { + availability_domain = var.availability_domain + compartment_id = var.compartment_id + shape = "VM.Standard.E4.Flex" + + # Bloque requerido para la seguridad en tránsito + launch_options { + boot_volume_type = "PARAVIRTUALIZED" + is_pv_encryption_in_transit_enabled = true + } + + source_details { + source_type = "image" + source_id = var.image_id + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_instance_transit_encryption/metadata.json b/assets/queries/terraform/oci/oci_instance_transit_encryption/metadata.json new file mode 100644 index 00000000000..e5173108120 --- /dev/null +++ b/assets/queries/terraform/oci/oci_instance_transit_encryption/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "c29ef8f7-33bc-4788-9085-3a561c91f7d1", + "queryName": "OCI Instance In-Transit Encryption Disabled", + "severity": "MEDIUM", + "category": "Encryption", + "descriptionText": "Compute instances should have in-transit encryption enabled for data moving between the instance and block storage. This is configured via 'is_pv_encryption_in_transit_enabled' in the 'launch_options' block.", + "descriptionUrl": "https://registry.terraform.io/providers/oracle/oci/latest/docs/resources/core_instance#is_pv_encryption_in_transit_enabled", + "platform": "Terraform", + "descriptionID": "c29ef8f7", + "cloudProvider": "oci", + "cwe": "CWE-311", + "riskScore": 3.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_instance_transit_encryption/query.rego b/assets/queries/terraform/oci/oci_instance_transit_encryption/query.rego new file mode 100644 index 00000000000..2dac4c0608d --- /dev/null +++ b/assets/queries/terraform/oci/oci_instance_transit_encryption/query.rego @@ -0,0 +1,58 @@ +package Cx + +ensure_array(x) = x { is_array(x) } +ensure_array(x) = [x] { is_object(x) } + +# CASO 1: El bloque launch_options existe, pero la opción está explícitamente en FALSE. +CxPolicy[result] { + doc := input.document[i] + instance := doc.resource.oci_core_instance[name] + + options := ensure_array(instance.launch_options) + opt := options[_] + + opt.is_pv_encryption_in_transit_enabled == false + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.oci_core_instance.%s.launch_options.is_pv_encryption_in_transit_enabled", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "'is_pv_encryption_in_transit_enabled' should be set to true", + "keyActualValue": "'is_pv_encryption_in_transit_enabled' is set to false", + } +} + +# CASO 2: El bloque launch_options existe, pero FALTA el atributo (default es false/inseguro). +CxPolicy[result] { + doc := input.document[i] + instance := doc.resource.oci_core_instance[name] + + options := ensure_array(instance.launch_options) + opt := options[_] + + object.get(opt, "is_pv_encryption_in_transit_enabled", "undefined") == "undefined" + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.oci_core_instance.%s.launch_options", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "'is_pv_encryption_in_transit_enabled' should be defined and set to true", + "keyActualValue": "'is_pv_encryption_in_transit_enabled' is missing", + } +} + +# CASO 3: FALTA el bloque launch_options completo. +CxPolicy[result] { + doc := input.document[i] + instance := doc.resource.oci_core_instance[name] + + not instance.launch_options + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.oci_core_instance.%s", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "'launch_options' block with 'is_pv_encryption_in_transit_enabled = true' should be defined", + "keyActualValue": "'launch_options' block is missing", + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_instance_transit_encryption/test/negative1.tf b/assets/queries/terraform/oci/oci_instance_transit_encryption/test/negative1.tf new file mode 100644 index 00000000000..59c69e1a07d --- /dev/null +++ b/assets/queries/terraform/oci/oci_instance_transit_encryption/test/negative1.tf @@ -0,0 +1,14 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_core_instance" "instance_compliant" { + availability_domain = "AD-1" + compartment_id = "ocid1.compartment..." + shape = "VM.Standard.E4.Flex" + + launch_options { + boot_volume_type = "PARAVIRTUALIZED" + is_pv_encryption_in_transit_enabled = true + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_instance_transit_encryption/test/positive1.tf b/assets/queries/terraform/oci/oci_instance_transit_encryption/test/positive1.tf new file mode 100644 index 00000000000..a0fc32fdc32 --- /dev/null +++ b/assets/queries/terraform/oci/oci_instance_transit_encryption/test/positive1.tf @@ -0,0 +1,20 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_core_instance" "instance_explicit_false" { + availability_domain = "AD-1" + compartment_id = "ocid1.compartment..." + shape = "VM.Standard.E4.Flex" + + launch_options { + boot_volume_type = "PARAVIRTUALIZED" + # FALLO: Explícitamente false (sin comentario pegado encima) + is_pv_encryption_in_transit_enabled = false + } + + source_details { + source_id = "ocid1.image..." + source_type = "image" + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_instance_transit_encryption/test/positive2.tf b/assets/queries/terraform/oci/oci_instance_transit_encryption/test/positive2.tf new file mode 100644 index 00000000000..3a1ca5f3519 --- /dev/null +++ b/assets/queries/terraform/oci/oci_instance_transit_encryption/test/positive2.tf @@ -0,0 +1,15 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_core_instance" "instance_missing_attribute" { + availability_domain = "AD-1" + compartment_id = "ocid1.compartment..." + shape = "VM.Standard.E4.Flex" + + # El bloque existe, pero falta el atributo de encriptación + launch_options { + boot_volume_type = "PARAVIRTUALIZED" + network_type = "PARAVIRTUALIZED" + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_instance_transit_encryption/test/positive3.tf b/assets/queries/terraform/oci/oci_instance_transit_encryption/test/positive3.tf new file mode 100644 index 00000000000..a6da327c592 --- /dev/null +++ b/assets/queries/terraform/oci/oci_instance_transit_encryption/test/positive3.tf @@ -0,0 +1,15 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_core_instance" "instance_missing_block" { + availability_domain = "AD-1" + compartment_id = "ocid1.compartment..." + shape = "VM.Standard.E4.Flex" + + # No existe el bloque launch_options + source_details { + source_id = "ocid1.image..." + source_type = "image" + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_instance_transit_encryption/test/positive_expected_result.json b/assets/queries/terraform/oci/oci_instance_transit_encryption/test/positive_expected_result.json new file mode 100644 index 00000000000..2099845fc82 --- /dev/null +++ b/assets/queries/terraform/oci/oci_instance_transit_encryption/test/positive_expected_result.json @@ -0,0 +1,20 @@ +[ + { + "queryName": "OCI Instance In-Transit Encryption Disabled", + "severity": "MEDIUM", + "line": 13, + "fileName": "positive1.tf" + }, + { + "queryName": "OCI Instance In-Transit Encryption Disabled", + "severity": "MEDIUM", + "line": 11, + "fileName": "positive2.tf" + }, + { + "queryName": "OCI Instance In-Transit Encryption Disabled", + "severity": "MEDIUM", + "line": 5, + "fileName": "positive3.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_local_user_authentication_event_rule_missing/README.md b/assets/queries/terraform/oci/oci_local_user_authentication_event_rule_missing/README.md new file mode 100644 index 00000000000..dc40f8f9440 --- /dev/null +++ b/assets/queries/terraform/oci/oci_local_user_authentication_event_rule_missing/README.md @@ -0,0 +1,45 @@ +# Regla KICS: Notificación para Autenticación de Usuarios Locales en OCI + +## Descripción General + +Esta regla de KICS para Terraform asegura que exista una regla de eventos (`oci_events_rule`) configurada para generar notificaciones cada vez que un usuario local de IAM (no federado) se autentica en la cuenta de OCI. + +Los usuarios locales de IAM son aquellos definidos directamente en OCI. Monitorizar sus inicios de sesión es una medida de seguridad fundamental para detectar actividad sospechosa, como inicios de sesión desde ubicaciones inusuales o intentos de fuerza bruta. + +## Lógica de la Regla + +La política verifica que exista configuración activa para capturar el siguiente evento crítico: +* `com.oraclecloud.identity.localuser.authenticate` + +## Casos de Fallo Detectados + +### Caso 1: Regla Ausente (Missing) +* **Descripción:** No existe ninguna regla configurada para monitorear la autenticación de usuarios locales. +* **Ubicación de la Alerta:** Bloque `provider "oci"`. + +### Caso 2: Regla Deshabilitada (Disabled) +* **Descripción:** Existe una regla que monitorea el evento correcto, pero está explícitamente deshabilitada (`is_enabled = false`). +* **Ubicación de la Alerta:** Atributo `is_enabled` dentro del recurso `oci_events_rule`. + +## Solución + +```terraform +resource "oci_events_rule" "local_user_auth_rule" { + display_name = "audit-rule-for-local-user-authentication" + description = "Alerts on any local IAM user authentication event" + compartment_id = var.tenancy_ocid + is_enabled = true + + condition = jsonencode({ + eventType = [ + "com.oraclecloud.identity.localuser.authenticate" + ] + }) + + actions { + actions { + action_type = "ONS" + topic_id = oci_ons_notification_topic.security_alerts_topic.id + } + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_local_user_authentication_event_rule_missing/metadata.json b/assets/queries/terraform/oci/oci_local_user_authentication_event_rule_missing/metadata.json new file mode 100644 index 00000000000..94d694b39b7 --- /dev/null +++ b/assets/queries/terraform/oci/oci_local_user_authentication_event_rule_missing/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "e0c61731-a692-44d8-bbf2-8279985a4e41", + "queryName": "Event Rule for Local User Authentication is Missing", + "severity": "HIGH", + "category": "Access Control", + "descriptionText": "Ensures that an OCI event rule is configured to monitor and alert on authentication events from local IAM users. Auditing local user logins is a critical security measure to detect unauthorized access attempts and compromised credentials.", + "descriptionUrl": "https://registry.terraform.io/providers/oracle/oci/latest/docs/resources/events_rule", + "platform": "Terraform", + "descriptionID": "e0c61731", + "cloudProvider": "oci", + "cwe": "CWE-778", + "riskScore": 6.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_local_user_authentication_event_rule_missing/query.rego b/assets/queries/terraform/oci/oci_local_user_authentication_event_rule_missing/query.rego new file mode 100644 index 00000000000..7bb5a1ce53d --- /dev/null +++ b/assets/queries/terraform/oci/oci_local_user_authentication_event_rule_missing/query.rego @@ -0,0 +1,43 @@ +package Cx + +expected_event := "com.oraclecloud.identity.localuser.authenticate" + +# REGLA 1: Missing (Global) +# No existe NINGUNA regla en el proyecto que monitoree el evento de autenticación local. +CxPolicy[result] { + doc := input.document[i] + _ := doc.provider.oci + + any_auth_rule := [rule | + rule := input.document[_].resource.oci_events_rule[_] + contains(rule.condition, expected_event) + ] + + count(any_auth_rule) == 0 + + result := { + "documentId": doc.id, + "searchKey": "provider.oci", + "issueType": "MissingAttribute", + "keyExpectedValue": "An 'oci_events_rule' for local user authentication events should exist", + "keyActualValue": "No 'oci_events_rule' found for local user authentication", + } +} + +# REGLA 2: Disabled (Local) +# La regla existe y monitorea autenticación, pero está deshabilitada. +CxPolicy[result] { + rule := input.document[i].resource.oci_events_rule[name] + + contains(rule.condition, expected_event) + + rule.is_enabled == false + + result := { + "documentId": input.document[i].id, + "searchKey": sprintf("resource.oci_events_rule.%s.is_enabled", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "'is_enabled' should be true", + "keyActualValue": "'is_enabled' is false", + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_local_user_authentication_event_rule_missing/test/negative1.tf b/assets/queries/terraform/oci/oci_local_user_authentication_event_rule_missing/test/negative1.tf new file mode 100644 index 00000000000..1f866cc41e8 --- /dev/null +++ b/assets/queries/terraform/oci/oci_local_user_authentication_event_rule_missing/test/negative1.tf @@ -0,0 +1,23 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_events_rule" "correct_auth_rule" { + display_name = "CorrectAuthRule" + compartment_id = "ocid1.tenancy..." + is_enabled = true + + condition = jsonencode({ + "eventType": [ + "com.oraclecloud.identity.localuser.authenticate" + ] + }) + + actions { + actions { + action_type = "ONS" + topic_id = "ocid1.onstopic..." + is_enabled = true + } + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_local_user_authentication_event_rule_missing/test/positive1.tf b/assets/queries/terraform/oci/oci_local_user_authentication_event_rule_missing/test/positive1.tf new file mode 100644 index 00000000000..6b07c9992ba --- /dev/null +++ b/assets/queries/terraform/oci/oci_local_user_authentication_event_rule_missing/test/positive1.tf @@ -0,0 +1,20 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_events_rule" "other_rule" { + display_name = "StorageRule" + compartment_id = "ocid1.tenancy..." + is_enabled = true + + condition = jsonencode({ + "eventType": ["com.oraclecloud.objectstorage.createbucket"] + }) + + actions { + actions { + action_type = "ONS" + topic_id = "ocid1.onstopic..." + } + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_local_user_authentication_event_rule_missing/test/positive2.tf b/assets/queries/terraform/oci/oci_local_user_authentication_event_rule_missing/test/positive2.tf new file mode 100644 index 00000000000..b34fdad393b --- /dev/null +++ b/assets/queries/terraform/oci/oci_local_user_authentication_event_rule_missing/test/positive2.tf @@ -0,0 +1,24 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_events_rule" "disabled_auth_rule" { + display_name = "DisabledAuthRule" + compartment_id = "ocid1.tenancy..." + description = "Monitors Auth but is disabled" + + condition = jsonencode({ + "eventType": [ + "com.oraclecloud.identity.localuser.authenticate" + ] + }) + + actions { + actions { + action_type = "ONS" + topic_id = "ocid1.onstopic..." + } + } + + is_enabled = false +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_local_user_authentication_event_rule_missing/test/positive_expected_result.json b/assets/queries/terraform/oci/oci_local_user_authentication_event_rule_missing/test/positive_expected_result.json new file mode 100644 index 00000000000..3f13367b2a8 --- /dev/null +++ b/assets/queries/terraform/oci/oci_local_user_authentication_event_rule_missing/test/positive_expected_result.json @@ -0,0 +1,14 @@ +[ + { + "queryName": "Event Rule for Local User Authentication is Missing", + "severity": "HIGH", + "line": 1, + "fileName": "positive1.tf" + }, + { + "queryName": "Event Rule for Local User Authentication is Missing", + "severity": "HIGH", + "line": 23, + "fileName": "positive2.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_network_gateway_change_event_rule_missing/README.md b/assets/queries/terraform/oci/oci_network_gateway_change_event_rule_missing/README.md new file mode 100644 index 00000000000..6c44e75e559 --- /dev/null +++ b/assets/queries/terraform/oci/oci_network_gateway_change_event_rule_missing/README.md @@ -0,0 +1,67 @@ +# Regla KICS: Notificación para Cambios en Gateways de Red en OCI + +## Descripción General + +Esta regla de KICS para Terraform asegura que exista una regla de eventos (`oci_events_rule`) configurada para monitorizar y generar alertas ante cualquier cambio (creación, actualización o eliminación) en los gateways de red de la cuenta de OCI. + +Los gateways de red (Internet Gateway, NAT Gateway, Service Gateway, DRG, LPG) son los puntos de entrada y salida del tráfico de una VCN. + +## Lógica de la Regla + +La política verifica que exista configuración activa para capturar los 15 eventos relacionados con el ciclo de vida de: +* Internet Gateway +* NAT Gateway +* Service Gateway +* Dynamic Routing Gateway (DRG) +* Local Peering Gateway (LPG) + +## Casos de Fallo Detectados + +### Caso 1: Regla Ausente (Missing) +* **Descripción:** No existe ninguna regla configurada para gateways en el proyecto. +* **Ubicación:** Bloque `provider "oci"`. + +### Caso 2: Regla Incompleta (Incomplete) +* **Descripción:** Existe una regla para gateways, pero le faltan tipos de eventos (ej. monitoriza Internet Gateway pero olvida DRG). +* **Ubicación:** Atributo `condition`. + +### Caso 3: Regla Deshabilitada (Disabled) +* **Descripción:** Existe una regla relevante pero está explícitamente deshabilitada (`is_enabled = false`). +* **Ubicación:** Atributo `is_enabled`. + +## Solución + +```terraform +resource "oci_events_rule" "network_gateway_change_rule" { + display_name = "audit-rule-for-network-gateway-changes" + description = "Alerts on any creation, update, or deletion of Network Gateways" + compartment_id = var.tenancy_ocid + is_enabled = true + + condition = jsonencode({ + eventType = [ + "com.oraclecloud.virtualnetwork.createinternetgateway", + "com.oraclecloud.virtualnetwork.updateinternetgateway", + "com.oraclecloud.virtualnetwork.deleteinternetgateway", + "com.oraclecloud.virtualnetwork.createnatgateway", + "com.oraclecloud.virtualnetwork.updatenatgateway", + "com.oraclecloud.virtualnetwork.deletenatgateway", + "com.oraclecloud.virtualnetwork.createservicegateway", + "com.oraclecloud.virtualnetwork.updateservicegateway", + "com.oraclecloud.virtualnetwork.deleteservicegateway", + "com.oraclecloud.virtualnetwork.createdrg", + "com.oraclecloud.virtualnetwork.updatedrg", + "com.oraclecloud.virtualnetwork.deletedrg", + "com.oraclecloud.virtualnetwork.createlocalpeeringgateway", + "com.oraclecloud.virtualnetwork.updatelocalpeeringgateway", + "com.oraclecloud.virtualnetwork.deletelocalpeeringgateway" + ] + }) + + actions { + actions { + action_type = "ONS" + topic_id = oci_ons_notification_topic.security_alerts_topic.id + } + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_network_gateway_change_event_rule_missing/metadata.json b/assets/queries/terraform/oci/oci_network_gateway_change_event_rule_missing/metadata.json new file mode 100644 index 00000000000..b289b68450e --- /dev/null +++ b/assets/queries/terraform/oci/oci_network_gateway_change_event_rule_missing/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "e3ade454-28a1-4a0c-9905-b73e82a648c0", + "queryName": "Event Rule for Network Gateway Changes is Missing", + "severity": "MEDIUM", + "category": "Networking and Firewall", + "descriptionText": "Ensures that an OCI event rule is configured to monitor and alert on changes to all types of Network Gateways (Internet, NAT, Service, DRG, etc.). Auditing gateway changes is crucial for detecting unauthorized modifications to network ingress/egress points.", + "descriptionUrl": "https://registry.terraform.io/providers/oracle/oci/latest/docs/resources/events_rule", + "platform": "Terraform", + "descriptionID": "e3ade454", + "cloudProvider": "oci", + "cwe": "CWE-778", + "riskScore": 3.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_network_gateway_change_event_rule_missing/query.rego b/assets/queries/terraform/oci/oci_network_gateway_change_event_rule_missing/query.rego new file mode 100644 index 00000000000..d2e8bf40def --- /dev/null +++ b/assets/queries/terraform/oci/oci_network_gateway_change_event_rule_missing/query.rego @@ -0,0 +1,88 @@ +package Cx + +expected_event_types := [ + "com.oraclecloud.virtualnetwork.createinternetgateway", + "com.oraclecloud.virtualnetwork.updateinternetgateway", + "com.oraclecloud.virtualnetwork.deleteinternetgateway", + "com.oraclecloud.virtualnetwork.createnatgateway", + "com.oraclecloud.virtualnetwork.updatenatgateway", + "com.oraclecloud.virtualnetwork.deletenatgateway", + "com.oraclecloud.virtualnetwork.createservicegateway", + "com.oraclecloud.virtualnetwork.updateservicegateway", + "com.oraclecloud.virtualnetwork.deleteservicegateway", + "com.oraclecloud.virtualnetwork.createdrg", + "com.oraclecloud.virtualnetwork.updatedrg", + "com.oraclecloud.virtualnetwork.deletedrg", + "com.oraclecloud.virtualnetwork.createlocalpeeringgateway", + "com.oraclecloud.virtualnetwork.updatelocalpeeringgateway", + "com.oraclecloud.virtualnetwork.deletelocalpeeringgateway" +] + +# REGLA 1: Missing (Global) +# No existe NINGUNA regla en el proyecto que monitoree Gateways. +CxPolicy[result] { + doc := input.document[i] + _ := doc.provider.oci + + any_gateway_rule := [rule | + rule := input.document[_].resource.oci_events_rule[_] + event := expected_event_types[_] + contains(rule.condition, event) + ] + + count(any_gateway_rule) == 0 + + result := { + "documentId": doc.id, + "searchKey": "provider.oci", + "issueType": "MissingAttribute", + "keyExpectedValue": "An 'oci_events_rule' for Network Gateway changes should exist", + "keyActualValue": "No 'oci_events_rule' found for Network Gateway changes", + } +} + +# REGLA 2: Incomplete (Local) +# La regla existe, pero le faltan eventos (ej. tiene IGW pero falta NAT). +CxPolicy[result] { + rule := input.document[i].resource.oci_events_rule[name] + + matches := [event | + event := expected_event_types[_] + contains(rule.condition, event) + ] + + count(matches) > 0 + count(matches) < count(expected_event_types) + + missing_count := count(expected_event_types) - count(matches) + + result := { + "documentId": input.document[i].id, + "searchKey": sprintf("resource.oci_events_rule.%s.condition", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "The rule condition should include all 15 Network Gateway events", + "keyActualValue": sprintf("The rule is missing %d Network Gateway event(s)", [missing_count]), + } +} + +# REGLA 3: Disabled (Local) +# La regla es relevante (gateways) pero está apagada. +CxPolicy[result] { + rule := input.document[i].resource.oci_events_rule[name] + + matches := [event | + event := expected_event_types[_] + contains(rule.condition, event) + ] + count(matches) > 0 + + rule.is_enabled == false + + result := { + "documentId": input.document[i].id, + "searchKey": sprintf("resource.oci_events_rule.%s.is_enabled", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "'is_enabled' should be true", + "keyActualValue": "'is_enabled' is false", + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_network_gateway_change_event_rule_missing/test/negative1.tf b/assets/queries/terraform/oci/oci_network_gateway_change_event_rule_missing/test/negative1.tf new file mode 100644 index 00000000000..9bda51072cf --- /dev/null +++ b/assets/queries/terraform/oci/oci_network_gateway_change_event_rule_missing/test/negative1.tf @@ -0,0 +1,43 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_events_rule" "compliant_gateway_rule" { + display_name = "CompliantGatewayRule" + compartment_id = "ocid1.tenancy.oc1..aaaa" + description = "Monitors all 15 Network Gateway events" + is_enabled = true + + condition = jsonencode({ + "eventType": [ + # Internet Gateway + "com.oraclecloud.virtualnetwork.createinternetgateway", + "com.oraclecloud.virtualnetwork.updateinternetgateway", + "com.oraclecloud.virtualnetwork.deleteinternetgateway", + # NAT Gateway + "com.oraclecloud.virtualnetwork.createnatgateway", + "com.oraclecloud.virtualnetwork.updatenatgateway", + "com.oraclecloud.virtualnetwork.deletenatgateway", + # Service Gateway + "com.oraclecloud.virtualnetwork.createservicegateway", + "com.oraclecloud.virtualnetwork.updateservicegateway", + "com.oraclecloud.virtualnetwork.deleteservicegateway", + # DRG (Dynamic Routing Gateway) + "com.oraclecloud.virtualnetwork.createdrg", + "com.oraclecloud.virtualnetwork.updatedrg", + "com.oraclecloud.virtualnetwork.deletedrg", + # LPG (Local Peering Gateway) + "com.oraclecloud.virtualnetwork.createlocalpeeringgateway", + "com.oraclecloud.virtualnetwork.updatelocalpeeringgateway", + "com.oraclecloud.virtualnetwork.deletelocalpeeringgateway" + ] + }) + + actions { + actions { + action_type = "ONS" + topic_id = "ocid1.onstopic.oc1.iad.aaaa" + is_enabled = true + } + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_network_gateway_change_event_rule_missing/test/positive1.tf b/assets/queries/terraform/oci/oci_network_gateway_change_event_rule_missing/test/positive1.tf new file mode 100644 index 00000000000..98b6d530f94 --- /dev/null +++ b/assets/queries/terraform/oci/oci_network_gateway_change_event_rule_missing/test/positive1.tf @@ -0,0 +1,8 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_core_vcn" "test_vcn" { + cidr_block = "10.0.0.0/16" + compartment_id = "ocid1.compartment..." +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_network_gateway_change_event_rule_missing/test/positive2.tf b/assets/queries/terraform/oci/oci_network_gateway_change_event_rule_missing/test/positive2.tf new file mode 100644 index 00000000000..00a9fbefb4e --- /dev/null +++ b/assets/queries/terraform/oci/oci_network_gateway_change_event_rule_missing/test/positive2.tf @@ -0,0 +1,25 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_events_rule" "incomplete_gateway_rule" { + display_name = "IncompleteGatewayRule" + compartment_id = "ocid1.tenancy..." + is_enabled = true + + # FALLO: Solo tiene Internet Gateway, faltan los otros 12 eventos + condition = jsonencode({ + "eventType": [ + "com.oraclecloud.virtualnetwork.createinternetgateway", + "com.oraclecloud.virtualnetwork.updateinternetgateway", + "com.oraclecloud.virtualnetwork.deleteinternetgateway" + ] + }) + + actions { + actions { + action_type = "ONS" + topic_id = "ocid1.onstopic..." + } + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_network_gateway_change_event_rule_missing/test/positive3.tf b/assets/queries/terraform/oci/oci_network_gateway_change_event_rule_missing/test/positive3.tf new file mode 100644 index 00000000000..82360d3d5b3 --- /dev/null +++ b/assets/queries/terraform/oci/oci_network_gateway_change_event_rule_missing/test/positive3.tf @@ -0,0 +1,38 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_events_rule" "disabled_gateway_rule" { + display_name = "DisabledGatewayRule" + compartment_id = "ocid1.tenancy..." + description = "Has all events but is disabled" + + condition = jsonencode({ + "eventType": [ + "com.oraclecloud.virtualnetwork.createinternetgateway", + "com.oraclecloud.virtualnetwork.updateinternetgateway", + "com.oraclecloud.virtualnetwork.deleteinternetgateway", + "com.oraclecloud.virtualnetwork.createnatgateway", + "com.oraclecloud.virtualnetwork.updatenatgateway", + "com.oraclecloud.virtualnetwork.deletenatgateway", + "com.oraclecloud.virtualnetwork.createservicegateway", + "com.oraclecloud.virtualnetwork.updateservicegateway", + "com.oraclecloud.virtualnetwork.deleteservicegateway", + "com.oraclecloud.virtualnetwork.createdrg", + "com.oraclecloud.virtualnetwork.updatedrg", + "com.oraclecloud.virtualnetwork.deletedrg", + "com.oraclecloud.virtualnetwork.createlocalpeeringgateway", + "com.oraclecloud.virtualnetwork.updatelocalpeeringgateway", + "com.oraclecloud.virtualnetwork.deletelocalpeeringgateway" + ] + }) + + actions { + actions { + action_type = "ONS" + topic_id = "ocid1.onstopic..." + } + } + + is_enabled = false +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_network_gateway_change_event_rule_missing/test/positive_expected_result.json b/assets/queries/terraform/oci/oci_network_gateway_change_event_rule_missing/test/positive_expected_result.json new file mode 100644 index 00000000000..170738f4e34 --- /dev/null +++ b/assets/queries/terraform/oci/oci_network_gateway_change_event_rule_missing/test/positive_expected_result.json @@ -0,0 +1,20 @@ +[ + { + "queryName": "Event Rule for Network Gateway Changes is Missing", + "severity": "MEDIUM", + "line": 1, + "fileName": "positive1.tf" + }, + { + "queryName": "Event Rule for Network Gateway Changes is Missing", + "severity": "MEDIUM", + "line": 11, + "fileName": "positive2.tf" + }, + { + "queryName": "Event Rule for Network Gateway Changes is Missing", + "severity": "MEDIUM", + "line": 37, + "fileName": "positive3.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_notification_topic_without_subscription/README.md b/assets/queries/terraform/oci/oci_notification_topic_without_subscription/README.md new file mode 100644 index 00000000000..b76a8721257 --- /dev/null +++ b/assets/queries/terraform/oci/oci_notification_topic_without_subscription/README.md @@ -0,0 +1,44 @@ +# Regla KICS: Tópicos de Notificación con Suscripción en OCI + +## Descripción General + +Esta regla de KICS para Terraform asegura que los tópicos de notificación del servicio OCI Notifications (`oci_ons_notification_topic`) tengan al menos una suscripción (`oci_ons_subscription`) asociada. + +El servicio de notificaciones de OCI se usa para desacoplar componentes y enviar alertas. Un "tópico" es el canal al que se publican los mensajes, pero sin una "suscripción" (email, función, webhook, etc.), esos mensajes no se entregan a ningún destino y se pierden. + +## Lógica de la Regla + +La política se divide en dos reglas para cubrir los posibles escenarios de fallo: +1. **Ausencia Global:** No hay tópicos de notificación definidos. +2. **Tópico Huérfano:** Existe un tópico que no está referenciado por ninguna suscripción. + +## Casos de Fallo Detectados + +### Caso 1: Recurso `oci_ons_notification_topic` Ausente +* **Descripción:** No se encuentra ni un solo recurso de tipo `oci_ons_notification_topic` en el proyecto. +* **Ubicación de la Alerta:** Bloque `provider "oci"`. + +### Caso 2: Tópico sin Suscripciones Asociadas +* **Descripción:** Existe un recurso `oci_ons_notification_topic`, pero no existe ningún `oci_ons_subscription` cuyo `topic_id` apunte a él. +* **Ubicación de la Alerta:** Recurso `oci_ons_notification_topic`. + +## Solución + +Asegúrate de que exista al menos un tópico y que tenga una suscripción enlazada: + +```terraform +resource "oci_ons_notification_topic" "critical_alerts" { + compartment_id = var.compartment_id + name = "my-critical-alerts-topic" +} + +# RECURSO REQUERIDO: La suscripción que enlaza al tópico +resource "oci_ons_subscription" "email_subscription" { + compartment_id = var.compartment_id + + # Referencia cruzada al tópico + topic_id = oci_ons_notification_topic.critical_alerts.id + + protocol = "EMAIL" + endpoint = "ops-team@example.com" +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_notification_topic_without_subscription/metadata.json b/assets/queries/terraform/oci/oci_notification_topic_without_subscription/metadata.json new file mode 100644 index 00000000000..17933e50ae3 --- /dev/null +++ b/assets/queries/terraform/oci/oci_notification_topic_without_subscription/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "8e61709a-7ba2-4d51-a8ef-796a45e7c82c", + "queryName": "Notification Topic Without Subscription", + "severity": "MEDIUM", + "category": "Observability", + "descriptionText": "Ensures that OCI notification topics have at least one active subscription to deliver alerts. A topic without a subscription receives messages but does not send them to any destination, rendering monitoring alerts ineffective.", + "descriptionUrl": "https://registry.terraform.io/providers/oracle/oci/latest/docs/resources/ons_subscription", + "platform": "Terraform", + "descriptionID": "8e61709a", + "cloudProvider": "oci", + "cwe": "CWE-778", + "riskScore": 3.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_notification_topic_without_subscription/query.rego b/assets/queries/terraform/oci/oci_notification_topic_without_subscription/query.rego new file mode 100644 index 00000000000..471b4476c5e --- /dev/null +++ b/assets/queries/terraform/oci/oci_notification_topic_without_subscription/query.rego @@ -0,0 +1,44 @@ +package Cx + +# REGLA 1: Missing (Global) +# No existe ningún recurso 'oci_ons_notification_topic' en todo el documento. +CxPolicy[result] { + doc := input.document[i] + _ := doc.provider.oci + + all_topics := [topic | + topic := input.document[_].resource.oci_ons_notification_topic[_] + ] + + count(all_topics) == 0 + + result := { + "documentId": doc.id, + "searchKey": "provider.oci", + "issueType": "MissingAttribute", + "keyExpectedValue": "At least one 'oci_ons_notification_topic' resource should exist", + "keyActualValue": "No 'oci_ons_notification_topic' resource was found", + } +} + +# REGLA 2: Orphan Topic (Local) +# Existe un tópico, pero ninguna suscripción apunta a él. +CxPolicy[result] { + doc := input.document[i] + _ := doc.resource.oci_ons_notification_topic[topic_name] + + matching_subscriptions := [sub | + sub := input.document[_].resource.oci_ons_subscription[_] + contains(sub.topic_id, topic_name) + ] + + count(matching_subscriptions) == 0 + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.oci_ons_notification_topic.%s", [topic_name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "'oci_ons_notification_topic' should have at least one associated 'oci_ons_subscription'", + "keyActualValue": "'oci_ons_notification_topic' has no subscriptions", + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_notification_topic_without_subscription/test/negative1.tf b/assets/queries/terraform/oci/oci_notification_topic_without_subscription/test/negative1.tf new file mode 100644 index 00000000000..85ed7f78d30 --- /dev/null +++ b/assets/queries/terraform/oci/oci_notification_topic_without_subscription/test/negative1.tf @@ -0,0 +1,16 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_ons_notification_topic" "linked_topic" { + compartment_id = "ocid1.compartment..." + name = "linked-topic" +} + +# Esta suscripción apunta correctamente al tópico de arriba +resource "oci_ons_subscription" "sub_for_linked" { + compartment_id = "ocid1.compartment..." + topic_id = oci_ons_notification_topic.linked_topic.id + protocol = "EMAIL" + endpoint = "test@example.com" +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_notification_topic_without_subscription/test/positive1.tf b/assets/queries/terraform/oci/oci_notification_topic_without_subscription/test/positive1.tf new file mode 100644 index 00000000000..d4d1b09e80c --- /dev/null +++ b/assets/queries/terraform/oci/oci_notification_topic_without_subscription/test/positive1.tf @@ -0,0 +1,8 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_core_vcn" "example_vcn" { + compartment_id = "ocid1.compartment..." + cidr_block = "10.0.0.0/16" +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_notification_topic_without_subscription/test/positive2.tf b/assets/queries/terraform/oci/oci_notification_topic_without_subscription/test/positive2.tf new file mode 100644 index 00000000000..45e44aa7f54 --- /dev/null +++ b/assets/queries/terraform/oci/oci_notification_topic_without_subscription/test/positive2.tf @@ -0,0 +1,9 @@ +provider "oci" { + region = "us-ashburn-1" +} + +# Caso: Tópico existe pero no tiene suscripción que lo apunte +resource "oci_ons_notification_topic" "orphan_topic" { + compartment_id = "ocid1.compartment..." + name = "orphan-topic" +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_notification_topic_without_subscription/test/positive_expected_result.json b/assets/queries/terraform/oci/oci_notification_topic_without_subscription/test/positive_expected_result.json new file mode 100644 index 00000000000..3cc75e994c7 --- /dev/null +++ b/assets/queries/terraform/oci/oci_notification_topic_without_subscription/test/positive_expected_result.json @@ -0,0 +1,14 @@ +[ + { + "queryName": "Notification Topic Without Subscription", + "severity": "MEDIUM", + "line": 1, + "fileName": "positive1.tf" + }, + { + "queryName": "Notification Topic Without Subscription", + "severity": "MEDIUM", + "line": 6, + "fileName": "positive2.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_nsg_change_event_rule_missing/README.md b/assets/queries/terraform/oci/oci_nsg_change_event_rule_missing/README.md new file mode 100644 index 00000000000..e137c8f098a --- /dev/null +++ b/assets/queries/terraform/oci/oci_nsg_change_event_rule_missing/README.md @@ -0,0 +1,53 @@ +# Regla KICS: Notificación para Cambios en Grupos de Seguridad de Red (NSG) en OCI + +## Descripción General + +Esta regla de KICS para Terraform asegura que exista una regla de eventos (`oci_events_rule`) configurada para monitorizar y generar alertas ante cualquier cambio (creación, actualización o eliminación) en los Grupos de Seguridad de Red (NSG) de la cuenta de OCI. + +Los NSG proporcionan un firewall virtual para un conjunto de recursos. Un cambio no autorizado en un NSG podría exponer una aplicación a internet o permitir movimientos laterales no deseados. + +## Lógica de la Regla + +La política verifica que exista configuración activa para capturar los tres tipos de eventos críticos de NSG: +1. `com.oraclecloud.virtualnetwork.createnetworksecuritygroup` +2. `com.oraclecloud.virtualnetwork.updatenetworksecuritygroup` +3. `com.oraclecloud.virtualnetwork.deletenetworksecuritygroup` + +## Casos de Fallo Detectados + +### Caso 1: Regla Ausente (Missing) +* **Descripción:** No existe ninguna regla configurada para NSGs en el proyecto. +* **Ubicación de la Alerta:** Bloque `provider "oci"`. + +### Caso 2: Regla Incompleta (Incomplete) +* **Descripción:** Existe una regla para NSGs, pero le faltan eventos (ej. monitoriza create pero olvida delete). +* **Ubicación de la Alerta:** Atributo `condition`. + +### Caso 3: Regla Deshabilitada (Disabled) +* **Descripción:** Existe una regla relevante pero está explícitamente deshabilitada (`is_enabled = false`). +* **Ubicación de la Alerta:** Atributo `is_enabled` dentro del recurso. + +## Solución + +```terraform +resource "oci_events_rule" "nsg_change_rule" { + display_name = "audit-rule-for-nsg-changes" + description = "Alerts on any creation, update, or deletion of Network Security Groups" + compartment_id = var.tenancy_ocid + is_enabled = true + + condition = jsonencode({ + eventType = [ + "com.oraclecloud.virtualnetwork.createnetworksecuritygroup", + "com.oraclecloud.virtualnetwork.updatenetworksecuritygroup", + "com.oraclecloud.virtualnetwork.deletenetworksecuritygroup" + ] + }) + + actions { + actions { + action_type = "ONS" + topic_id = oci_ons_notification_topic.security_alerts_topic.id + } + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_nsg_change_event_rule_missing/metadata.json b/assets/queries/terraform/oci/oci_nsg_change_event_rule_missing/metadata.json new file mode 100644 index 00000000000..f6523663741 --- /dev/null +++ b/assets/queries/terraform/oci/oci_nsg_change_event_rule_missing/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "a1b080a7-8d58-4018-a14d-17beb72f5cef", + "queryName": "Event Rule for NSG Changes is Missing", + "severity": "MEDIUM", + "category": "Networking and Firewall", + "descriptionText": "Ensures that an OCI event rule is configured to monitor and alert on changes to Network Security Groups (NSGs). Auditing changes to NSGs is critical for detecting unauthorized modifications to network micro-segmentation rules that could expose services.", + "descriptionUrl": "https://registry.terraform.io/providers/oracle/oci/latest/docs/resources/events_rule", + "platform": "Terraform", + "descriptionID": "a1b080a7", + "cloudProvider": "oci", + "cwe": "CWE-778", + "riskScore": 3.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_nsg_change_event_rule_missing/query.rego b/assets/queries/terraform/oci/oci_nsg_change_event_rule_missing/query.rego new file mode 100644 index 00000000000..79295221afb --- /dev/null +++ b/assets/queries/terraform/oci/oci_nsg_change_event_rule_missing/query.rego @@ -0,0 +1,76 @@ +package Cx + +expected_event_types := [ + "com.oraclecloud.virtualnetwork.createnetworksecuritygroup", + "com.oraclecloud.virtualnetwork.updatenetworksecuritygroup", + "com.oraclecloud.virtualnetwork.deletenetworksecuritygroup" +] + +# REGLA 1: Missing (Global) +# No existe NINGUNA regla en el proyecto que monitoree NSGs. +CxPolicy[result] { + doc := input.document[i] + _ := doc.provider.oci + + any_nsg_rule := [rule | + rule := input.document[_].resource.oci_events_rule[_] + event := expected_event_types[_] + contains(rule.condition, event) + ] + + count(any_nsg_rule) == 0 + + result := { + "documentId": doc.id, + "searchKey": "provider.oci", + "issueType": "MissingAttribute", + "keyExpectedValue": "An 'oci_events_rule' for Network Security Group changes should exist", + "keyActualValue": "No 'oci_events_rule' found for Network Security Group changes", + } +} + +# REGLA 2: Incomplete (Local) +# La regla existe, pero le faltan eventos (ej: tiene create pero falta delete). +CxPolicy[result] { + rule := input.document[i].resource.oci_events_rule[name] + + matches := [event | + event := expected_event_types[_] + contains(rule.condition, event) + ] + + count(matches) > 0 + count(matches) < count(expected_event_types) + + missing_count := count(expected_event_types) - count(matches) + + result := { + "documentId": input.document[i].id, + "searchKey": sprintf("resource.oci_events_rule.%s.condition", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "The rule condition should include all 3 NSG events (create, update, delete)", + "keyActualValue": sprintf("The rule is missing %d NSG event(s)", [missing_count]), + } +} + +# REGLA 3: Disabled (Local) +# La regla es relevante (NSG) pero está apagada. +CxPolicy[result] { + rule := input.document[i].resource.oci_events_rule[name] + + matches := [event | + event := expected_event_types[_] + contains(rule.condition, event) + ] + count(matches) > 0 + + rule.is_enabled == false + + result := { + "documentId": input.document[i].id, + "searchKey": sprintf("resource.oci_events_rule.%s.is_enabled", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "'is_enabled' should be true", + "keyActualValue": "'is_enabled' is false", + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_nsg_change_event_rule_missing/test/negative1.tf b/assets/queries/terraform/oci/oci_nsg_change_event_rule_missing/test/negative1.tf new file mode 100644 index 00000000000..520332272d4 --- /dev/null +++ b/assets/queries/terraform/oci/oci_nsg_change_event_rule_missing/test/negative1.tf @@ -0,0 +1,25 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_events_rule" "correct_nsg_rule" { + display_name = "CorrectNSGRule" + compartment_id = "ocid1.tenancy..." + is_enabled = true + + condition = jsonencode({ + "eventType": [ + "com.oraclecloud.virtualnetwork.createnetworksecuritygroup", + "com.oraclecloud.virtualnetwork.updatenetworksecuritygroup", + "com.oraclecloud.virtualnetwork.deletenetworksecuritygroup" + ] + }) + + actions { + actions { + action_type = "ONS" + topic_id = "ocid1.onstopic..." + is_enabled = true + } + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_nsg_change_event_rule_missing/test/positive1.tf b/assets/queries/terraform/oci/oci_nsg_change_event_rule_missing/test/positive1.tf new file mode 100644 index 00000000000..7df264f33a7 --- /dev/null +++ b/assets/queries/terraform/oci/oci_nsg_change_event_rule_missing/test/positive1.tf @@ -0,0 +1,9 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_core_network_security_group" "test_nsg" { + compartment_id = "ocid1.compartment..." + display_name = "test-nsg" + vcn_id = "ocid1.vcn..." +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_nsg_change_event_rule_missing/test/positive2.tf b/assets/queries/terraform/oci/oci_nsg_change_event_rule_missing/test/positive2.tf new file mode 100644 index 00000000000..ecf21db2ea8 --- /dev/null +++ b/assets/queries/terraform/oci/oci_nsg_change_event_rule_missing/test/positive2.tf @@ -0,0 +1,24 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_events_rule" "incomplete_nsg_rule" { + display_name = "IncompleteNSGRule" + compartment_id = "ocid1.tenancy..." + is_enabled = true + + # FALLO: Falta "deletenetworksecuritygroup" + condition = jsonencode({ + "eventType": [ + "com.oraclecloud.virtualnetwork.createnetworksecuritygroup", + "com.oraclecloud.virtualnetwork.updatenetworksecuritygroup" + ] + }) + + actions { + actions { + action_type = "ONS" + topic_id = "ocid1.onstopic..." + } + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_nsg_change_event_rule_missing/test/positive3.tf b/assets/queries/terraform/oci/oci_nsg_change_event_rule_missing/test/positive3.tf new file mode 100644 index 00000000000..2923fcaf250 --- /dev/null +++ b/assets/queries/terraform/oci/oci_nsg_change_event_rule_missing/test/positive3.tf @@ -0,0 +1,26 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_events_rule" "disabled_nsg_rule" { + display_name = "DisabledNSGRule" + compartment_id = "ocid1.tenancy..." + description = "Has all events but is disabled" + + condition = jsonencode({ + "eventType": [ + "com.oraclecloud.virtualnetwork.createnetworksecuritygroup", + "com.oraclecloud.virtualnetwork.updatenetworksecuritygroup", + "com.oraclecloud.virtualnetwork.deletenetworksecuritygroup" + ] + }) + + actions { + actions { + action_type = "ONS" + topic_id = "ocid1.onstopic..." + } + } + + is_enabled = false +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_nsg_change_event_rule_missing/test/positive_expected_result.json b/assets/queries/terraform/oci/oci_nsg_change_event_rule_missing/test/positive_expected_result.json new file mode 100644 index 00000000000..9eedb1c4941 --- /dev/null +++ b/assets/queries/terraform/oci/oci_nsg_change_event_rule_missing/test/positive_expected_result.json @@ -0,0 +1,20 @@ +[ + { + "queryName": "Event Rule for NSG Changes is Missing", + "severity": "MEDIUM", + "line": 1, + "fileName": "positive1.tf" + }, + { + "queryName": "Event Rule for NSG Changes is Missing", + "severity": "MEDIUM", + "line": 11, + "fileName": "positive2.tf" + }, + { + "queryName": "Event Rule for NSG Changes is Missing", + "severity": "MEDIUM", + "line": 25, + "fileName": "positive3.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_objectstorage_bucket_logging_enabled/README.md b/assets/queries/terraform/oci/oci_objectstorage_bucket_logging_enabled/README.md new file mode 100644 index 00000000000..8fe00ec4c3b --- /dev/null +++ b/assets/queries/terraform/oci/oci_objectstorage_bucket_logging_enabled/README.md @@ -0,0 +1,34 @@ +# Regla KICS: Logging de Escritura en OCI Object Storage + +## Descripción General + +Esta regla de KICS para Terraform asegura que todos los buckets de OCI Object Storage (`oci_objectstorage_bucket`) tengan habilitada la emisión de eventos para objetos. Esta funcionalidad es fundamental para el logging de operaciones de escritura, como la creación (`put`), sobreescritura y eliminación de objetos. + +Auditar quién modifica o elimina datos es un requisito de seguridad y cumplimiento normativo crítico. Esta política verifica los dos escenarios posibles de mala configuración para proporcionar alertas precisas. + +## Lógica de la Regla + +La política se compone de dos reglas especializadas que cubren todos los casos en los que el logging de eventos de objeto está desactivado. + +## Casos de Fallo Detectados + +### Caso 1: Atributo `object_events_enabled` Ausente + +* **Descripción:** Esta regla detecta cualquier recurso `oci_objectstorage_bucket` donde el atributo `object_events_enabled` no está definido. El valor por defecto es `false`. +* **Ubicación de la Alerta:** Recurso `oci_objectstorage_bucket`. + +### Caso 2: Atributo `object_events_enabled` es `false` + +* **Descripción:** Esta regla busca un recurso `oci_objectstorage_bucket` que tiene el atributo `object_events_enabled` explícitamente configurado como `false`. +* **Ubicación de la Alerta:** Atributo `object_events_enabled`. + +## Solución + +Para solucionar los problemas detectados por esta regla, asegúrate de que cada recurso `oci_objectstorage_bucket` incluya la siguiente línea: + +```terraform +resource "oci_objectstorage_bucket" "example" { + # ... otros atributos ... + + object_events_enabled = true +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_objectstorage_bucket_logging_enabled/metadata.json b/assets/queries/terraform/oci/oci_objectstorage_bucket_logging_enabled/metadata.json new file mode 100644 index 00000000000..cb41f1a3af0 --- /dev/null +++ b/assets/queries/terraform/oci/oci_objectstorage_bucket_logging_enabled/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "5ecf7b51-8c4b-4911-9d59-1f7f6b9a2143", + "queryName": "Object Storage Bucket Logging Disabled", + "severity": "MEDIUM", + "category": "Observability", + "descriptionText": "Ensures that OCI Object Storage buckets have object event emission enabled, which is crucial for write-level logging and security auditing.", + "descriptionUrl": "https://registry.terraform.io/providers/oracle/oci/latest/docs/resources/objectstorage_bucket#object_events_enabled", + "platform": "Terraform", + "descriptionID": "5ecf7b51", + "cloudProvider": "oci", + "cwe": "CWE-223", + "riskScore": 3.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_objectstorage_bucket_logging_enabled/query.rego b/assets/queries/terraform/oci/oci_objectstorage_bucket_logging_enabled/query.rego new file mode 100644 index 00000000000..8a11d86be3d --- /dev/null +++ b/assets/queries/terraform/oci/oci_objectstorage_bucket_logging_enabled/query.rego @@ -0,0 +1,32 @@ +package Cx + +# REGLA 1: El atributo 'object_events_enabled' está ausente (MissingAttribute). +# Por defecto en OCI Terraform es false, así que su ausencia es un riesgo. +CxPolicy[result] { + bucket := input.document[i].resource.oci_objectstorage_bucket[bucket_name] + + object.get(bucket, "object_events_enabled", null) == null + + result := { + "documentId": input.document[i].id, + "searchKey": sprintf("resource.oci_objectstorage_bucket.%s", [bucket_name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "'object_events_enabled' should be present and set to 'true'", + "keyActualValue": "'object_events_enabled' is missing and defaults to 'false'", + } +} + +# REGLA 2: El atributo 'object_events_enabled' está explícitamente en 'false' (IncorrectValue). +CxPolicy[result] { + bucket := input.document[i].resource.oci_objectstorage_bucket[bucket_name] + + bucket.object_events_enabled == false + + result := { + "documentId": input.document[i].id, + "searchKey": sprintf("resource.oci_objectstorage_bucket.%s.object_events_enabled", [bucket_name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "'object_events_enabled' attribute should be 'true'", + "keyActualValue": "'object_events_enabled' attribute is 'false'", + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_objectstorage_bucket_logging_enabled/test/negative1.tf b/assets/queries/terraform/oci/oci_objectstorage_bucket_logging_enabled/test/negative1.tf new file mode 100644 index 00000000000..dcb5c904ef2 --- /dev/null +++ b/assets/queries/terraform/oci/oci_objectstorage_bucket_logging_enabled/test/negative1.tf @@ -0,0 +1,12 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_objectstorage_bucket" "bucket_enabled_log" { + compartment_id = "ocid1.compartment.oc1..aaaa" + namespace = "my-namespace" + name = "bucket-secure" + + # CORRECTO + object_events_enabled = true +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_objectstorage_bucket_logging_enabled/test/positive1.tf b/assets/queries/terraform/oci/oci_objectstorage_bucket_logging_enabled/test/positive1.tf new file mode 100644 index 00000000000..3e1a2bfe185 --- /dev/null +++ b/assets/queries/terraform/oci/oci_objectstorage_bucket_logging_enabled/test/positive1.tf @@ -0,0 +1,11 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_objectstorage_bucket" "bucket_missing_log" { + compartment_id = "ocid1.compartment.oc1..aaaa" + namespace = "my-namespace" + name = "bucket-no-logs" + access_type = "NoPublicAccess" + # FALLO: Falta object_events_enabled +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_objectstorage_bucket_logging_enabled/test/positive2.tf b/assets/queries/terraform/oci/oci_objectstorage_bucket_logging_enabled/test/positive2.tf new file mode 100644 index 00000000000..bac22f14e63 --- /dev/null +++ b/assets/queries/terraform/oci/oci_objectstorage_bucket_logging_enabled/test/positive2.tf @@ -0,0 +1,12 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_objectstorage_bucket" "bucket_disabled_log" { + compartment_id = "ocid1.compartment.oc1..aaaa" + namespace = "my-namespace" + name = "bucket-disabled-logs" + + # FALLO: Explícitamente false + object_events_enabled = false +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_objectstorage_bucket_logging_enabled/test/positive_expected_result.json b/assets/queries/terraform/oci/oci_objectstorage_bucket_logging_enabled/test/positive_expected_result.json new file mode 100644 index 00000000000..92add310abf --- /dev/null +++ b/assets/queries/terraform/oci/oci_objectstorage_bucket_logging_enabled/test/positive_expected_result.json @@ -0,0 +1,14 @@ +[ + { + "queryName": "Object Storage Bucket Logging Disabled", + "severity": "MEDIUM", + "line": 5, + "fileName": "positive1.tf" + }, + { + "queryName": "Object Storage Bucket Logging Disabled", + "severity": "MEDIUM", + "line": 11, + "fileName": "positive2.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_objectstorage_bucket_versioning_disabled/README.md b/assets/queries/terraform/oci/oci_objectstorage_bucket_versioning_disabled/README.md new file mode 100644 index 00000000000..e356a9823d2 --- /dev/null +++ b/assets/queries/terraform/oci/oci_objectstorage_bucket_versioning_disabled/README.md @@ -0,0 +1,34 @@ +# Regla KICS: Versionado Habilitado para Buckets de Object Storage en OCI + +## Descripción General + +Esta regla de KICS para Terraform asegura que todos los buckets de OCI Object Storage (`oci_objectstorage_bucket`) tengan habilitada la funcionalidad de versionado. + +El versionado es una capa de protección de datos fundamental. Cuando está habilitado, cualquier modificación (sobrescritura) o eliminación de un objeto no lo destruye permanentemente, sino que crea una nueva versión o un marcador de eliminación. Esto permite recuperar fácilmente versiones anteriores de un objeto en caso de un borrado accidental o una modificación no deseada, actuando como una red de seguridad contra la pérdida de datos. + +## Lógica de la Regla + +La política se compone de dos reglas para cubrir los escenarios en los que el versionado está desactivado, centradas en el atributo `versioning`. + +## Casos de Fallo Detectados + +### Caso 1: Atributo `versioning` Ausente + +* **Descripción:** Esta regla detecta cualquier recurso `oci_objectstorage_bucket` donde el atributo `versioning` no está definido. Si se omite, el versionado no se habilita por defecto. +* **Ubicación de la Alerta:** Recurso `oci_objectstorage_bucket`. + +### Caso 2: Atributo `versioning` no es `Enabled` + +* **Descripción:** Esta regla busca un recurso `oci_objectstorage_bucket` que tiene el atributo `versioning` explícitamente configurado con un valor diferente a `"Enabled"` (por ejemplo, `"Suspended"` o `"Disabled"`). +* **Ubicación de la Alerta:** Atributo `versioning`. + +## Solución + +Para solucionar los problemas detectados, asegúrate de que cada recurso `oci_objectstorage_bucket` incluya el atributo `versioning` con el valor `"Enabled"`. + +```terraform +resource "oci_objectstorage_bucket" "example" { + # ... otros atributos ... + + versioning = "Enabled" +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_objectstorage_bucket_versioning_disabled/metadata.json b/assets/queries/terraform/oci/oci_objectstorage_bucket_versioning_disabled/metadata.json new file mode 100644 index 00000000000..600bcac7475 --- /dev/null +++ b/assets/queries/terraform/oci/oci_objectstorage_bucket_versioning_disabled/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "72b1c53c-619e-4e0a-937a-9af24cae69df", + "queryName": "Object Storage Bucket Versioning Disabled", + "severity": "MEDIUM", + "category": "Data Security", + "descriptionText": "Ensures that OCI Object Storage buckets have versioning enabled. Versioning protects against accidental deletion or overwriting of objects by keeping a history of all object versions.", + "descriptionUrl": "https://registry.terraform.io/providers/oracle/oci/latest/docs/resources/objectstorage_bucket#versioning", + "platform": "Terraform", + "descriptionID": "72b1c53c", + "cloudProvider": "oci", + "cwe": "CWE-668", + "riskScore": 3.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_objectstorage_bucket_versioning_disabled/query.rego b/assets/queries/terraform/oci/oci_objectstorage_bucket_versioning_disabled/query.rego new file mode 100644 index 00000000000..e280e479a55 --- /dev/null +++ b/assets/queries/terraform/oci/oci_objectstorage_bucket_versioning_disabled/query.rego @@ -0,0 +1,34 @@ +package Cx + +# REGLA 1: El atributo 'versioning' está ausente en el bucket. +# Por defecto, si no se especifica, el versionado está deshabilitado. +CxPolicy[result] { + bucket := input.document[i].resource.oci_objectstorage_bucket[bucket_name] + + object.get(bucket, "versioning", null) == null + + result := { + "documentId": input.document[i].id, + "searchKey": sprintf("resource.oci_objectstorage_bucket.%s", [bucket_name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "'versioning' attribute should be present and set to 'Enabled'", + "keyActualValue": "'versioning' attribute is missing, disabling versioning", + } +} + +# REGLA 2: El atributo 'versioning' existe pero no es 'Enabled'. +# Puede ser 'Disabled' o 'Suspended'. +CxPolicy[result] { + bucket := input.document[i].resource.oci_objectstorage_bucket[bucket_name] + + object.get(bucket, "versioning", null) != null + bucket.versioning != "Enabled" + + result := { + "documentId": input.document[i].id, + "searchKey": sprintf("resource.oci_objectstorage_bucket.%s.versioning", [bucket_name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "'versioning' attribute should be 'Enabled'", + "keyActualValue": sprintf("'versioning' attribute is '%s'", [bucket.versioning]), + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_objectstorage_bucket_versioning_disabled/test/negative1.tf b/assets/queries/terraform/oci/oci_objectstorage_bucket_versioning_disabled/test/negative1.tf new file mode 100644 index 00000000000..466b92b441c --- /dev/null +++ b/assets/queries/terraform/oci/oci_objectstorage_bucket_versioning_disabled/test/negative1.tf @@ -0,0 +1,12 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_objectstorage_bucket" "bucket_enabled_versioning" { + compartment_id = "ocid1.compartment.oc1..aaaa" + namespace = "my-namespace" + name = "bucket-secure" + + # CORRECTO + versioning = "Enabled" +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_objectstorage_bucket_versioning_disabled/test/positive1.tf b/assets/queries/terraform/oci/oci_objectstorage_bucket_versioning_disabled/test/positive1.tf new file mode 100644 index 00000000000..a23885af982 --- /dev/null +++ b/assets/queries/terraform/oci/oci_objectstorage_bucket_versioning_disabled/test/positive1.tf @@ -0,0 +1,11 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_objectstorage_bucket" "bucket_missing_versioning" { + compartment_id = "ocid1.compartment.oc1..aaaa" + namespace = "my-namespace" + name = "bucket-no-ver" + access_type = "NoPublicAccess" + # FALLO: Falta el atributo versioning +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_objectstorage_bucket_versioning_disabled/test/positive2.tf b/assets/queries/terraform/oci/oci_objectstorage_bucket_versioning_disabled/test/positive2.tf new file mode 100644 index 00000000000..7730d44e087 --- /dev/null +++ b/assets/queries/terraform/oci/oci_objectstorage_bucket_versioning_disabled/test/positive2.tf @@ -0,0 +1,12 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_objectstorage_bucket" "bucket_suspended_versioning" { + compartment_id = "ocid1.compartment.oc1..aaaa" + namespace = "my-namespace" + name = "bucket-suspended" + + # FALLO: Está suspendido + versioning = "Suspended" +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_objectstorage_bucket_versioning_disabled/test/positive_expected_result.json b/assets/queries/terraform/oci/oci_objectstorage_bucket_versioning_disabled/test/positive_expected_result.json new file mode 100644 index 00000000000..6e91b54c4a4 --- /dev/null +++ b/assets/queries/terraform/oci/oci_objectstorage_bucket_versioning_disabled/test/positive_expected_result.json @@ -0,0 +1,14 @@ +[ + { + "queryName": "Object Storage Bucket Versioning Disabled", + "severity": "MEDIUM", + "line": 5, + "fileName": "positive1.tf" + }, + { + "queryName": "Object Storage Bucket Versioning Disabled", + "severity": "MEDIUM", + "line": 11, + "fileName": "positive2.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_resource_created_in_root_compartment/README.md b/assets/queries/terraform/oci/oci_resource_created_in_root_compartment/README.md new file mode 100644 index 00000000000..e9d61f3d55d --- /dev/null +++ b/assets/queries/terraform/oci/oci_resource_created_in_root_compartment/README.md @@ -0,0 +1,35 @@ +# Regla KICS: Recursos Creados Fuera del Compartimento Raíz en OCI + +## Descripción General + +Esta regla de KICS para Terraform asegura que los recursos no se creen directamente en el compartimento raíz (también conocido como *tenancy*). + +El compartimento raíz tiene los máximos privilegios y debe mantenerse lo más limpio posible, conteniendo únicamente otros compartimentos y políticas de IAM a nivel de *tenancy*. Crear recursos operativos (como instancias de cómputo, redes virtuales o bases de datos) en el compartimento raíz viola los principios de **menor privilegio**. + +## Lógica de la Regla + +La política mantiene una lista interna de tipos de recursos auditables. Itera sobre todos los recursos definidos en la configuración de Terraform y, si un recurso es de un tipo auditable, verifica el valor de su `compartment_id`. + +La regla se activa si el `compartment_id` apunta al compartimento raíz. Esto se detecta de dos maneras: +1. Si el valor es una referencia a `var.tenancy_ocid`. +2. Si el propio valor del OCID contiene la palabra `tenancy` (case-insensitive). + +## Caso de Fallo Detectado + +### Caso Único: Recurso con `compartment_id` apuntando al Raíz +* **Descripción:** Un recurso de la lista auditable tiene su `compartment_id` asignado al OCID del *tenancy*. +* **Ubicación de la Alerta:** Atributo `compartment_id`. + +## Solución + +Asegúrate de que todos los recursos se creen en compartimentos hijos apropiados. + +```terraform +variable "networking_compartment_ocid" {} + +# Esta VCN se crea en un compartimento hijo dedicado, lo cual es correcto. +resource "oci_core_vcn" "example_vcn_correct" { + compartment_id = var.networking_compartment_ocid + display_name = "VcnInNetworkingCompartment" + cidr_block = "10.1.0.0/16" +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_resource_created_in_root_compartment/metadata.json b/assets/queries/terraform/oci/oci_resource_created_in_root_compartment/metadata.json new file mode 100644 index 00000000000..c568020e617 --- /dev/null +++ b/assets/queries/terraform/oci/oci_resource_created_in_root_compartment/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "eb205044-d89b-40ac-9846-2e5993e091ab", + "queryName": "Resource Created In Root Compartment", + "severity": "HIGH", + "category": "Best Practices", + "descriptionText": "Ensures that resources are not created directly in the root compartment (tenancy). Following the principle of least privilege and separation of concerns, resources should be organized into dedicated child compartments.", + "descriptionUrl": "https://docs.oracle.com/en-us/iaas/Content/cloud-adoption-framework/iam-security-structure.htm", + "platform": "Terraform", + "descriptionID": "eb205044", + "cloudProvider": "oci", + "cwe": "CWE-284", + "riskScore": 6.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_resource_created_in_root_compartment/query.rego b/assets/queries/terraform/oci/oci_resource_created_in_root_compartment/query.rego new file mode 100644 index 00000000000..10acdb2003a --- /dev/null +++ b/assets/queries/terraform/oci/oci_resource_created_in_root_compartment/query.rego @@ -0,0 +1,52 @@ +package Cx + +import future.keywords.in + +auditable_resource_types := { + "oci_core_instance", + "oci_core_vcn", + "oci_core_subnet", + "oci_core_security_list", + "oci_core_route_table", + "oci_core_internet_gateway", + "oci_core_nat_gateway", + "oci_core_service_gateway", + "oci_objectstorage_bucket", + "oci_database_db_system", + "oci_containerengine_cluster", + "oci_functions_application", + "oci_kms_vault", + "oci_ons_notification_topic" +} + +is_root_compartment(compartment_id) { + contains(lower(compartment_id), "var.tenancy_ocid") +} +is_root_compartment(compartment_id) { + contains(lower(compartment_id), "tenancy") +} + +CxPolicy[result] { + doc := input.document[i] + + some resource_type + resources := doc.resource[resource_type] + + resource_type in auditable_resource_types + + some resource_name + resource := resources[resource_name] + + compartment_id := object.get(resource, "compartment_id", "") + compartment_id != "" + + is_root_compartment(compartment_id) + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.%s.%s.compartment_id", [resource_type, resource_name]), + "issueType": "IncorrectValue", + "keyExpectedValue": sprintf("Resource '%s' should be created in a child compartment, not the root compartment", [resource_name]), + "keyActualValue": sprintf("Resource '%s' is created in the root compartment (tenancy)", [resource_name]), + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_resource_created_in_root_compartment/test/negative1.tf b/assets/queries/terraform/oci/oci_resource_created_in_root_compartment/test/negative1.tf new file mode 100644 index 00000000000..94e3a98c1c7 --- /dev/null +++ b/assets/queries/terraform/oci/oci_resource_created_in_root_compartment/test/negative1.tf @@ -0,0 +1,17 @@ +provider "oci" { + region = "us-ashburn-1" +} + +variable "network_cmp_id" {} + +resource "oci_core_vcn" "child_vcn" { + cidr_block = "10.0.0.0/16" + compartment_id = var.network_cmp_id + display_name = "ChildVCN" +} + +resource "oci_objectstorage_bucket" "child_bucket" { + namespace = "ns" + name = "child-bucket" + compartment_id = "ocid1.compartment.oc1..bbbb..." +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_resource_created_in_root_compartment/test/positive1.tf b/assets/queries/terraform/oci/oci_resource_created_in_root_compartment/test/positive1.tf new file mode 100644 index 00000000000..1baa92902ff --- /dev/null +++ b/assets/queries/terraform/oci/oci_resource_created_in_root_compartment/test/positive1.tf @@ -0,0 +1,12 @@ +provider "oci" { + region = "us-ashburn-1" +} + +variable "tenancy_ocid" {} + +# Caso: Uso explícito de var.tenancy_ocid +resource "oci_core_vcn" "root_vcn" { + cidr_block = "10.0.0.0/16" + compartment_id = var.tenancy_ocid + display_name = "RootVCN" +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_resource_created_in_root_compartment/test/positive2.tf b/assets/queries/terraform/oci/oci_resource_created_in_root_compartment/test/positive2.tf new file mode 100644 index 00000000000..37e4bc75290 --- /dev/null +++ b/assets/queries/terraform/oci/oci_resource_created_in_root_compartment/test/positive2.tf @@ -0,0 +1,10 @@ +provider "oci" { + region = "us-ashburn-1" +} + +# Caso: OCID hardcoded que contiene 'tenancy' +resource "oci_objectstorage_bucket" "root_bucket" { + namespace = "ns" + name = "root-bucket" + compartment_id = "ocid1.tenancy.oc1..aaaa..." +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_resource_created_in_root_compartment/test/positive_expected_result.json b/assets/queries/terraform/oci/oci_resource_created_in_root_compartment/test/positive_expected_result.json new file mode 100644 index 00000000000..3d6d6db9c73 --- /dev/null +++ b/assets/queries/terraform/oci/oci_resource_created_in_root_compartment/test/positive_expected_result.json @@ -0,0 +1,14 @@ +[ + { + "queryName": "Resource Created In Root Compartment", + "severity": "HIGH", + "line": 10, + "fileName": "positive1.tf" + }, + { + "queryName": "Resource Created In Root Compartment", + "severity": "HIGH", + "line": 9, + "fileName": "positive2.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_route_table_change_event_rule_missing/README.md b/assets/queries/terraform/oci/oci_route_table_change_event_rule_missing/README.md new file mode 100644 index 00000000000..bd96ad8138f --- /dev/null +++ b/assets/queries/terraform/oci/oci_route_table_change_event_rule_missing/README.md @@ -0,0 +1,53 @@ +# Regla KICS: Notificación para Cambios en Tablas de Enrutamiento en OCI + +## Descripción General + +Esta regla de KICS para Terraform asegura que exista una regla de eventos (`oci_events_rule`) configurada para monitorizar y generar alertas ante cualquier cambio (creación, actualización o eliminación) en las Tablas de Enrutamiento (Route Tables) de la cuenta de OCI. + +Las tablas de enrutamiento controlan el flujo del tráfico de red entre subredes . Un cambio no autorizado podría redirigir el tráfico maliciosamente. + +## Lógica de la Regla + +La política verifica que exista configuración activa para capturar los tres tipos de eventos críticos de Route Tables: +1. `com.oraclecloud.virtualnetwork.createroutetable` +2. `com.oraclecloud.virtualnetwork.updateroutetable` +3. `com.oraclecloud.virtualnetwork.deleteroutetable` + +## Casos de Fallo Detectados + +### Caso 1: Regla Ausente (Missing) +* **Descripción:** No existe ninguna regla configurada para Route Tables en el proyecto. +* **Ubicación de la Alerta:** Bloque `provider "oci"`. + +### Caso 2: Regla Incompleta (Incomplete) +* **Descripción:** Existe una regla para Route Tables, pero le faltan eventos (ej. monitoriza create pero olvida delete). +* **Ubicación de la Alerta:** Atributo `condition`. + +### Caso 3: Regla Deshabilitada (Disabled) +* **Descripción:** Existe una regla relevante pero está explícitamente deshabilitada (`is_enabled = false`). +* **Ubicación de la Alerta:** Atributo `is_enabled` dentro del recurso. + +## Solución + +```terraform +resource "oci_events_rule" "route_table_change_rule" { + display_name = "audit-rule-for-route-table-changes" + description = "Alerts on any creation, update, or deletion of Route Tables" + compartment_id = var.tenancy_ocid + is_enabled = true + + condition = jsonencode({ + eventType = [ + "com.oraclecloud.virtualnetwork.createroutetable", + "com.oraclecloud.virtualnetwork.updateroutetable", + "com.oraclecloud.virtualnetwork.deleteroutetable" + ] + }) + + actions { + actions { + action_type = "ONS" + topic_id = oci_ons_notification_topic.security_alerts_topic.id + } + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_route_table_change_event_rule_missing/metadata.json b/assets/queries/terraform/oci/oci_route_table_change_event_rule_missing/metadata.json new file mode 100644 index 00000000000..81c3ef62cf8 --- /dev/null +++ b/assets/queries/terraform/oci/oci_route_table_change_event_rule_missing/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "b3af1ce8-5711-4304-b1db-17b658629df2", + "queryName": "Event Rule for Route Table Changes is Missing", + "severity": "MEDIUM", + "category": "Networking and Firewall", + "descriptionText": "Ensures that an OCI event rule is configured to monitor and alert on changes to Route Tables. Auditing changes to route tables is critical for detecting unauthorized network path modifications that could lead to traffic interception or service disruption.", + "descriptionUrl": "https://registry.terraform.io/providers/oracle/oci/latest/docs/resources/events_rule", + "platform": "Terraform", + "descriptionID": "b3af1ce8", + "cloudProvider": "oci", + "cwe": "CWE-778", + "riskScore": 3.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_route_table_change_event_rule_missing/query.rego b/assets/queries/terraform/oci/oci_route_table_change_event_rule_missing/query.rego new file mode 100644 index 00000000000..78c6bc27730 --- /dev/null +++ b/assets/queries/terraform/oci/oci_route_table_change_event_rule_missing/query.rego @@ -0,0 +1,76 @@ +package Cx + +expected_event_types := [ + "com.oraclecloud.virtualnetwork.createroutetable", + "com.oraclecloud.virtualnetwork.updateroutetable", + "com.oraclecloud.virtualnetwork.deleteroutetable" +] + +# REGLA 1: Missing (Global) +# No existe NINGUNA regla en el proyecto que monitoree Route Tables. +CxPolicy[result] { + doc := input.document[i] + _ := doc.provider.oci + + any_rt_rule := [rule | + rule := input.document[_].resource.oci_events_rule[_] + event := expected_event_types[_] + contains(rule.condition, event) + ] + + count(any_rt_rule) == 0 + + result := { + "documentId": doc.id, + "searchKey": "provider.oci", + "issueType": "MissingAttribute", + "keyExpectedValue": "An 'oci_events_rule' for Route Table changes should exist", + "keyActualValue": "No 'oci_events_rule' found for Route Table changes", + } +} + +# REGLA 2: Incomplete (Local) +# La regla existe, pero le faltan eventos (ej: tiene create pero falta update). +CxPolicy[result] { + rule := input.document[i].resource.oci_events_rule[name] + + matches := [event | + event := expected_event_types[_] + contains(rule.condition, event) + ] + + count(matches) > 0 + count(matches) < count(expected_event_types) + + missing_count := count(expected_event_types) - count(matches) + + result := { + "documentId": input.document[i].id, + "searchKey": sprintf("resource.oci_events_rule.%s.condition", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "The rule condition should include all 3 Route Table events (create, update, delete)", + "keyActualValue": sprintf("The rule is missing %d Route Table event(s)", [missing_count]), + } +} + +# REGLA 3: Disabled (Local) +# La regla es relevante (Route Table) pero está apagada. +CxPolicy[result] { + rule := input.document[i].resource.oci_events_rule[name] + + matches := [event | + event := expected_event_types[_] + contains(rule.condition, event) + ] + count(matches) > 0 + + rule.is_enabled == false + + result := { + "documentId": input.document[i].id, + "searchKey": sprintf("resource.oci_events_rule.%s.is_enabled", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "'is_enabled' should be true", + "keyActualValue": "'is_enabled' is false", + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_route_table_change_event_rule_missing/test/negative1.tf b/assets/queries/terraform/oci/oci_route_table_change_event_rule_missing/test/negative1.tf new file mode 100644 index 00000000000..573e9e52a2a --- /dev/null +++ b/assets/queries/terraform/oci/oci_route_table_change_event_rule_missing/test/negative1.tf @@ -0,0 +1,25 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_events_rule" "correct_rt_rule" { + display_name = "CorrectRTRule" + compartment_id = "ocid1.tenancy..." + is_enabled = true + + condition = jsonencode({ + "eventType": [ + "com.oraclecloud.virtualnetwork.createroutetable", + "com.oraclecloud.virtualnetwork.updateroutetable", + "com.oraclecloud.virtualnetwork.deleteroutetable" + ] + }) + + actions { + actions { + action_type = "ONS" + topic_id = "ocid1.onstopic..." + is_enabled = true + } + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_route_table_change_event_rule_missing/test/positive1.tf b/assets/queries/terraform/oci/oci_route_table_change_event_rule_missing/test/positive1.tf new file mode 100644 index 00000000000..ebf9915565f --- /dev/null +++ b/assets/queries/terraform/oci/oci_route_table_change_event_rule_missing/test/positive1.tf @@ -0,0 +1,9 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_core_route_table" "test_rt" { + compartment_id = "ocid1.compartment..." + vcn_id = "ocid1.vcn..." + display_name = "test-rt" +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_route_table_change_event_rule_missing/test/positive2.tf b/assets/queries/terraform/oci/oci_route_table_change_event_rule_missing/test/positive2.tf new file mode 100644 index 00000000000..74025ee37c7 --- /dev/null +++ b/assets/queries/terraform/oci/oci_route_table_change_event_rule_missing/test/positive2.tf @@ -0,0 +1,24 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_events_rule" "incomplete_rt_rule" { + display_name = "IncompleteRTRule" + compartment_id = "ocid1.tenancy..." + is_enabled = true + + # FALLO: Falta "deleteroutetable" + condition = jsonencode({ + "eventType": [ + "com.oraclecloud.virtualnetwork.createroutetable", + "com.oraclecloud.virtualnetwork.updateroutetable" + ] + }) + + actions { + actions { + action_type = "ONS" + topic_id = "ocid1.onstopic..." + } + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_route_table_change_event_rule_missing/test/positive3.tf b/assets/queries/terraform/oci/oci_route_table_change_event_rule_missing/test/positive3.tf new file mode 100644 index 00000000000..9e99b4f636b --- /dev/null +++ b/assets/queries/terraform/oci/oci_route_table_change_event_rule_missing/test/positive3.tf @@ -0,0 +1,26 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_events_rule" "disabled_rt_rule" { + display_name = "DisabledRTRule" + compartment_id = "ocid1.tenancy..." + description = "Has all events but is disabled" + + condition = jsonencode({ + "eventType": [ + "com.oraclecloud.virtualnetwork.createroutetable", + "com.oraclecloud.virtualnetwork.updateroutetable", + "com.oraclecloud.virtualnetwork.deleteroutetable" + ] + }) + + actions { + actions { + action_type = "ONS" + topic_id = "ocid1.onstopic..." + } + } + + is_enabled = false +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_route_table_change_event_rule_missing/test/positive_expected_result.json b/assets/queries/terraform/oci/oci_route_table_change_event_rule_missing/test/positive_expected_result.json new file mode 100644 index 00000000000..d4502f2e88f --- /dev/null +++ b/assets/queries/terraform/oci/oci_route_table_change_event_rule_missing/test/positive_expected_result.json @@ -0,0 +1,20 @@ +[ + { + "queryName": "Event Rule for Route Table Changes is Missing", + "severity": "MEDIUM", + "line": 1, + "fileName": "positive1.tf" + }, + { + "queryName": "Event Rule for Route Table Changes is Missing", + "severity": "MEDIUM", + "line": 11, + "fileName": "positive2.tf" + }, + { + "queryName": "Event Rule for Route Table Changes is Missing", + "severity": "MEDIUM", + "line": 25, + "fileName": "positive3.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_security_list_change_event_rule_missing/README.md b/assets/queries/terraform/oci/oci_security_list_change_event_rule_missing/README.md new file mode 100644 index 00000000000..5aeef264100 --- /dev/null +++ b/assets/queries/terraform/oci/oci_security_list_change_event_rule_missing/README.md @@ -0,0 +1,54 @@ +# Regla KICS: Notificación para Cambios en Listas de Seguridad en OCI + +## Descripción General + +Esta regla de KICS para Terraform asegura que exista una regla de eventos (`oci_events_rule`) configurada para monitorizar y generar alertas ante cualquier cambio (creación, actualización o eliminación) en las Listas de Seguridad (Security Lists) de la cuenta de OCI. + +Las Listas de Seguridad actúan como un firewall virtual a nivel de subred. Un cambio no autorizado, como añadir una regla que permita el acceso desde cualquier IP (`0.0.0.0/0`) a un puerto de gestión, puede exponer gravemente la infraestructura. + +## Lógica de la Regla + +La política verifica la configuración de los recursos `oci_events_rule` buscando los tres tipos de eventos fundamentales: +* `com.oraclecloud.virtualnetwork.createsecuritylist` +* `com.oraclecloud.virtualnetwork.updatesecuritylist` +* `com.oraclecloud.virtualnetwork.deletesecuritylist` + +## Casos de Fallo Detectados + +### Caso 1: Regla Ausente (Missing) +* **Descripción:** No existe ninguna regla en la configuración que monitorice cambios en Security Lists. +* **Ubicación:** Bloque `provider "oci"`. + +### Caso 2: Regla Incompleta (Incomplete) +* **Descripción:** Existe una regla pero no incluye los tres eventos requeridos (create, update, delete). +* **Ubicación:** Atributo `condition`. + +### Caso 3: Regla Deshabilitada (Disabled) +* **Descripción:** Existe una regla relevante pero el atributo `is_enabled` es `false`. +* **Ubicación:** Atributo `is_enabled`. + +## Solución + +Asegúrate de incluir una regla habilitada con los eventos correspondientes: + +```terraform +resource "oci_events_rule" "security_list_change_rule" { + display_name = "audit-rule-for-security-list-changes" + compartment_id = var.tenancy_ocid + is_enabled = true + + condition = jsonencode({ + eventType = [ + "com.oraclecloud.virtualnetwork.createsecuritylist", + "com.oraclecloud.virtualnetwork.updatesecuritylist", + "com.oraclecloud.virtualnetwork.deletesecuritylist" + ] + }) + + actions { + actions { + action_type = "ONS" + topic_id = oci_ons_notification_topic.security_alerts_topic.id + } + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_security_list_change_event_rule_missing/metadata.json b/assets/queries/terraform/oci/oci_security_list_change_event_rule_missing/metadata.json new file mode 100644 index 00000000000..2a6684d23b6 --- /dev/null +++ b/assets/queries/terraform/oci/oci_security_list_change_event_rule_missing/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "a5dfa109-1f7d-4077-8d10-41247bfcf922", + "queryName": "Event Rule for Security List Changes is Missing", + "severity": "MEDIUM", + "category": "Networking and Firewall", + "descriptionText": "Ensures that an OCI event rule is configured to monitor and alert on changes to Security Lists. Auditing changes to these network ACLs is critical for detecting unauthorized modifications that could expose services to unintended traffic.", + "descriptionUrl": "https://registry.terraform.io/providers/oracle/oci/latest/docs/resources/events_rule", + "platform": "Terraform", + "descriptionID": "a5dfa109", + "cloudProvider": "oci", + "cwe": "CWE-778", + "riskScore": 3.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_security_list_change_event_rule_missing/query.rego b/assets/queries/terraform/oci/oci_security_list_change_event_rule_missing/query.rego new file mode 100644 index 00000000000..13db46a0e07 --- /dev/null +++ b/assets/queries/terraform/oci/oci_security_list_change_event_rule_missing/query.rego @@ -0,0 +1,75 @@ +package Cx + +expected_event_types := [ + "com.oraclecloud.virtualnetwork.createsecuritylist", + "com.oraclecloud.virtualnetwork.updatesecuritylist", + "com.oraclecloud.virtualnetwork.deletesecuritylist" +] + +# REGLA 1: Missing (Global) +# No existe ninguna regla en el proyecto que monitoree Security Lists. +CxPolicy[result] { + doc := input.document[i] + _ := doc.provider.oci + + any_sl_rule := [rule | + rule := input.document[_].resource.oci_events_rule[_] + event := expected_event_types[_] + contains(rule.condition, event) + ] + + count(any_sl_rule) == 0 + + result := { + "documentId": doc.id, + "searchKey": "provider.oci", + "issueType": "MissingAttribute", + "keyExpectedValue": "An 'oci_events_rule' for Security List changes should exist", + "keyActualValue": "No 'oci_events_rule' found for Security List changes", + } +} + +# REGLA 2: Incomplete (Local) +# La regla existe pero le faltan eventos (ej: tiene create pero falta delete). +CxPolicy[result] { + rule := input.document[i].resource.oci_events_rule[name] + + matches := [event | + event := expected_event_types[_] + contains(rule.condition, event) + ] + + count(matches) > 0 + count(matches) < count(expected_event_types) + + missing_count := count(expected_event_types) - count(matches) + + result := { + "documentId": input.document[i].id, + "searchKey": sprintf("resource.oci_events_rule.%s.condition", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "The rule condition should include all 3 Security List events (create, update, delete)", + "keyActualValue": sprintf("The rule is missing %d Security List event(s)", [missing_count]), + } +} + +# REGLA 3: Disabled (Local) +# La regla es relevante para Security Lists pero está apagada. +CxPolicy[result] { + rule := input.document[i].resource.oci_events_rule[name] + + matches := [event | + event := expected_event_types[_] + contains(rule.condition, event) + ] + count(matches) > 0 + rule.is_enabled == false + + result := { + "documentId": input.document[i].id, + "searchKey": sprintf("resource.oci_events_rule.%s.is_enabled", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "'is_enabled' should be true", + "keyActualValue": "'is_enabled' is false", + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_security_list_change_event_rule_missing/test/negative1.tf b/assets/queries/terraform/oci/oci_security_list_change_event_rule_missing/test/negative1.tf new file mode 100644 index 00000000000..f4b702e0533 --- /dev/null +++ b/assets/queries/terraform/oci/oci_security_list_change_event_rule_missing/test/negative1.tf @@ -0,0 +1,24 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_events_rule" "compliant_sl_rule" { + display_name = "CompliantSLRule" + compartment_id = "ocid1.tenancy.oc1..aaaa" + is_enabled = true + + condition = jsonencode({ + "eventType": [ + "com.oraclecloud.virtualnetwork.createsecuritylist", + "com.oraclecloud.virtualnetwork.updatesecuritylist", + "com.oraclecloud.virtualnetwork.deletesecuritylist" + ] + }) + + actions { + actions { + action_type = "ONS" + topic_id = "ocid1.onstopic.oc1..aaaa" + } + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_security_list_change_event_rule_missing/test/positive1.tf b/assets/queries/terraform/oci/oci_security_list_change_event_rule_missing/test/positive1.tf new file mode 100644 index 00000000000..1183f90ad37 --- /dev/null +++ b/assets/queries/terraform/oci/oci_security_list_change_event_rule_missing/test/positive1.tf @@ -0,0 +1,9 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_core_security_list" "test_sl" { + compartment_id = "ocid1.compartment.oc1..aaaa" + vcn_id = "ocid1.vcn.oc1..aaaa" + display_name = "vulnerable_sl" +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_security_list_change_event_rule_missing/test/positive2.tf b/assets/queries/terraform/oci/oci_security_list_change_event_rule_missing/test/positive2.tf new file mode 100644 index 00000000000..3af0e42cc84 --- /dev/null +++ b/assets/queries/terraform/oci/oci_security_list_change_event_rule_missing/test/positive2.tf @@ -0,0 +1,23 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_events_rule" "incomplete_sl_rule" { + display_name = "IncompleteSLRule" + compartment_id = "ocid1.tenancy.oc1..aaaa" + is_enabled = true + + condition = jsonencode({ + "eventType": [ + "com.oraclecloud.virtualnetwork.createsecuritylist", + "com.oraclecloud.virtualnetwork.updatesecuritylist" + ] + }) + + actions { + actions { + action_type = "ONS" + topic_id = "ocid1.onstopic.oc1..aaaa" + } + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_security_list_change_event_rule_missing/test/positive3.tf b/assets/queries/terraform/oci/oci_security_list_change_event_rule_missing/test/positive3.tf new file mode 100644 index 00000000000..82e7e8390ac --- /dev/null +++ b/assets/queries/terraform/oci/oci_security_list_change_event_rule_missing/test/positive3.tf @@ -0,0 +1,25 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_events_rule" "disabled_sl_rule" { + display_name = "DisabledSLRule" + compartment_id = "ocid1.tenancy.oc1..aaaa" + + condition = jsonencode({ + "eventType": [ + "com.oraclecloud.virtualnetwork.createsecuritylist", + "com.oraclecloud.virtualnetwork.updatesecuritylist", + "com.oraclecloud.virtualnetwork.deletesecuritylist" + ] + }) + + actions { + actions { + action_type = "ONS" + topic_id = "ocid1.onstopic.oc1..aaaa" + } + } + + is_enabled = false +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_security_list_change_event_rule_missing/test/positive_expected_result.json b/assets/queries/terraform/oci/oci_security_list_change_event_rule_missing/test/positive_expected_result.json new file mode 100644 index 00000000000..7b344cdf4f9 --- /dev/null +++ b/assets/queries/terraform/oci/oci_security_list_change_event_rule_missing/test/positive_expected_result.json @@ -0,0 +1,20 @@ +[ + { + "queryName": "Event Rule for Security List Changes is Missing", + "severity": "MEDIUM", + "line": 1, + "fileName": "positive1.tf" + }, + { + "queryName": "Event Rule for Security List Changes is Missing", + "severity": "MEDIUM", + "line": 10, + "fileName": "positive2.tf" + }, + { + "queryName": "Event Rule for Security List Changes is Missing", + "severity": "MEDIUM", + "line": 24, + "fileName": "positive3.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_storage_admin_no_delete_manual/README.md b/assets/queries/terraform/oci/oci_storage_admin_no_delete_manual/README.md new file mode 100644 index 00000000000..63a02d7b3d2 --- /dev/null +++ b/assets/queries/terraform/oci/oci_storage_admin_no_delete_manual/README.md @@ -0,0 +1,40 @@ +# Regla KICS: OCI Storage Admin with Delete Privileges (Manual) + +## Descripción General + +Esta regla informativa (INFO) audita las políticas de IAM (`oci_identity_policy`) para identificar administradores de almacenamiento con permisos excesivos, específicamente la capacidad de borrar recursos. + +En OCI, el verbo **`manage`** incluye todos los permisos: `inspect`, `read`, `use`, `create`, `update` y **`DELETE`**. Para cumplir con controles estrictos de protección de datos, los administradores de almacenamiento no deberían tener capacidad para eliminar volúmenes o buckets por defecto, o esta capacidad debería estar restringida mediante condiciones. + +## Lógica de la Regla + +1. Analiza las sentencias (`statements`) de las políticas. +2. Busca la combinación del verbo exacto `manage` con familias de almacenamiento: + * `object-family` + * `volume-family` + * `file-family` + * `autonomous-database-family` +3. Genera una alerta INFO para revisión manual si se detecta acceso total de gestión. + +## Casos de Fallo Detectados + +### Caso 1: Gestión Total de Almacenamiento +* **Descripción:** Se otorga `manage` sobre una familia de almacenamiento sin restricciones visibles en la sentencia. +* **Acción:** Verificar si el usuario realmente requiere capacidad de borrado o si se puede añadir una cláusula `where request.permission != 'DELETE'`. + +## Recursos Involucrados +* `oci_identity_policy` + +## Solución + +Añade una condición a la política para restringir el borrado. + +```terraform +resource "oci_identity_policy" "storage_admin_safe" { + name = "SafeStorageAdmin" + compartment_id = var.tenancy_ocid + statements = [ + # Permite gestionar todo EXCEPTO borrar + "Allow group StorageAdmins to manage object-family in tenancy where request.permission != 'DELETE'" + ] +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_storage_admin_no_delete_manual/metadata.json b/assets/queries/terraform/oci/oci_storage_admin_no_delete_manual/metadata.json new file mode 100644 index 00000000000..2ea719539c7 --- /dev/null +++ b/assets/queries/terraform/oci/oci_storage_admin_no_delete_manual/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "8cd7b5db-800b-4c93-949b-bc215d63f7c3", + "queryName": "OCI Storage Admin with Delete Privileges (Manual)", + "severity": "INFO", + "category": "Identity and Access Management", + "descriptionText": "Storage service-level admins should ideally not have 'DELETE' permissions to prevent accidental or malicious data loss. The verb 'manage' includes DELETE permissions by default. Verify if a condition 'where request.permission != DELETE' is applied or if the 'manage' verb is necessary.", + "descriptionUrl": "https://registry.terraform.io/providers/oracle/oci/latest/docs/resources/identity_policy", + "platform": "Terraform", + "descriptionID": "8cd7b5db", + "cloudProvider": "oci", + "cwe": "CWE-269", + "riskScore": 0.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_storage_admin_no_delete_manual/query.rego b/assets/queries/terraform/oci/oci_storage_admin_no_delete_manual/query.rego new file mode 100644 index 00000000000..429152d2fe1 --- /dev/null +++ b/assets/queries/terraform/oci/oci_storage_admin_no_delete_manual/query.rego @@ -0,0 +1,29 @@ +package Cx + +storage_families := { + "object-family", + "volume-family", + "file-family", + "autonomous-database-family" +} + +CxPolicy[result] { + doc := input.document[i] + policy := doc.resource.oci_identity_policy[name] + + statement := policy.statements[_] + statement_lower := lower(statement) + + regex.match("(?i)\\bmanage\\b", statement) + + family := storage_families[_] + contains(statement_lower, family) + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.oci_identity_policy.%s.statements", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": sprintf("Statement granting 'manage' on '%s' should ideally exclude DELETE permissions via 'where request.permission != DELETE'", [family]), + "keyActualValue": sprintf("Statement grants full 'manage' access (including DELETE) on '%s': '%s'", [family, statement]), + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_storage_admin_no_delete_manual/test/negative1.tf b/assets/queries/terraform/oci/oci_storage_admin_no_delete_manual/test/negative1.tf new file mode 100644 index 00000000000..907698d9916 --- /dev/null +++ b/assets/queries/terraform/oci/oci_storage_admin_no_delete_manual/test/negative1.tf @@ -0,0 +1,13 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_identity_policy" "read_only_storage" { + name = "ReadOnlyStorage" + compartment_id = "ocid1.tenancy..." + + # CORRECTO: El verbo es read, no incluye delete + statements = [ + "Allow group Auditors to read object-family in tenancy" + ] +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_storage_admin_no_delete_manual/test/negative2.tf b/assets/queries/terraform/oci/oci_storage_admin_no_delete_manual/test/negative2.tf new file mode 100644 index 00000000000..74f3d0745ba --- /dev/null +++ b/assets/queries/terraform/oci/oci_storage_admin_no_delete_manual/test/negative2.tf @@ -0,0 +1,13 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_identity_policy" "false_positive_check" { + name = "ManagerCheck" + compartment_id = "ocid1.tenancy..." + + # CORRECTO: Aunque el grupo se llama Managers, el verbo es read. El regex \bmanage\b evita el fallo. + statements = [ + "Allow group ObjectManagers to read object-family in tenancy" + ] +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_storage_admin_no_delete_manual/test/positive1.tf b/assets/queries/terraform/oci/oci_storage_admin_no_delete_manual/test/positive1.tf new file mode 100644 index 00000000000..4ea3bcac30a --- /dev/null +++ b/assets/queries/terraform/oci/oci_storage_admin_no_delete_manual/test/positive1.tf @@ -0,0 +1,13 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_identity_policy" "unsafe_object_admin" { + name = "UnsafeObjectAdmin" + compartment_id = "ocid1.tenancy..." + + # FALLO: Otorga manage (incluye delete) en object-family + statements = [ + "Allow group StorageAdmins to manage object-family in tenancy" + ] +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_storage_admin_no_delete_manual/test/positive2.tf b/assets/queries/terraform/oci/oci_storage_admin_no_delete_manual/test/positive2.tf new file mode 100644 index 00000000000..4f5f8234695 --- /dev/null +++ b/assets/queries/terraform/oci/oci_storage_admin_no_delete_manual/test/positive2.tf @@ -0,0 +1,13 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_identity_policy" "unsafe_volume_admin" { + name = "UnsafeVolumeAdmin" + compartment_id = "ocid1.tenancy..." + + # FALLO: Otorga manage (incluye delete) en volume-family + statements = [ + "Allow group BackupAdmins to MANAGE volume-family in tenancy" + ] +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_storage_admin_no_delete_manual/test/positive_expected_result.json b/assets/queries/terraform/oci/oci_storage_admin_no_delete_manual/test/positive_expected_result.json new file mode 100644 index 00000000000..e4a32105d59 --- /dev/null +++ b/assets/queries/terraform/oci/oci_storage_admin_no_delete_manual/test/positive_expected_result.json @@ -0,0 +1,14 @@ +[ + { + "queryName": "OCI Storage Admin with Delete Privileges (Manual)", + "severity": "INFO", + "line": 10, + "fileName": "positive1.tf" + }, + { + "queryName": "OCI Storage Admin with Delete Privileges (Manual)", + "severity": "INFO", + "line": 10, + "fileName": "positive2.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_storage_cmk_encryption_unified/README.md b/assets/queries/terraform/oci/oci_storage_cmk_encryption_unified/README.md new file mode 100644 index 00000000000..175b79913f5 --- /dev/null +++ b/assets/queries/terraform/oci/oci_storage_cmk_encryption_unified/README.md @@ -0,0 +1,40 @@ +# Regla KICS: OCI Storage Resources Without CMK + +## Descripción General + +Esta regla unificada de severidad **MEDIA** audita los recursos de almacenamiento de OCI para asegurar que utilicen claves gestionadas por el cliente (CMK). + +Aunque OCI cifra los datos en reposo por defecto, lo hace con claves gestionadas por Oracle. Para tener control total sobre el ciclo de vida de la clave (rotación, revocación), es necesario asignar una clave de **OCI Vault** mediante el atributo `kms_key_id`. + +## Lógica de la Regla + +La política verifica los siguientes recursos: +* `oci_objectstorage_bucket` +* `oci_core_volume` +* `oci_core_boot_volume` +* `oci_file_storage_file_system` + +La regla se activa si: +1. El atributo `kms_key_id` no está definido. +2. El atributo `kms_key_id` está presente pero tiene un valor vacío. + +## Casos de Fallo Detectados + +### Caso 1: Cifrado por Defecto (Oracle-Managed) +* **Descripción:** El recurso no define `kms_key_id`, delegando el control de la clave a Oracle. +* **Ubicación de la Alerta:** Bloque del recurso. + +### Caso 2: Configuración Vacía +* **Descripción:** Se define el atributo pero no se proporciona un OCID válido. +* **Ubicación de la Alerta:** Atributo `kms_key_id`. + +## Solución + +Asigna el OCID de una clave de OCI Vault al recurso. + +```terraform +resource "oci_objectstorage_bucket" "secure_bucket" { + name = "secure-bucket" + compartment_id = var.compartment_id + kms_key_id = "ocid1.key.oc1.iad.exampleuniqueID" +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_storage_cmk_encryption_unified/metadata.json b/assets/queries/terraform/oci/oci_storage_cmk_encryption_unified/metadata.json new file mode 100644 index 00000000000..5f63a1e8122 --- /dev/null +++ b/assets/queries/terraform/oci/oci_storage_cmk_encryption_unified/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "cb72ae80-1b5a-407e-9f5b-86ddd64df644", + "queryName": "OCI Storage Resources Without CMK", + "severity": "MEDIUM", + "category": "Encryption", + "descriptionText": "Storage resources (Object Storage, Block Volumes, Boot Volumes, File Storage) are using default Oracle-managed encryption keys. To satisfy strict compliance requirements, they should be encrypted with a Customer Managed Key (CMK) by defining the 'kms_key_id' attribute.", + "descriptionUrl": "https://docs.oracle.com/en-us/iaas/Content/KeyManagement/Concepts/keyoverview.htm", + "platform": "Terraform", + "descriptionID": "cb72ae80", + "cloudProvider": "oci", + "cwe": "CWE-312", + "riskScore": 3.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_storage_cmk_encryption_unified/query.rego b/assets/queries/terraform/oci/oci_storage_cmk_encryption_unified/query.rego new file mode 100644 index 00000000000..fcfae666d27 --- /dev/null +++ b/assets/queries/terraform/oci/oci_storage_cmk_encryption_unified/query.rego @@ -0,0 +1,44 @@ +package Cx + +targets := { + "oci_objectstorage_bucket", + "oci_core_volume", + "oci_core_boot_volume", + "oci_file_storage_file_system" +} + +# CASO 1: Falta el atributo kms_key_id (Usa claves gestionadas por Oracle) +CxPolicy[result] { + doc := input.document[i] + + resource := doc.resource[resource_type][name] + targets[resource_type] + + object.get(resource, "kms_key_id", "undefined") == "undefined" + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.%s.%s", [resource_type, name]), + "issueType": "MissingAttribute", + "keyExpectedValue": sprintf("'%s' should have 'kms_key_id' defined with a Vault Key OCID", [name]), + "keyActualValue": "'kms_key_id' is missing (using Oracle-managed keys)", + } +} + +# CASO 2: El atributo existe pero está vacío +CxPolicy[result] { + doc := input.document[i] + resource := doc.resource[resource_type][name] + targets[resource_type] + + resource.kms_key_id + resource.kms_key_id == "" + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.%s.%s.kms_key_id", [resource_type, name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "'kms_key_id' should not be empty", + "keyActualValue": "'kms_key_id' is empty", + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_storage_cmk_encryption_unified/test/negative1.tf b/assets/queries/terraform/oci/oci_storage_cmk_encryption_unified/test/negative1.tf new file mode 100644 index 00000000000..b55fff6f8b0 --- /dev/null +++ b/assets/queries/terraform/oci/oci_storage_cmk_encryption_unified/test/negative1.tf @@ -0,0 +1,11 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_objectstorage_bucket" "bucket_with_cmk" { + compartment_id = "ocid1.compartment.oc1..aaaa" + namespace = "my-namespace" + name = "bucket-secure" + + kms_key_id = "ocid1.key.oc1.iad.example" +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_storage_cmk_encryption_unified/test/negative2.tf b/assets/queries/terraform/oci/oci_storage_cmk_encryption_unified/test/negative2.tf new file mode 100644 index 00000000000..56f1735388d --- /dev/null +++ b/assets/queries/terraform/oci/oci_storage_cmk_encryption_unified/test/negative2.tf @@ -0,0 +1,11 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_file_storage_file_system" "fs_secure" { + availability_domain = "AD-1" + compartment_id = "ocid1.compartment.oc1..aaaa" + display_name = "fs-secure" + + kms_key_id = "ocid1.key.oc1.iad.example" +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_storage_cmk_encryption_unified/test/positive1.tf b/assets/queries/terraform/oci/oci_storage_cmk_encryption_unified/test/positive1.tf new file mode 100644 index 00000000000..bee43bee98a --- /dev/null +++ b/assets/queries/terraform/oci/oci_storage_cmk_encryption_unified/test/positive1.tf @@ -0,0 +1,9 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_objectstorage_bucket" "bucket_without_cmk" { + compartment_id = "ocid1.compartment.oc1..aaaa" + namespace = "my-namespace" + name = "bucket-insecure" +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_storage_cmk_encryption_unified/test/positive2.tf b/assets/queries/terraform/oci/oci_storage_cmk_encryption_unified/test/positive2.tf new file mode 100644 index 00000000000..a9c46eafb79 --- /dev/null +++ b/assets/queries/terraform/oci/oci_storage_cmk_encryption_unified/test/positive2.tf @@ -0,0 +1,11 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_core_volume" "volume_empty_cmk" { + availability_domain = "AD-1" + compartment_id = "ocid1.compartment.oc1..aaaa" + display_name = "volume-empty" + + kms_key_id = "" +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_storage_cmk_encryption_unified/test/positive_expected_result.json b/assets/queries/terraform/oci/oci_storage_cmk_encryption_unified/test/positive_expected_result.json new file mode 100644 index 00000000000..a762ba46ea8 --- /dev/null +++ b/assets/queries/terraform/oci/oci_storage_cmk_encryption_unified/test/positive_expected_result.json @@ -0,0 +1,14 @@ +[ + { + "queryName": "OCI Storage Resources Without CMK", + "severity": "MEDIUM", + "line": 5, + "fileName": "positive1.tf" + }, + { + "queryName": "OCI Storage Resources Without CMK", + "severity": "MEDIUM", + "line": 10, + "fileName": "positive2.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_subnet_flow_logging_disabled/README.md b/assets/queries/terraform/oci/oci_subnet_flow_logging_disabled/README.md new file mode 100644 index 00000000000..5143fc1f110 --- /dev/null +++ b/assets/queries/terraform/oci/oci_subnet_flow_logging_disabled/README.md @@ -0,0 +1,56 @@ +# Regla KICS: VCN Flow Logging Habilitado + +## Descripción General + +Esta regla de KICS para Terraform asegura que todas las subredes de una VCN (Virtual Cloud Network) en OCI tengan el registro de flujos (`Flow Logging`) correctamente configurado y habilitado. + +La monitorización del tráfico de red es crucial para la detección de anomalías y la resolución de incidentes, cumpliendo con las recomendaciones de seguridad CIS. + +## Lógica de la Regla + +La política se divide en cuatro comprobaciones: +1. **Huérfano:** Subredes sin ningún recurso de log que las apunte. +2. **Estado:** Logs de flujo marcados como `is_enabled = false`. +3. **Tipo:** Logs de servicio que no usan el `log_type = "SERVICE"`. +4. **Servicio:** Logs que no especifican `service = "flowlogs"` en su origen. + +## Casos de Fallo Detectados + +### Caso 1: Subred sin Log Asociado +* **Descripción:** Falta la definición del log para la subred. +* **Ubicación:** Recurso `oci_core_subnet`. + +### Caso 2: Log de Flujo Deshabilitado +* **Descripción:** El atributo `is_enabled` está en `false`. +* **Ubicación:** Atributo `is_enabled` en `oci_logging_log`. + +### Caso 3: Log de Flujo con el Tipo Incorrecto +* **Descripción:** `log_type` no es `"SERVICE"`. +* **Ubicación:** Atributo `log_type` en `oci_logging_log`. + +### Caso 4: Log de Flujo con el Servicio Incorrecto +* **Descripción:** El servicio configurado no es `"flowlogs"`. +* **Ubicación:** Atributo `service` dentro del bloque `source`. + +## Recursos Involucrados + +* `oci_core_subnet` +* `oci_logging_log` + +## Solución + +```terraform +resource "oci_logging_log" "vcn_flow_log" { + display_name = "subnet_flow_log" + log_group_id = oci_logging_log_group.example_log_group.id + log_type = "SERVICE" + is_enabled = true + + configuration { + source { + resource = oci_core_subnet.example_subnet.id + service = "flowlogs" + category = "all" + } + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_subnet_flow_logging_disabled/metadata.json b/assets/queries/terraform/oci/oci_subnet_flow_logging_disabled/metadata.json new file mode 100644 index 00000000000..16a25033e7a --- /dev/null +++ b/assets/queries/terraform/oci/oci_subnet_flow_logging_disabled/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "30d3bb83-f6aa-4302-a647-c404f12f8b8a", + "queryName": "VCN Subnet Without Flow Log", + "severity": "HIGH", + "category": "Observability", + "descriptionText": "VCN Subnet should have flow logging enabled to monitor traffic, as per CIS v3.0.0 recommendation 4.13.", + "descriptionUrl": "https://registry.terraform.io/providers/oracle/oci/latest/docs/resources/core_subnet", + "platform": "Terraform", + "descriptionID": "30d3bb83", + "cloudProvider": "oci", + "cwe": "778", + "riskScore": 6.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_subnet_flow_logging_disabled/query.rego b/assets/queries/terraform/oci/oci_subnet_flow_logging_disabled/query.rego new file mode 100644 index 00000000000..cdce921cd2f --- /dev/null +++ b/assets/queries/terraform/oci/oci_subnet_flow_logging_disabled/query.rego @@ -0,0 +1,73 @@ +package Cx + +# REGLA 1: No existe ningún log asociado a la subred (Missing) +CxPolicy[result] { + doc := input.document[i] + _ := doc.resource.oci_core_subnet[subnet_name] + + associated_logs := [l | + l := input.document[_].resource.oci_logging_log[_] + contains(l.configuration.source.resource, subnet_name) + ] + count(associated_logs) == 0 + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.oci_core_subnet.%s", [subnet_name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "Subnet to have an associated VCN flow log", + "keyActualValue": "Subnet does not have an associated VCN flow log", + } +} + +# REGLA 2: Un log de flujo está deshabilitado +CxPolicy[result] { + doc := input.document[i] + log := doc.resource.oci_logging_log[log_name] + + log.log_type == "SERVICE" + object.get(log.configuration.source, "service", "") == "flowlogs" + log.is_enabled == false + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.oci_logging_log.%s.is_enabled", [log_name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "'is_enabled' should be 'true'", + "keyActualValue": "'is_enabled' is 'false'", + } +} + +# REGLA 3: Un log de flujo tiene el tipo incorrecto +CxPolicy[result] { + doc := input.document[i] + log := doc.resource.oci_logging_log[log_name] + + object.get(log.configuration.source, "service", "") == "flowlogs" + log.log_type != "SERVICE" + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.oci_logging_log.%s.log_type", [log_name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "'log_type' should be 'SERVICE'", + "keyActualValue": sprintf("'log_type' is '%s'", [log.log_type]), + } +} + +# REGLA 4: Un log de flujo tiene el servicio incorrecto +CxPolicy[result] { + doc := input.document[i] + log := doc.resource.oci_logging_log[log_name] + + log.log_type == "SERVICE" + object.get(log.configuration.source, "service", "") != "flowlogs" + + result := { + "documentId": doc.id, + "searchKey": sprintf("resource.oci_logging_log.%s.configuration.source.service", [log_name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "'service' should be 'flowlogs'", + "keyActualValue": sprintf("'service' is '%s'", [object.get(log.configuration.source, "service", "undefined")]), + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_subnet_flow_logging_disabled/test/negative1.tf b/assets/queries/terraform/oci/oci_subnet_flow_logging_disabled/test/negative1.tf new file mode 100644 index 00000000000..3ca9e0f1246 --- /dev/null +++ b/assets/queries/terraform/oci/oci_subnet_flow_logging_disabled/test/negative1.tf @@ -0,0 +1,21 @@ +resource "oci_core_subnet" "secure_subnet" { + cidr_block = "10.0.1.0/24" + compartment_id = "ocid1.compartment.oc1..aaaa" + vcn_id = "ocid1.vcn.oc1..bbbb" + display_name = "secure_subnet" +} + +resource "oci_logging_log" "secure_flow_log" { + display_name = "secure_log" + log_group_id = "ocid1.loggroup.oc1..cccc" + log_type = "SERVICE" + is_enabled = true + + configuration { + source { + resource = oci_core_subnet.secure_subnet.id + service = "flowlogs" + category = "all" + } + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_subnet_flow_logging_disabled/test/positive1.tf b/assets/queries/terraform/oci/oci_subnet_flow_logging_disabled/test/positive1.tf new file mode 100644 index 00000000000..aabb4d49dab --- /dev/null +++ b/assets/queries/terraform/oci/oci_subnet_flow_logging_disabled/test/positive1.tf @@ -0,0 +1,6 @@ +resource "oci_core_subnet" "subnet_without_log" { + cidr_block = "10.0.1.0/24" + compartment_id = "ocid1.compartment.oc1..aaaa" + vcn_id = "ocid1.vcn.oc1..bbbb" + display_name = "orphan_subnet" +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_subnet_flow_logging_disabled/test/positive2.tf b/assets/queries/terraform/oci/oci_subnet_flow_logging_disabled/test/positive2.tf new file mode 100644 index 00000000000..25b5435ae62 --- /dev/null +++ b/assets/queries/terraform/oci/oci_subnet_flow_logging_disabled/test/positive2.tf @@ -0,0 +1,15 @@ +resource "oci_logging_log" "disabled_flow_log" { + display_name = "disabled_log" + log_group_id = "ocid1.loggroup.oc1..cccc" + log_type = "SERVICE" + + # FALLO: Deshabilitado + is_enabled = false + + configuration { + source { + resource = "ocid1.subnet.oc1..aaaa" + service = "flowlogs" + } + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_subnet_flow_logging_disabled/test/positive3.tf b/assets/queries/terraform/oci/oci_subnet_flow_logging_disabled/test/positive3.tf new file mode 100644 index 00000000000..790e68430cb --- /dev/null +++ b/assets/queries/terraform/oci/oci_subnet_flow_logging_disabled/test/positive3.tf @@ -0,0 +1,15 @@ +resource "oci_logging_log" "wrong_type_log" { + display_name = "wrong_type" + log_group_id = "ocid1.loggroup.oc1..cccc" + + # FALLO: Debería ser SERVICE + log_type = "CUSTOM" + is_enabled = true + + configuration { + source { + resource = "ocid1.subnet.oc1..aaaa" + service = "flowlogs" + } + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_subnet_flow_logging_disabled/test/positive4.tf b/assets/queries/terraform/oci/oci_subnet_flow_logging_disabled/test/positive4.tf new file mode 100644 index 00000000000..b20dee2c0fa --- /dev/null +++ b/assets/queries/terraform/oci/oci_subnet_flow_logging_disabled/test/positive4.tf @@ -0,0 +1,14 @@ +resource "oci_logging_log" "wrong_service_log" { + display_name = "wrong_service" + log_group_id = "ocid1.loggroup.oc1..cccc" + log_type = "SERVICE" + is_enabled = true + + configuration { + source { + resource = "ocid1.subnet.oc1..aaaa" + # FALLO: Debería ser flowlogs + service = "wronglogs" + } + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_subnet_flow_logging_disabled/test/positive_expected_result.json b/assets/queries/terraform/oci/oci_subnet_flow_logging_disabled/test/positive_expected_result.json new file mode 100644 index 00000000000..487c19e8a97 --- /dev/null +++ b/assets/queries/terraform/oci/oci_subnet_flow_logging_disabled/test/positive_expected_result.json @@ -0,0 +1,26 @@ +[ + { + "queryName": "VCN Subnet Without Flow Log", + "severity": "HIGH", + "line": 1, + "fileName": "positive1.tf" + }, + { + "queryName": "VCN Subnet Without Flow Log", + "severity": "HIGH", + "line": 7, + "fileName": "positive2.tf" + }, + { + "queryName": "VCN Subnet Without Flow Log", + "severity": "HIGH", + "line": 6, + "fileName": "positive3.tf" + }, + { + "queryName": "VCN Subnet Without Flow Log", + "severity": "HIGH", + "line": 11, + "fileName": "positive4.tf" + } +] \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_vcn_change_event_rule_missing/README.md b/assets/queries/terraform/oci/oci_vcn_change_event_rule_missing/README.md new file mode 100644 index 00000000000..5b1d6fa4753 --- /dev/null +++ b/assets/queries/terraform/oci/oci_vcn_change_event_rule_missing/README.md @@ -0,0 +1,54 @@ +# Regla KICS: Notificación para Cambios en VCN en OCI + +## Descripción General + +Esta regla de KICS para Terraform asegura que exista una regla de eventos (`oci_events_rule`) configurada para monitorizar y generar alertas ante cualquier cambio (creación, actualización o eliminación) en las Redes Virtuales en la Nube (VCN) de la cuenta de OCI. + +Las VCN son el componente fundamental de la red en OCI. Cambios no autorizados pueden tener un impacto severo en la conectividad y la seguridad de los servicios. + +## Lógica de la Regla + +La política verifica la configuración de los recursos `oci_events_rule` buscando los tres tipos de eventos fundamentales: +* `com.oraclecloud.virtualnetwork.createvcn` +* `com.oraclecloud.virtualnetwork.updatevcn` +* `com.oraclecloud.virtualnetwork.deletevcn` + +## Casos de Fallo Detectados + +### Caso 1: Regla Ausente (Missing) +* **Descripción:** No existe ninguna regla en la configuración que monitorice cambios en VCNs. +* **Ubicación:** Bloque `provider "oci"`. + +### Caso 2: Regla Incompleta (Incomplete) +* **Descripción:** Existe una regla pero no incluye los tres eventos requeridos (create, update, delete). +* **Ubicación:** Atributo `condition`. + +### Caso 3: Regla Deshabilitada (Disabled) +* **Descripción:** Existe una regla relevante pero el atributo `is_enabled` es `false`. +* **Ubicación:** Atributo `is_enabled`. + +## Solución + +Asegúrate de incluir una regla habilitada con los eventos correspondientes: + +```terraform +resource "oci_events_rule" "vcn_change_rule" { + display_name = "audit-rule-for-vcn-changes" + compartment_id = var.tenancy_ocid + is_enabled = true + + condition = jsonencode({ + eventType = [ + "com.oraclecloud.virtualnetwork.createvcn", + "com.oraclecloud.virtualnetwork.updatevcn", + "com.oraclecloud.virtualnetwork.deletevcn" + ] + }) + + actions { + actions { + action_type = "ONS" + topic_id = oci_ons_notification_topic.security_alerts_topic.id + } + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_vcn_change_event_rule_missing/metadata.json b/assets/queries/terraform/oci/oci_vcn_change_event_rule_missing/metadata.json new file mode 100644 index 00000000000..361d6d05535 --- /dev/null +++ b/assets/queries/terraform/oci/oci_vcn_change_event_rule_missing/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "419bb431-bf37-4e5d-bda6-af3750e808e4", + "queryName": "Event Rule for VCN Changes is Missing", + "severity": "MEDIUM", + "category": "Networking and Firewall", + "descriptionText": "Ensures that an OCI event rule is configured to monitor and alert on changes to Virtual Cloud Networks (VCNs). Auditing the creation, modification, and deletion of VCNs is important for detecting unauthorized network changes that could impact security posture.", + "descriptionUrl": "https://registry.terraform.io/providers/oracle/oci/latest/docs/resources/events_rule", + "platform": "Terraform", + "descriptionID": "419bb431", + "cloudProvider": "oci", + "cwe": "CWE-778", + "riskScore": 3.0 +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_vcn_change_event_rule_missing/query.rego b/assets/queries/terraform/oci/oci_vcn_change_event_rule_missing/query.rego new file mode 100644 index 00000000000..f2223e8e8f1 --- /dev/null +++ b/assets/queries/terraform/oci/oci_vcn_change_event_rule_missing/query.rego @@ -0,0 +1,75 @@ +package Cx + +expected_event_types := [ + "com.oraclecloud.virtualnetwork.createvcn", + "com.oraclecloud.virtualnetwork.updatevcn", + "com.oraclecloud.virtualnetwork.deletevcn" +] + +# REGLA 1: Missing (Global) +# No existe ninguna regla en el proyecto que monitoree cambios en VCNs. +CxPolicy[result] { + doc := input.document[i] + _ := doc.provider.oci + + any_vcn_rule := [rule | + rule := input.document[_].resource.oci_events_rule[_] + event := expected_event_types[_] + contains(rule.condition, event) + ] + + count(any_vcn_rule) == 0 + + result := { + "documentId": doc.id, + "searchKey": "provider.oci", + "issueType": "MissingAttribute", + "keyExpectedValue": "An 'oci_events_rule' for VCN changes should exist", + "keyActualValue": "No 'oci_events_rule' found for VCN changes", + } +} + +# REGLA 2: Incomplete (Local) +# La regla existe pero le faltan eventos (ej: tiene create pero falta delete). +CxPolicy[result] { + rule := input.document[i].resource.oci_events_rule[name] + + matches := [event | + event := expected_event_types[_] + contains(rule.condition, event) + ] + + count(matches) > 0 + count(matches) < count(expected_event_types) + + missing_count := count(expected_event_types) - count(matches) + + result := { + "documentId": input.document[i].id, + "searchKey": sprintf("resource.oci_events_rule.%s.condition", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "The rule condition should include all 3 VCN events (create, update, delete)", + "keyActualValue": sprintf("The rule is missing %d VCN event(s)", [missing_count]), + } +} + +# REGLA 3: Disabled (Local) +# La regla es relevante para VCN pero está apagada. +CxPolicy[result] { + rule := input.document[i].resource.oci_events_rule[name] + + matches := [event | + event := expected_event_types[_] + contains(rule.condition, event) + ] + count(matches) > 0 + rule.is_enabled == false + + result := { + "documentId": input.document[i].id, + "searchKey": sprintf("resource.oci_events_rule.%s.is_enabled", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "'is_enabled' should be true", + "keyActualValue": "'is_enabled' is false", + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_vcn_change_event_rule_missing/test/negative1.tf b/assets/queries/terraform/oci/oci_vcn_change_event_rule_missing/test/negative1.tf new file mode 100644 index 00000000000..a1a9b5dbc92 --- /dev/null +++ b/assets/queries/terraform/oci/oci_vcn_change_event_rule_missing/test/negative1.tf @@ -0,0 +1,24 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_events_rule" "compliant_vcn_rule" { + display_name = "CompliantVCNRule" + compartment_id = "ocid1.tenancy.oc1..aaaa" + is_enabled = true + + condition = jsonencode({ + "eventType": [ + "com.oraclecloud.virtualnetwork.createvcn", + "com.oraclecloud.virtualnetwork.updatevcn", + "com.oraclecloud.virtualnetwork.deletevcn" + ] + }) + + actions { + actions { + action_type = "ONS" + topic_id = "ocid1.onstopic.oc1..aaaa" + } + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_vcn_change_event_rule_missing/test/positive1.tf b/assets/queries/terraform/oci/oci_vcn_change_event_rule_missing/test/positive1.tf new file mode 100644 index 00000000000..b6bc413c48c --- /dev/null +++ b/assets/queries/terraform/oci/oci_vcn_change_event_rule_missing/test/positive1.tf @@ -0,0 +1,9 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_core_vcn" "test_vcn" { + compartment_id = "ocid1.compartment.oc1..aaaa" + cidr_block = "10.0.0.0/16" + display_name = "vulnerable_vcn" +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_vcn_change_event_rule_missing/test/positive2.tf b/assets/queries/terraform/oci/oci_vcn_change_event_rule_missing/test/positive2.tf new file mode 100644 index 00000000000..cd99b64e5b6 --- /dev/null +++ b/assets/queries/terraform/oci/oci_vcn_change_event_rule_missing/test/positive2.tf @@ -0,0 +1,23 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_events_rule" "incomplete_vcn_rule" { + display_name = "IncompleteVCNRule" + compartment_id = "ocid1.tenancy.oc1..aaaa" + is_enabled = true + + condition = jsonencode({ + "eventType": [ + "com.oraclecloud.virtualnetwork.createvcn", + "com.oraclecloud.virtualnetwork.updatevcn" + ] + }) + + actions { + actions { + action_type = "ONS" + topic_id = "ocid1.onstopic.oc1..aaaa" + } + } +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_vcn_change_event_rule_missing/test/positive3.tf b/assets/queries/terraform/oci/oci_vcn_change_event_rule_missing/test/positive3.tf new file mode 100644 index 00000000000..52e7ca34bc9 --- /dev/null +++ b/assets/queries/terraform/oci/oci_vcn_change_event_rule_missing/test/positive3.tf @@ -0,0 +1,25 @@ +provider "oci" { + region = "us-ashburn-1" +} + +resource "oci_events_rule" "disabled_vcn_rule" { + display_name = "DisabledVCNRule" + compartment_id = "ocid1.tenancy.oc1..aaaa" + + condition = jsonencode({ + "eventType": [ + "com.oraclecloud.virtualnetwork.createvcn", + "com.oraclecloud.virtualnetwork.updatevcn", + "com.oraclecloud.virtualnetwork.deletevcn" + ] + }) + + actions { + actions { + action_type = "ONS" + topic_id = "ocid1.onstopic.oc1..aaaa" + } + } + + is_enabled = false +} \ No newline at end of file diff --git a/assets/queries/terraform/oci/oci_vcn_change_event_rule_missing/test/positive_expected_result.json b/assets/queries/terraform/oci/oci_vcn_change_event_rule_missing/test/positive_expected_result.json new file mode 100644 index 00000000000..7789396e1ca --- /dev/null +++ b/assets/queries/terraform/oci/oci_vcn_change_event_rule_missing/test/positive_expected_result.json @@ -0,0 +1,20 @@ +[ + { + "queryName": "Event Rule for VCN Changes is Missing", + "severity": "MEDIUM", + "line": 1, + "fileName": "positive1.tf" + }, + { + "queryName": "Event Rule for VCN Changes is Missing", + "severity": "MEDIUM", + "line": 10, + "fileName": "positive2.tf" + }, + { + "queryName": "Event Rule for VCN Changes is Missing", + "severity": "MEDIUM", + "line": 24, + "fileName": "positive3.tf" + } +] \ No newline at end of file From d3d868424ae70006557789b47a295385687906d5 Mon Sep 17 00:00:00 2001 From: Antero Silva Date: Mon, 23 Mar 2026 19:17:52 +0000 Subject: [PATCH 2/4] coorected gcp_sql_postgresql_log_statement_improperly_set query --- .../query.rego | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/assets/queries/terraform/gcp/gcp_sql_postgresql_log_statement_improperly_set/query.rego b/assets/queries/terraform/gcp/gcp_sql_postgresql_log_statement_improperly_set/query.rego index a8fc36e8108..8cef3c00b9d 100644 --- a/assets/queries/terraform/gcp/gcp_sql_postgresql_log_statement_improperly_set/query.rego +++ b/assets/queries/terraform/gcp/gcp_sql_postgresql_log_statement_improperly_set/query.rego @@ -1,5 +1,7 @@ package Cx +import data.generic.terraform as tf_lib + ensure_array(x) = x { is_array(x) } ensure_array(x) = [x] { is_object(x) } @@ -20,7 +22,9 @@ CxPolicy[result] { result := { "documentId": doc.id, - "searchKey": sprintf("resource.google_sql_database_instance.%s.settings.database_flags", [name]), + "resourceType": "google_sql_database_instance", + "resourceName": tf_lib.get_resource_name(resource, name), + "searchKey": sprintf("google_sql_database_instance[%s].settings.database_flags", [name]), "issueType": "MissingAttribute", "keyExpectedValue": "'database_flags' should include 'log_statement' set to 'ddl', 'mod', or 'all'", "keyActualValue": "'log_statement' flag is missing (defaults to 'none')", @@ -41,7 +45,9 @@ CxPolicy[result] { result := { "documentId": doc.id, - "searchKey": sprintf("resource.google_sql_database_instance.%s.settings.database_flags", [name]), + "resourceType": "google_sql_database_instance", + "resourceName": tf_lib.get_resource_name(resource, name), + "searchKey": sprintf("google_sql_database_instance[%s].settings.database_flags", [name]), "issueType": "IncorrectValue", "keyExpectedValue": "'log_statement' should be set to 'ddl', 'mod', or 'all'", "keyActualValue": "'log_statement' is set to 'none'", From d133b84a7575d16db9feb70a970b9c25be9c9f92 Mon Sep 17 00:00:00 2001 From: Antero Silva Date: Mon, 23 Mar 2026 19:48:24 +0000 Subject: [PATCH 3/4] query gcp_sql_postgresql_log_statement_improperly_set correction --- .../metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/queries/terraform/gcp/gcp_sql_postgresql_log_statement_improperly_set/metadata.json b/assets/queries/terraform/gcp/gcp_sql_postgresql_log_statement_improperly_set/metadata.json index 46a857f7d7b..694b953ce2a 100644 --- a/assets/queries/terraform/gcp/gcp_sql_postgresql_log_statement_improperly_set/metadata.json +++ b/assets/queries/terraform/gcp/gcp_sql_postgresql_log_statement_improperly_set/metadata.json @@ -9,5 +9,5 @@ "descriptionID": "ede2d9e4", "cloudProvider": "gcp", "cwe": "CWE-778", - "riskScore": 5.0 + "riskScore": "5.0" } \ No newline at end of file From df768b9144d65f3ce89c9c7ebb95ffff1316e32c Mon Sep 17 00:00:00 2001 From: Antero Silva Date: Mon, 23 Mar 2026 20:31:26 +0000 Subject: [PATCH 4/4] fix issues in the query tests for the new queries --- .../metadata.json | 2 +- .../query.rego | 94 +++++----- .../metadata.json | 2 +- .../query.rego | 82 ++++----- .../metadata.json | 2 +- .../query.rego | 54 +++--- .../metadata.json | 2 +- .../query.rego | 72 ++++---- .../metadata.json | 2 +- .../query.rego | 38 +++-- .../azure_bastion_host_missing/metadata.json | 2 +- .../azure_bastion_host_missing/query.rego | 44 ++--- .../metadata.json | 2 +- .../query.rego | 34 ++-- .../test/negative1.tf | 7 + .../metadata.json | 2 +- .../query.rego | 40 +++-- .../metadata.json | 2 +- .../query.rego | 82 ++++----- .../test/positive_expected_result.json | 4 +- .../metadata.json | 2 +- .../query.rego | 70 ++++---- .../metadata.json | 2 +- .../query.rego | 38 +++-- .../metadata.json | 2 +- .../query.rego | 38 +++-- .../metadata.json | 2 +- .../query.rego | 42 ++--- .../test/negative1.tf | 7 + .../metadata.json | 2 +- .../query.rego | 42 ++--- .../test/negative1.tf | 7 + .../metadata.json | 2 +- .../query.rego | 76 +++++---- .../metadata.json | 2 +- .../query.rego | 112 ++++++------ .../metadata.json | 2 +- .../query.rego | 74 ++++---- .../metadata.json | 2 +- .../query.rego | 38 +++-- .../metadata.json | 2 +- .../query.rego | 72 ++++---- .../metadata.json | 2 +- .../query.rego | 74 ++++---- .../metadata.json | 2 +- .../query.rego | 68 ++++---- .../metadata.json | 2 +- .../query.rego | 46 ++--- .../metadata.json | 2 +- .../query.rego | 72 ++++---- .../metadata.json | 2 +- .../query.rego | 122 ++++++------- .../metadata.json | 2 +- .../query.rego | 110 ++++++------ .../metadata.json | 2 +- .../query.rego | 160 +++++++++--------- .../metadata.json | 2 +- .../query.rego | 72 ++++---- .../metadata.json | 2 +- .../query.rego | 74 ++++---- .../metadata.json | 2 +- .../query.rego | 158 +++++++++-------- .../metadata.json | 2 +- .../query.rego | 160 +++++++++--------- .../metadata.json | 2 +- .../gcp_access_approval_disabled/query.rego | 82 ++++----- .../metadata.json | 2 +- .../query.rego | 74 ++++---- .../metadata.json | 2 +- .../query.rego | 72 ++++---- .../metadata.json | 2 +- .../query.rego | 90 +++++----- .../metadata.json | 2 +- .../query.rego | 110 ++++++------ .../metadata.json | 2 +- .../query.rego | 72 ++++---- .../metadata.json | 2 +- .../query.rego | 110 ++++++------ .../gcp_gke_manual_iam_check/metadata.json | 2 +- .../gcp/gcp_gke_manual_iam_check/query.rego | 72 ++++---- .../metadata.json | 2 +- .../query.rego | 140 ++++++++------- .../gcp_gke_sandbox_disabled/metadata.json | 2 +- .../gcp/gcp_gke_sandbox_disabled/query.rego | 140 ++++++++------- .../metadata.json | 2 +- .../query.rego | 112 ++++++------ .../metadata.json | 2 +- .../query.rego | 72 ++++---- .../metadata.json | 2 +- .../query.rego | 72 ++++---- .../metadata.json | 2 +- .../query.rego | 72 ++++---- .../metadata.json | 2 +- .../query.rego | 110 ++++++------ .../metadata.json | 2 +- .../query.rego | 54 +++--- 96 files changed, 2060 insertions(+), 1769 deletions(-) create mode 100644 assets/queries/terraform/azure/azure_defender_easm_enabled_manual/test/negative1.tf create mode 100644 assets/queries/terraform/azure/azure_mysql_audit_log_enabled_manual/test/negative1.tf create mode 100644 assets/queries/terraform/azure/azure_mysql_audit_log_events_connection_manual/test/negative1.tf diff --git a/assets/queries/terraform/azure/azure_app_service_application_insights_not_configured/metadata.json b/assets/queries/terraform/azure/azure_app_service_application_insights_not_configured/metadata.json index 72ff8d8cb65..afe786dd933 100644 --- a/assets/queries/terraform/azure/azure_app_service_application_insights_not_configured/metadata.json +++ b/assets/queries/terraform/azure/azure_app_service_application_insights_not_configured/metadata.json @@ -9,5 +9,5 @@ "descriptionID": "721c26ff", "cloudProvider": "azure", "cwe": "CWE-778", - "riskScore": 5.0 + "riskScore": "5.0" } \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_app_service_application_insights_not_configured/query.rego b/assets/queries/terraform/azure/azure_app_service_application_insights_not_configured/query.rego index 52d2cefd933..f20fb3ab448 100644 --- a/assets/queries/terraform/azure/azure_app_service_application_insights_not_configured/query.rego +++ b/assets/queries/terraform/azure/azure_app_service_application_insights_not_configured/query.rego @@ -1,44 +1,50 @@ -package Cx - -targets := { - "azurerm_linux_web_app", - "azurerm_windows_web_app", - "azurerm_linux_function_app", - "azurerm_windows_function_app" -} - -# REGLA 1: El bloque 'app_settings' no existe en absoluto. -CxPolicy[result] { - doc := input.document[i] - resource_type := targets[t] - app := doc.resource[resource_type][name] - - not app.app_settings - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.%s.%s", [resource_type, name]), - "issueType": "MissingAttribute", - "keyExpectedValue": sprintf("'%s.%s' should have 'app_settings' defined", [resource_type, name]), - "keyActualValue": sprintf("'%s.%s' is missing 'app_settings'", [resource_type, name]), - } -} - -# REGLA 2: El bloque 'app_settings' existe pero no tiene ninguna clave de App Insights. -CxPolicy[result] { - doc := input.document[i] - resource_type := targets[t] - app := doc.resource[resource_type][name] - - app.app_settings - not app.app_settings["APPLICATIONINSIGHTS_CONNECTION_STRING"] - not app.app_settings["APPINSIGHTS_INSTRUMENTATIONKEY"] - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.%s.%s.app_settings", [resource_type, name]), - "issueType": "IncorrectValue", - "keyExpectedValue": "'app_settings' should contain 'APPLICATIONINSIGHTS_CONNECTION_STRING'", - "keyActualValue": "'app_settings' does not contain Application Insights configuration", - } -} \ No newline at end of file +package Cx + +import data.generic.terraform as tf_lib + +targets := { + "azurerm_linux_web_app", + "azurerm_windows_web_app", + "azurerm_linux_function_app", + "azurerm_windows_function_app" +} + +# REGLA 1: El bloque 'app_settings' no existe en absoluto. +CxPolicy[result] { + doc := input.document[i] + resource_type := targets[t] + app := doc.resource[resource_type][name] + + not app.app_settings + + result := { + "documentId": doc.id, + "resourceType": resource_type, + "resourceName": tf_lib.get_resource_name(app, name), + "searchKey": sprintf("%s[%s]", [resource_type, name]), + "issueType": "MissingAttribute", + "keyExpectedValue": sprintf("'%s.%s' should have 'app_settings' defined", [resource_type, name]), + "keyActualValue": sprintf("'%s.%s' is missing 'app_settings'", [resource_type, name]), + } +} + +# REGLA 2: El bloque 'app_settings' existe pero no tiene ninguna clave de App Insights. +CxPolicy[result] { + doc := input.document[i] + resource_type := targets[t] + app := doc.resource[resource_type][name] + + app.app_settings + not app.app_settings["APPLICATIONINSIGHTS_CONNECTION_STRING"] + not app.app_settings["APPINSIGHTS_INSTRUMENTATIONKEY"] + + result := { + "documentId": doc.id, + "resourceType": resource_type, + "resourceName": tf_lib.get_resource_name(app, name), + "searchKey": sprintf("%s[%s].app_settings", [resource_type, name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "'app_settings' should contain 'APPLICATIONINSIGHTS_CONNECTION_STRING'", + "keyActualValue": "'app_settings' does not contain Application Insights configuration", + } +} diff --git a/assets/queries/terraform/azure/azure_app_service_http_logs_disabled/metadata.json b/assets/queries/terraform/azure/azure_app_service_http_logs_disabled/metadata.json index b30be71a396..95eaf2da695 100644 --- a/assets/queries/terraform/azure/azure_app_service_http_logs_disabled/metadata.json +++ b/assets/queries/terraform/azure/azure_app_service_http_logs_disabled/metadata.json @@ -9,5 +9,5 @@ "descriptionID": "72eb3dd8", "cloudProvider": "azure", "cwe": "CWE-778", - "riskScore": 5.0 + "riskScore": "5.0" } \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_app_service_http_logs_disabled/query.rego b/assets/queries/terraform/azure/azure_app_service_http_logs_disabled/query.rego index 8ed6d368b34..7b56ce92f11 100644 --- a/assets/queries/terraform/azure/azure_app_service_http_logs_disabled/query.rego +++ b/assets/queries/terraform/azure/azure_app_service_http_logs_disabled/query.rego @@ -1,38 +1,44 @@ -package Cx - -targets := {"azurerm_linux_web_app", "azurerm_windows_web_app"} - -# REGLA 1: El bloque 'logs' no existe en el App Service. -CxPolicy[result] { - doc := input.document[i] - resource_type := targets[t] - app := doc.resource[resource_type][name] - - not app.logs - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.%s.%s", [resource_type, name]), - "issueType": "MissingAttribute", - "keyExpectedValue": sprintf("'%s.%s' should have a 'logs' block defined", [resource_type, name]), - "keyActualValue": sprintf("'%s.%s' is missing the 'logs' block", [resource_type, name]), - } -} - -# REGLA 2: El bloque 'logs' existe pero no tiene 'http_logs' configurado. -CxPolicy[result] { - doc := input.document[i] - resource_type := targets[t] - app := doc.resource[resource_type][name] - - app.logs - not app.logs.http_logs - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.%s.%s.logs", [resource_type, name]), - "issueType": "MissingAttribute", - "keyExpectedValue": sprintf("'%s.%s.logs' should have 'http_logs' configured", [resource_type, name]), - "keyActualValue": sprintf("'%s.%s.logs' is missing 'http_logs'", [resource_type, name]), - } -} \ No newline at end of file +package Cx + +import data.generic.terraform as tf_lib + +targets := {"azurerm_linux_web_app", "azurerm_windows_web_app"} + +# REGLA 1: El bloque 'logs' no existe en el App Service. +CxPolicy[result] { + doc := input.document[i] + resource_type := targets[t] + app := doc.resource[resource_type][name] + + not app.logs + + result := { + "documentId": doc.id, + "resourceType": resource_type, + "resourceName": tf_lib.get_resource_name(app, name), + "searchKey": sprintf("%s[%s]", [resource_type, name]), + "issueType": "MissingAttribute", + "keyExpectedValue": sprintf("'%s.%s' should have a 'logs' block defined", [resource_type, name]), + "keyActualValue": sprintf("'%s.%s' is missing the 'logs' block", [resource_type, name]), + } +} + +# REGLA 2: El bloque 'logs' existe pero no tiene 'http_logs' configurado. +CxPolicy[result] { + doc := input.document[i] + resource_type := targets[t] + app := doc.resource[resource_type][name] + + app.logs + not app.logs.http_logs + + result := { + "documentId": doc.id, + "resourceType": resource_type, + "resourceName": tf_lib.get_resource_name(app, name), + "searchKey": sprintf("%s[%s].logs", [resource_type, name]), + "issueType": "MissingAttribute", + "keyExpectedValue": sprintf("'%s.%s.logs' should have 'http_logs' configured", [resource_type, name]), + "keyActualValue": sprintf("'%s.%s.logs' is missing 'http_logs'", [resource_type, name]), + } +} diff --git a/assets/queries/terraform/azure/azure_backup_vault_cmk_encryption_disabled/metadata.json b/assets/queries/terraform/azure/azure_backup_vault_cmk_encryption_disabled/metadata.json index 4b63512897a..de557efe53d 100644 --- a/assets/queries/terraform/azure/azure_backup_vault_cmk_encryption_disabled/metadata.json +++ b/assets/queries/terraform/azure/azure_backup_vault_cmk_encryption_disabled/metadata.json @@ -9,5 +9,5 @@ "descriptionID": "bf4474a7", "cloudProvider": "azure", "cwe": "CWE-326", - "riskScore": 5.0 + "riskScore": "5.0" } \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_backup_vault_cmk_encryption_disabled/query.rego b/assets/queries/terraform/azure/azure_backup_vault_cmk_encryption_disabled/query.rego index 114fb74bd78..1e78a4e383c 100644 --- a/assets/queries/terraform/azure/azure_backup_vault_cmk_encryption_disabled/query.rego +++ b/assets/queries/terraform/azure/azure_backup_vault_cmk_encryption_disabled/query.rego @@ -1,25 +1,29 @@ -package Cx - -has_cmk_configured(doc, vault_id) { - cmk := doc.resource.azurerm_data_protection_backup_vault_customer_managed_key[_] - cmk.data_protection_backup_vault_id == vault_id - cmk.key_vault_key_id -} - -# REGLA 1: El Backup Vault no tiene cifrado CMK configurado a través del recurso de asociación. -CxPolicy[result] { - doc := input.document[i] - vault := doc.resource.azurerm_data_protection_backup_vault[name] - - vault_id := sprintf("${azurerm_data_protection_backup_vault.%s.id}", [name]) - - not has_cmk_configured(doc, vault_id) - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.azurerm_data_protection_backup_vault.%s", [name]), - "issueType": "MissingAttribute", - "keyExpectedValue": sprintf("'azurerm_data_protection_backup_vault.%s' should be associated with an 'azurerm_data_protection_backup_vault_customer_managed_key' resource", [name]), - "keyActualValue": sprintf("'azurerm_data_protection_backup_vault.%s' is using Platform-Managed Keys (default)", [name]), - } -} \ No newline at end of file +package Cx + +import data.generic.terraform as tf_lib + +has_cmk_configured(doc, vault_id) { + cmk := doc.resource.azurerm_data_protection_backup_vault_customer_managed_key[_] + cmk.data_protection_backup_vault_id == vault_id + cmk.key_vault_key_id +} + +# REGLA 1: El Backup Vault no tiene cifrado CMK configurado a través del recurso de asociación. +CxPolicy[result] { + doc := input.document[i] + vault := doc.resource.azurerm_data_protection_backup_vault[name] + + vault_id := sprintf("${azurerm_data_protection_backup_vault.%s.id}", [name]) + + not has_cmk_configured(doc, vault_id) + + result := { + "documentId": doc.id, + "resourceType": "azurerm_data_protection_backup_vault", + "resourceName": tf_lib.get_resource_name(vault, name), + "searchKey": sprintf("azurerm_data_protection_backup_vault[%s]", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": sprintf("'azurerm_data_protection_backup_vault.%s' should be associated with an 'azurerm_data_protection_backup_vault_customer_managed_key' resource", [name]), + "keyActualValue": sprintf("'azurerm_data_protection_backup_vault.%s' is using Platform-Managed Keys (default)", [name]), + } +} diff --git a/assets/queries/terraform/azure/azure_backup_vault_cross_region_restore_disabled/metadata.json b/assets/queries/terraform/azure/azure_backup_vault_cross_region_restore_disabled/metadata.json index 5bb3f8ea098..2dc65346e16 100644 --- a/assets/queries/terraform/azure/azure_backup_vault_cross_region_restore_disabled/metadata.json +++ b/assets/queries/terraform/azure/azure_backup_vault_cross_region_restore_disabled/metadata.json @@ -9,5 +9,5 @@ "descriptionID": "0117fb32", "cloudProvider": "azure", "cwe": "CWE-668", - "riskScore": 5.0 + "riskScore": "5.0" } \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_backup_vault_cross_region_restore_disabled/query.rego b/assets/queries/terraform/azure/azure_backup_vault_cross_region_restore_disabled/query.rego index 899dc447728..c119b997648 100644 --- a/assets/queries/terraform/azure/azure_backup_vault_cross_region_restore_disabled/query.rego +++ b/assets/queries/terraform/azure/azure_backup_vault_cross_region_restore_disabled/query.rego @@ -1,33 +1,39 @@ -package Cx - -# REGLA 1: Configuración Ausente. -CxPolicy[result] { - doc := input.document[i] - vault := doc.resource.azurerm_data_protection_backup_vault[name] - - object.get(vault, "cross_region_restore_enabled", "undefined") == "undefined" - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.azurerm_data_protection_backup_vault.%s", [name]), - "issueType": "MissingAttribute", - "keyExpectedValue": sprintf("'azurerm_data_protection_backup_vault.%s' should have 'cross_region_restore_enabled' set to true", [name]), - "keyActualValue": sprintf("'azurerm_data_protection_backup_vault.%s' is missing 'cross_region_restore_enabled'", [name]), - } -} - -# REGLA 2: Configuración Incorrecta (Deshabilitado explícitamente). -CxPolicy[result] { - doc := input.document[i] - vault := doc.resource.azurerm_data_protection_backup_vault[name] - - vault.cross_region_restore_enabled == false - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.azurerm_data_protection_backup_vault.%s.cross_region_restore_enabled", [name]), - "issueType": "IncorrectValue", - "keyExpectedValue": "'cross_region_restore_enabled' should be set to true", - "keyActualValue": "'cross_region_restore_enabled' is set to false", - } -} \ No newline at end of file +package Cx + +import data.generic.terraform as tf_lib + +# REGLA 1: Configuración Ausente. +CxPolicy[result] { + doc := input.document[i] + vault := doc.resource.azurerm_data_protection_backup_vault[name] + + object.get(vault, "cross_region_restore_enabled", "undefined") == "undefined" + + result := { + "documentId": doc.id, + "resourceType": "azurerm_data_protection_backup_vault", + "resourceName": tf_lib.get_resource_name(vault, name), + "searchKey": sprintf("azurerm_data_protection_backup_vault[%s]", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": sprintf("'azurerm_data_protection_backup_vault.%s' should have 'cross_region_restore_enabled' set to true", [name]), + "keyActualValue": sprintf("'azurerm_data_protection_backup_vault.%s' is missing 'cross_region_restore_enabled'", [name]), + } +} + +# REGLA 2: Configuración Incorrecta (Deshabilitado explícitamente). +CxPolicy[result] { + doc := input.document[i] + vault := doc.resource.azurerm_data_protection_backup_vault[name] + + vault.cross_region_restore_enabled == false + + result := { + "documentId": doc.id, + "resourceType": "azurerm_data_protection_backup_vault", + "resourceName": tf_lib.get_resource_name(vault, name), + "searchKey": sprintf("azurerm_data_protection_backup_vault[%s].cross_region_restore_enabled", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "'cross_region_restore_enabled' should be set to true", + "keyActualValue": "'cross_region_restore_enabled' is set to false", + } +} diff --git a/assets/queries/terraform/azure/azure_backup_vault_infrastructure_encryption_disabled/metadata.json b/assets/queries/terraform/azure/azure_backup_vault_infrastructure_encryption_disabled/metadata.json index e766039db6a..72af6f2ffac 100644 --- a/assets/queries/terraform/azure/azure_backup_vault_infrastructure_encryption_disabled/metadata.json +++ b/assets/queries/terraform/azure/azure_backup_vault_infrastructure_encryption_disabled/metadata.json @@ -9,5 +9,5 @@ "descriptionID": "f7bf03d5", "cloudProvider": "azure", "cwe": "CWE-312", - "riskScore": 5.0 + "riskScore": "5.0" } \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_backup_vault_infrastructure_encryption_disabled/query.rego b/assets/queries/terraform/azure/azure_backup_vault_infrastructure_encryption_disabled/query.rego index 42de971f8c9..f76ea1d04fe 100644 --- a/assets/queries/terraform/azure/azure_backup_vault_infrastructure_encryption_disabled/query.rego +++ b/assets/queries/terraform/azure/azure_backup_vault_infrastructure_encryption_disabled/query.rego @@ -1,17 +1,21 @@ -package Cx - -# REGLA 1: Falta el bloque 'identity', necesario para gestionar cifrado avanzado. -CxPolicy[result] { - doc := input.document[i] - vault := doc.resource.azurerm_data_protection_backup_vault[name] - - not vault.identity - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.azurerm_data_protection_backup_vault.%s", [name]), - "issueType": "MissingAttribute", - "keyExpectedValue": sprintf("'azurerm_data_protection_backup_vault.%s' should have an 'identity' block to support advanced encryption", [name]), - "keyActualValue": sprintf("'azurerm_data_protection_backup_vault.%s' is missing the 'identity' block", [name]), - } -} \ No newline at end of file +package Cx + +import data.generic.terraform as tf_lib + +# REGLA 1: Falta el bloque 'identity', necesario para gestionar cifrado avanzado. +CxPolicy[result] { + doc := input.document[i] + vault := doc.resource.azurerm_data_protection_backup_vault[name] + + not vault.identity + + result := { + "documentId": doc.id, + "resourceType": "azurerm_data_protection_backup_vault", + "resourceName": tf_lib.get_resource_name(vault, name), + "searchKey": sprintf("azurerm_data_protection_backup_vault[%s]", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": sprintf("'azurerm_data_protection_backup_vault.%s' should have an 'identity' block to support advanced encryption", [name]), + "keyActualValue": sprintf("'azurerm_data_protection_backup_vault.%s' is missing the 'identity' block", [name]), + } +} diff --git a/assets/queries/terraform/azure/azure_bastion_host_missing/metadata.json b/assets/queries/terraform/azure/azure_bastion_host_missing/metadata.json index b21e74e168d..ca98958d416 100644 --- a/assets/queries/terraform/azure/azure_bastion_host_missing/metadata.json +++ b/assets/queries/terraform/azure/azure_bastion_host_missing/metadata.json @@ -9,5 +9,5 @@ "descriptionID": "a3e941c5", "cloudProvider": "azure", "cwe": "CWE-284", - "riskScore": 5.0 + "riskScore": "5.0" } \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_bastion_host_missing/query.rego b/assets/queries/terraform/azure/azure_bastion_host_missing/query.rego index 0f5dabba3ac..b777996ee5d 100644 --- a/assets/queries/terraform/azure/azure_bastion_host_missing/query.rego +++ b/assets/queries/terraform/azure/azure_bastion_host_missing/query.rego @@ -1,20 +1,24 @@ -package Cx - -# REGLA 1: Existe una VNet, pero no existe ningún recurso azurerm_bastion_host en el documento. -CxPolicy[result] { - doc := input.document[i] - - vnet := doc.resource.azurerm_virtual_network[name] - - bastions := [b | b := doc.resource.azurerm_bastion_host[_]] - - count(bastions) == 0 - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.azurerm_virtual_network.%s", [name]), - "issueType": "MissingAttribute", - "keyExpectedValue": sprintf("An 'azurerm_bastion_host' resource should be defined to protect Virtual Network '%s'", [name]), - "keyActualValue": "No 'azurerm_bastion_host' resource was found in the configuration", - } -} \ No newline at end of file +package Cx + +import data.generic.terraform as tf_lib + +# REGLA 1: Existe una VNet, pero no existe ningún recurso azurerm_bastion_host en el documento. +CxPolicy[result] { + doc := input.document[i] + + vnet := doc.resource.azurerm_virtual_network[name] + + bastions := [b | b := doc.resource.azurerm_bastion_host[_]] + + count(bastions) == 0 + + result := { + "documentId": doc.id, + "resourceType": "azurerm_virtual_network", + "resourceName": tf_lib.get_resource_name(vnet, name), + "searchKey": sprintf("azurerm_virtual_network[%s]", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": sprintf("An 'azurerm_bastion_host' resource should be defined to protect Virtual Network '%s'", [name]), + "keyActualValue": "No 'azurerm_bastion_host' resource was found in the configuration", + } +} diff --git a/assets/queries/terraform/azure/azure_defender_easm_enabled_manual/metadata.json b/assets/queries/terraform/azure/azure_defender_easm_enabled_manual/metadata.json index 9f1fe2d2c5a..637929c90d2 100644 --- a/assets/queries/terraform/azure/azure_defender_easm_enabled_manual/metadata.json +++ b/assets/queries/terraform/azure/azure_defender_easm_enabled_manual/metadata.json @@ -9,5 +9,5 @@ "descriptionID": "b9f8511b", "cloudProvider": "azure", "cwe": "CWE-778", - "riskScore": 0.0 + "riskScore": "0.0" } \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_defender_easm_enabled_manual/query.rego b/assets/queries/terraform/azure/azure_defender_easm_enabled_manual/query.rego index b0084fd4d98..e7bfbf42b0e 100644 --- a/assets/queries/terraform/azure/azure_defender_easm_enabled_manual/query.rego +++ b/assets/queries/terraform/azure/azure_defender_easm_enabled_manual/query.rego @@ -1,15 +1,19 @@ -package Cx - -# REGLA 1: Genera un aviso manual por cada Resource Group encontrado. -CxPolicy[result] { - doc := input.document[i] - rg := doc.resource.azurerm_resource_group[name] - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.azurerm_resource_group.%s", [name]), - "issueType": "MissingAttribute", - "keyExpectedValue": sprintf("Microsoft Defender EASM should be verified manually for resource group '%s'", [name]), - "keyActualValue": "EASM status cannot be verified statically via Terraform (Manual Verification Required)", - } -} \ No newline at end of file +package Cx + +import data.generic.terraform as tf_lib + +# REGLA 1: Genera un aviso manual por cada Resource Group encontrado. +CxPolicy[result] { + doc := input.document[i] + rg := doc.resource.azurerm_resource_group[name] + + result := { + "documentId": doc.id, + "resourceType": "azurerm_resource_group", + "resourceName": tf_lib.get_resource_name(rg, name), + "searchKey": sprintf("azurerm_resource_group[%s]", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": sprintf("Microsoft Defender EASM should be verified manually for resource group '%s'", [name]), + "keyActualValue": "EASM status cannot be verified statically via Terraform (Manual Verification Required)", + } +} diff --git a/assets/queries/terraform/azure/azure_defender_easm_enabled_manual/test/negative1.tf b/assets/queries/terraform/azure/azure_defender_easm_enabled_manual/test/negative1.tf new file mode 100644 index 00000000000..c8ce844cce0 --- /dev/null +++ b/assets/queries/terraform/azure/azure_defender_easm_enabled_manual/test/negative1.tf @@ -0,0 +1,7 @@ +resource "azurerm_storage_account" "no_rg_here" { + name = "storageaccountneg1" + resource_group_name = "rg-existing" + location = "East US" + account_tier = "Standard" + account_replication_type = "LRS" +} diff --git a/assets/queries/terraform/azure/azure_elastic_san_public_access_enabled/metadata.json b/assets/queries/terraform/azure/azure_elastic_san_public_access_enabled/metadata.json index 9bf0857c145..e5b0c20030c 100644 --- a/assets/queries/terraform/azure/azure_elastic_san_public_access_enabled/metadata.json +++ b/assets/queries/terraform/azure/azure_elastic_san_public_access_enabled/metadata.json @@ -9,5 +9,5 @@ "cloudProvider": "azure", "cwe": "CWE-284", "descriptionID": "660863a5", - "riskScore": 9.0 + "riskScore": "9.0" } \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_elastic_san_public_access_enabled/query.rego b/assets/queries/terraform/azure/azure_elastic_san_public_access_enabled/query.rego index ab3e86102fe..5cda347b958 100644 --- a/assets/queries/terraform/azure/azure_elastic_san_public_access_enabled/query.rego +++ b/assets/queries/terraform/azure/azure_elastic_san_public_access_enabled/query.rego @@ -1,18 +1,22 @@ -package Cx - -# REGLA 1: El bloque 'network_rule' no está definido. -# En azurerm_elastic_san_volume_group, la ausencia del bloque permite el acceso público. -CxPolicy[result] { - doc := input.document[i] - vg := doc.resource.azurerm_elastic_san_volume_group[name] - - not vg.network_rule - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.azurerm_elastic_san_volume_group.%s", [name]), - "issueType": "MissingAttribute", - "keyExpectedValue": sprintf("'azurerm_elastic_san_volume_group.%s' should have a 'network_rule' block to restrict public access", [name]), - "keyActualValue": sprintf("'azurerm_elastic_san_volume_group.%s' is missing the 'network_rule' block", [name]), - } -} \ No newline at end of file +package Cx + +import data.generic.terraform as tf_lib + +# REGLA 1: El bloque 'network_rule' no está definido. +# En azurerm_elastic_san_volume_group, la ausencia del bloque permite el acceso público. +CxPolicy[result] { + doc := input.document[i] + vg := doc.resource.azurerm_elastic_san_volume_group[name] + + not vg.network_rule + + result := { + "documentId": doc.id, + "resourceType": "azurerm_elastic_san_volume_group", + "resourceName": tf_lib.get_resource_name(vg, name), + "searchKey": sprintf("azurerm_elastic_san_volume_group[%s]", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": sprintf("'azurerm_elastic_san_volume_group.%s' should have a 'network_rule' block to restrict public access", [name]), + "keyActualValue": sprintf("'azurerm_elastic_san_volume_group.%s' is missing the 'network_rule' block", [name]), + } +} diff --git a/assets/queries/terraform/azure/azure_elastic_san_volume_group_cmk_encryption_disabled/metadata.json b/assets/queries/terraform/azure/azure_elastic_san_volume_group_cmk_encryption_disabled/metadata.json index 0da8c3ce89b..b0395ce911a 100644 --- a/assets/queries/terraform/azure/azure_elastic_san_volume_group_cmk_encryption_disabled/metadata.json +++ b/assets/queries/terraform/azure/azure_elastic_san_volume_group_cmk_encryption_disabled/metadata.json @@ -9,5 +9,5 @@ "cloudProvider": "azure", "cwe": "CWE-326", "descriptionID": "3b101ad5", - "riskScore": 5.0 + "riskScore": "5.0" } \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_elastic_san_volume_group_cmk_encryption_disabled/query.rego b/assets/queries/terraform/azure/azure_elastic_san_volume_group_cmk_encryption_disabled/query.rego index 9cee7397bb0..e1d3f3a9fa8 100644 --- a/assets/queries/terraform/azure/azure_elastic_san_volume_group_cmk_encryption_disabled/query.rego +++ b/assets/queries/terraform/azure/azure_elastic_san_volume_group_cmk_encryption_disabled/query.rego @@ -1,38 +1,44 @@ -package Cx - -# REGLA 1: El tipo de cifrado no es CMK o no está definido. -CxPolicy[result] { - doc := input.document[i] - vg := doc.resource.azurerm_elastic_san_volume_group[name] - - object.get(vg, "encryption_type", "undefined") != "EncryptionAtRestWithCustomerManagedKey" - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.azurerm_elastic_san_volume_group.%s", [name]), - "issueType": "IncorrectValue", - "keyExpectedValue": "'encryption_type' should be set to 'EncryptionAtRestWithCustomerManagedKey'", - "keyActualValue": sprintf("'encryption_type' is set to '%v'", [object.get(vg, "encryption_type", "PlatformKey (Default)")]), - } -} - -# REGLA 2: Si el tipo es CMK, debe existir tanto el bloque 'encryption' como el bloque 'identity'. -CxPolicy[result] { - doc := input.document[i] - vg := doc.resource.azurerm_elastic_san_volume_group[name] - vg.encryption_type == "EncryptionAtRestWithCustomerManagedKey" - - required_blocks := {"encryption", "identity"} - existing_blocks := {b | vg[b]} - missing := required_blocks - existing_blocks - - count(missing) > 0 - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.azurerm_elastic_san_volume_group.%s", [name]), - "issueType": "MissingAttribute", - "keyExpectedValue": sprintf("'azurerm_elastic_san_volume_group.%s' should have both 'encryption' and 'identity' blocks for CMK", [name]), - "keyActualValue": sprintf("'azurerm_elastic_san_volume_group.%s' is missing the following block(s): %s", [name, concat(", ", missing)]), - } -} \ No newline at end of file +package Cx + +import data.generic.terraform as tf_lib + +# REGLA 1: El tipo de cifrado no es CMK o no está definido. +CxPolicy[result] { + doc := input.document[i] + vg := doc.resource.azurerm_elastic_san_volume_group[name] + + object.get(vg, "encryption_type", "undefined") != "EncryptionAtRestWithCustomerManagedKey" + + result := { + "documentId": doc.id, + "resourceType": "azurerm_elastic_san_volume_group", + "resourceName": tf_lib.get_resource_name(vg, name), + "searchKey": sprintf("azurerm_elastic_san_volume_group[%s]", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "'encryption_type' should be set to 'EncryptionAtRestWithCustomerManagedKey'", + "keyActualValue": sprintf("'encryption_type' is set to '%v'", [object.get(vg, "encryption_type", "PlatformKey (Default)")]), + } +} + +# REGLA 2: Si el tipo es CMK, debe existir tanto el bloque 'encryption' como el bloque 'identity'. +CxPolicy[result] { + doc := input.document[i] + vg := doc.resource.azurerm_elastic_san_volume_group[name] + vg.encryption_type == "EncryptionAtRestWithCustomerManagedKey" + + required_blocks := {"encryption", "identity"} + existing_blocks := {b | vg[b]} + missing := required_blocks - existing_blocks + + count(missing) > 0 + + result := { + "documentId": doc.id, + "resourceType": "azurerm_elastic_san_volume_group", + "resourceName": tf_lib.get_resource_name(vg, name), + "searchKey": sprintf("azurerm_elastic_san_volume_group[%s]", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": sprintf("'azurerm_elastic_san_volume_group.%s' should have both 'encryption' and 'identity' blocks for CMK", [name]), + "keyActualValue": sprintf("'azurerm_elastic_san_volume_group.%s' is missing the following block(s): %s", [name, concat(", ", missing)]), + } +} diff --git a/assets/queries/terraform/azure/azure_elastic_san_volume_group_cmk_encryption_disabled/test/positive_expected_result.json b/assets/queries/terraform/azure/azure_elastic_san_volume_group_cmk_encryption_disabled/test/positive_expected_result.json index c0f988b83b3..32f203345b4 100644 --- a/assets/queries/terraform/azure/azure_elastic_san_volume_group_cmk_encryption_disabled/test/positive_expected_result.json +++ b/assets/queries/terraform/azure/azure_elastic_san_volume_group_cmk_encryption_disabled/test/positive_expected_result.json @@ -3,12 +3,12 @@ "queryName": "Elastic SAN Volume Group CMK Encryption Disabled", "severity": "MEDIUM", "line": 1, - "fileName": "positive.tf" + "fileName": "positive1.tf" }, { "queryName": "Elastic SAN Volume Group CMK Encryption Disabled", "severity": "MEDIUM", "line": 1, - "fileName": "positive.tf" + "fileName": "positive2.tf" } ] \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_iot_hub_defender_disabled/metadata.json b/assets/queries/terraform/azure/azure_iot_hub_defender_disabled/metadata.json index 4c7522e2bb0..9ab57e0d2f8 100644 --- a/assets/queries/terraform/azure/azure_iot_hub_defender_disabled/metadata.json +++ b/assets/queries/terraform/azure/azure_iot_hub_defender_disabled/metadata.json @@ -9,5 +9,5 @@ "cloudProvider": "azure", "cwe": "CWE-693", "descriptionID": "13be738b", - "riskScore": 5.0 + "riskScore": "5.0" } \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_iot_hub_defender_disabled/query.rego b/assets/queries/terraform/azure/azure_iot_hub_defender_disabled/query.rego index 13c444b6108..e6d34c49b56 100644 --- a/assets/queries/terraform/azure/azure_iot_hub_defender_disabled/query.rego +++ b/assets/queries/terraform/azure/azure_iot_hub_defender_disabled/query.rego @@ -1,33 +1,37 @@ -package Cx - -# REGLA 1: IoT Hub sin solución de seguridad (azurerm_iot_security_solution) asociada. -CxPolicy[result] { - doc := input.document[i] - iot_hub := doc.resource.azurerm_iothub[hub_name] - - hub_ref := sprintf("azurerm_iothub.%s.id", [hub_name]) - - solutions := [sol | - sol := doc.resource.azurerm_iot_security_solution[_] - current_hub_id := sol.iothub_ids[_] - check_hub_id(current_hub_id, hub_ref) - ] - - count(solutions) == 0 - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.azurerm_iothub.%s", [hub_name]), - "issueType": "MissingAttribute", - "keyExpectedValue": sprintf("'azurerm_iothub.%s' should be included in 'iothub_ids' of an 'azurerm_iot_security_solution'", [hub_name]), - "keyActualValue": sprintf("'azurerm_iothub.%s' is not associated with any 'azurerm_iot_security_solution'", [hub_name]), - } -} - -check_hub_id(current, target) { - current == target -} - -check_hub_id(current, target) { - current == sprintf("${%s}", [target]) -} \ No newline at end of file +package Cx + +import data.generic.terraform as tf_lib + +# REGLA 1: IoT Hub sin solución de seguridad (azurerm_iot_security_solution) asociada. +CxPolicy[result] { + doc := input.document[i] + iot_hub := doc.resource.azurerm_iothub[hub_name] + + hub_ref := sprintf("azurerm_iothub.%s.id", [hub_name]) + + solutions := [sol | + sol := doc.resource.azurerm_iot_security_solution[_] + current_hub_id := sol.iothub_ids[_] + check_hub_id(current_hub_id, hub_ref) + ] + + count(solutions) == 0 + + result := { + "documentId": doc.id, + "resourceType": "azurerm_iothub", + "resourceName": tf_lib.get_resource_name(iot_hub, hub_name), + "searchKey": sprintf("azurerm_iothub[%s]", [hub_name]), + "issueType": "MissingAttribute", + "keyExpectedValue": sprintf("'azurerm_iothub.%s' should be included in 'iothub_ids' of an 'azurerm_iot_security_solution'", [hub_name]), + "keyActualValue": sprintf("'azurerm_iothub.%s' is not associated with any 'azurerm_iot_security_solution'", [hub_name]), + } +} + +check_hub_id(current, target) { + current == target +} + +check_hub_id(current, target) { + current == sprintf("${%s}", [target]) +} diff --git a/assets/queries/terraform/azure/azure_key_vault_key_rotation_disabled/metadata.json b/assets/queries/terraform/azure/azure_key_vault_key_rotation_disabled/metadata.json index 6498e1bff29..cfe4588462c 100644 --- a/assets/queries/terraform/azure/azure_key_vault_key_rotation_disabled/metadata.json +++ b/assets/queries/terraform/azure/azure_key_vault_key_rotation_disabled/metadata.json @@ -9,5 +9,5 @@ "cloudProvider": "azure", "cwe": "CWE-320", "descriptionID": "ca1ffce5", - "riskScore": 5.0 + "riskScore": "5.0" } \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_key_vault_key_rotation_disabled/query.rego b/assets/queries/terraform/azure/azure_key_vault_key_rotation_disabled/query.rego index 13ca1cc6da7..9ff8c50a62f 100644 --- a/assets/queries/terraform/azure/azure_key_vault_key_rotation_disabled/query.rego +++ b/assets/queries/terraform/azure/azure_key_vault_key_rotation_disabled/query.rego @@ -1,17 +1,21 @@ -package Cx - -# REGLA 1: La clave del Key Vault no tiene política de rotación configurada. -CxPolicy[result] { - doc := input.document[i] - key := doc.resource.azurerm_key_vault_key[name] - - not key.rotation_policy - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.azurerm_key_vault_key.%s", [name]), - "issueType": "MissingAttribute", - "keyExpectedValue": sprintf("'azurerm_key_vault_key.%s' should have a 'rotation_policy' block defined", [name]), - "keyActualValue": sprintf("'azurerm_key_vault_key.%s' does not have a 'rotation_policy' block defined", [name]), - } -} \ No newline at end of file +package Cx + +import data.generic.terraform as tf_lib + +# REGLA 1: La clave del Key Vault no tiene política de rotación configurada. +CxPolicy[result] { + doc := input.document[i] + key := doc.resource.azurerm_key_vault_key[name] + + not key.rotation_policy + + result := { + "documentId": doc.id, + "resourceType": "azurerm_key_vault_key", + "resourceName": tf_lib.get_resource_name(key, name), + "searchKey": sprintf("azurerm_key_vault_key[%s]", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": sprintf("'azurerm_key_vault_key.%s' should have a 'rotation_policy' block defined", [name]), + "keyActualValue": sprintf("'azurerm_key_vault_key.%s' does not have a 'rotation_policy' block defined", [name]), + } +} diff --git a/assets/queries/terraform/azure/azure_managed_lustre_cmk_encryption_disabled/metadata.json b/assets/queries/terraform/azure/azure_managed_lustre_cmk_encryption_disabled/metadata.json index 752e13181c2..9b364bf8e86 100644 --- a/assets/queries/terraform/azure/azure_managed_lustre_cmk_encryption_disabled/metadata.json +++ b/assets/queries/terraform/azure/azure_managed_lustre_cmk_encryption_disabled/metadata.json @@ -9,5 +9,5 @@ "cloudProvider": "azure", "cwe": "CWE-326", "descriptionID": "7fc653e2", - "riskScore": 5.0 + "riskScore": "5.0" } \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_managed_lustre_cmk_encryption_disabled/query.rego b/assets/queries/terraform/azure/azure_managed_lustre_cmk_encryption_disabled/query.rego index d0b0f475678..c757a60bbc2 100644 --- a/assets/queries/terraform/azure/azure_managed_lustre_cmk_encryption_disabled/query.rego +++ b/assets/queries/terraform/azure/azure_managed_lustre_cmk_encryption_disabled/query.rego @@ -1,17 +1,21 @@ -package Cx - -# REGLA 1: El bloque 'encryption_key' no está definido. -CxPolicy[result] { - doc := input.document[i] - lustre := doc.resource.azurerm_managed_lustre_file_system[name] - - not lustre.encryption_key - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.azurerm_managed_lustre_file_system.%s", [name]), - "issueType": "MissingAttribute", - "keyExpectedValue": sprintf("'azurerm_managed_lustre_file_system.%s' should have an 'encryption_key' block defined", [name]), - "keyActualValue": sprintf("'azurerm_managed_lustre_file_system.%s' is missing the 'encryption_key' block", [name]), - } -} \ No newline at end of file +package Cx + +import data.generic.terraform as tf_lib + +# REGLA 1: El bloque 'encryption_key' no está definido. +CxPolicy[result] { + doc := input.document[i] + lustre := doc.resource.azurerm_managed_lustre_file_system[name] + + not lustre.encryption_key + + result := { + "documentId": doc.id, + "resourceType": "azurerm_managed_lustre_file_system", + "resourceName": tf_lib.get_resource_name(lustre, name), + "searchKey": sprintf("azurerm_managed_lustre_file_system[%s]", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": sprintf("'azurerm_managed_lustre_file_system.%s' should have an 'encryption_key' block defined", [name]), + "keyActualValue": sprintf("'azurerm_managed_lustre_file_system.%s' is missing the 'encryption_key' block", [name]), + } +} diff --git a/assets/queries/terraform/azure/azure_mysql_audit_log_enabled_manual/metadata.json b/assets/queries/terraform/azure/azure_mysql_audit_log_enabled_manual/metadata.json index 11e04045ca4..d385f664b47 100644 --- a/assets/queries/terraform/azure/azure_mysql_audit_log_enabled_manual/metadata.json +++ b/assets/queries/terraform/azure/azure_mysql_audit_log_enabled_manual/metadata.json @@ -9,5 +9,5 @@ "cloudProvider": "azure", "cwe": "CWE-778", "descriptionID": "8d8ac482", - "riskScore": 0.0 + "riskScore": "0.0" } \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_mysql_audit_log_enabled_manual/query.rego b/assets/queries/terraform/azure/azure_mysql_audit_log_enabled_manual/query.rego index 5c28617c5cf..0023adb1aec 100644 --- a/assets/queries/terraform/azure/azure_mysql_audit_log_enabled_manual/query.rego +++ b/assets/queries/terraform/azure/azure_mysql_audit_log_enabled_manual/query.rego @@ -1,19 +1,23 @@ -package Cx - -targets := {"azurerm_mssql_server", "azurerm_mysql_flexible_server"} - -# REGLA MANUAL: Detecta servidores MySQL para solicitar verificación de logs de auditoría. -CxPolicy[result] { - doc := input.document[i] - - resource_type := targets[t] - server := doc.resource[resource_type][name] - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.%s.%s", [resource_type, name]), - "issueType": "MissingAttribute", - "keyExpectedValue": sprintf("'audit_log_enabled' should be set to 'ON' for '%s.%s' (Manual Verification)", [resource_type, name]), - "keyActualValue": "Logging configuration requires manual verification or check of 'azurerm_mysql_configuration' resources", - } -} \ No newline at end of file +package Cx + +import data.generic.terraform as tf_lib + +targets := {"azurerm_mssql_server", "azurerm_mysql_flexible_server"} + +# REGLA MANUAL: Detecta servidores MySQL para solicitar verificación de logs de auditoría. +CxPolicy[result] { + doc := input.document[i] + + resource_type := targets[t] + server := doc.resource[resource_type][name] + + result := { + "documentId": doc.id, + "resourceType": resource_type, + "resourceName": tf_lib.get_resource_name(server, name), + "searchKey": sprintf("%s[%s]", [resource_type, name]), + "issueType": "MissingAttribute", + "keyExpectedValue": sprintf("'audit_log_enabled' should be set to 'ON' for '%s.%s' (Manual Verification)", [resource_type, name]), + "keyActualValue": "Logging configuration requires manual verification or check of 'azurerm_mysql_configuration' resources", + } +} diff --git a/assets/queries/terraform/azure/azure_mysql_audit_log_enabled_manual/test/negative1.tf b/assets/queries/terraform/azure/azure_mysql_audit_log_enabled_manual/test/negative1.tf new file mode 100644 index 00000000000..b1137217038 --- /dev/null +++ b/assets/queries/terraform/azure/azure_mysql_audit_log_enabled_manual/test/negative1.tf @@ -0,0 +1,7 @@ +resource "azurerm_storage_account" "no_mysql_here" { + name = "storageaccountneg1" + resource_group_name = "rg-test" + location = "West Europe" + account_tier = "Standard" + account_replication_type = "LRS" +} diff --git a/assets/queries/terraform/azure/azure_mysql_audit_log_events_connection_manual/metadata.json b/assets/queries/terraform/azure/azure_mysql_audit_log_events_connection_manual/metadata.json index d5160af4619..5c03bdb8c92 100644 --- a/assets/queries/terraform/azure/azure_mysql_audit_log_events_connection_manual/metadata.json +++ b/assets/queries/terraform/azure/azure_mysql_audit_log_events_connection_manual/metadata.json @@ -9,5 +9,5 @@ "cloudProvider": "azure", "cwe": "CWE-778", "descriptionID": "fe65cd89", - "riskScore": 0.0 + "riskScore": "0.0" } \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_mysql_audit_log_events_connection_manual/query.rego b/assets/queries/terraform/azure/azure_mysql_audit_log_events_connection_manual/query.rego index 91355c01e0e..cfc0c13ccf1 100644 --- a/assets/queries/terraform/azure/azure_mysql_audit_log_events_connection_manual/query.rego +++ b/assets/queries/terraform/azure/azure_mysql_audit_log_events_connection_manual/query.rego @@ -1,19 +1,23 @@ -package Cx - -targets := {"azurerm_mssql_server", "azurerm_mysql_flexible_server"} - -# REGLA MANUAL: Detecta servidores MySQL para solicitar verificación del evento CONNECTION. -CxPolicy[result] { - doc := input.document[i] - - resource_type := targets[t] - server := doc.resource[resource_type][name] - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.%s.%s", [resource_type, name]), - "issueType": "MissingAttribute", - "keyExpectedValue": sprintf("'audit_log_events' should include 'CONNECTION' for '%s.%s' (Manual Verification)", [resource_type, name]), - "keyActualValue": "Audit log event configuration requires manual verification or check of 'azurerm_mysql_configuration'", - } -} \ No newline at end of file +package Cx + +import data.generic.terraform as tf_lib + +targets := {"azurerm_mssql_server", "azurerm_mysql_flexible_server"} + +# REGLA MANUAL: Detecta servidores MySQL para solicitar verificación del evento CONNECTION. +CxPolicy[result] { + doc := input.document[i] + + resource_type := targets[t] + server := doc.resource[resource_type][name] + + result := { + "documentId": doc.id, + "resourceType": resource_type, + "resourceName": tf_lib.get_resource_name(server, name), + "searchKey": sprintf("%s[%s]", [resource_type, name]), + "issueType": "MissingAttribute", + "keyExpectedValue": sprintf("'audit_log_events' should include 'CONNECTION' for '%s.%s' (Manual Verification)", [resource_type, name]), + "keyActualValue": "Audit log event configuration requires manual verification or check of 'azurerm_mysql_configuration'", + } +} diff --git a/assets/queries/terraform/azure/azure_mysql_audit_log_events_connection_manual/test/negative1.tf b/assets/queries/terraform/azure/azure_mysql_audit_log_events_connection_manual/test/negative1.tf new file mode 100644 index 00000000000..b1137217038 --- /dev/null +++ b/assets/queries/terraform/azure/azure_mysql_audit_log_events_connection_manual/test/negative1.tf @@ -0,0 +1,7 @@ +resource "azurerm_storage_account" "no_mysql_here" { + name = "storageaccountneg1" + resource_group_name = "rg-test" + location = "West Europe" + account_tier = "Standard" + account_replication_type = "LRS" +} diff --git a/assets/queries/terraform/azure/azure_netapp_account_cmk_encryption_disabled/metadata.json b/assets/queries/terraform/azure/azure_netapp_account_cmk_encryption_disabled/metadata.json index e98c7036089..3a9cb5216a0 100644 --- a/assets/queries/terraform/azure/azure_netapp_account_cmk_encryption_disabled/metadata.json +++ b/assets/queries/terraform/azure/azure_netapp_account_cmk_encryption_disabled/metadata.json @@ -9,5 +9,5 @@ "cloudProvider": "azure", "cwe": "CWE-326", "descriptionID": "8bed44b5", - "riskScore": 5.0 + "riskScore": "5.0" } \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_netapp_account_cmk_encryption_disabled/query.rego b/assets/queries/terraform/azure/azure_netapp_account_cmk_encryption_disabled/query.rego index aaef399b3a3..6a686cfe748 100644 --- a/assets/queries/terraform/azure/azure_netapp_account_cmk_encryption_disabled/query.rego +++ b/assets/queries/terraform/azure/azure_netapp_account_cmk_encryption_disabled/query.rego @@ -1,36 +1,40 @@ -package Cx - -has_encryption_resource(doc, account_id) { - enc := doc.resource.azurerm_netapp_account_encryption[_] - check_id(enc.netapp_account_id, account_id) -} - -check_id(current, target) { - current == target -} - -check_id(current, target) { - current == sprintf("${%s}", [target]) -} - -has_inline_encryption(account) { - account.encryption.key_source == "Microsoft.KeyVault" -} - -# REGLA 1: La cuenta NetApp utiliza Platform-Managed Keys (falta configuración CMK). -CxPolicy[result] { - doc := input.document[i] - account := doc.resource.azurerm_netapp_account[name] - account_id := sprintf("azurerm_netapp_account.%s.id", [name]) - - not has_inline_encryption(account) - not has_encryption_resource(doc, account_id) - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.azurerm_netapp_account.%s", [name]), - "issueType": "MissingAttribute", - "keyExpectedValue": sprintf("'azurerm_netapp_account.%s' should have 'encryption.key_source' set to 'Microsoft.KeyVault' or have an 'azurerm_netapp_account_encryption' resource associated", [name]), - "keyActualValue": sprintf("'azurerm_netapp_account.%s' is using Platform-Managed Keys (default)", [name]), - } -} \ No newline at end of file +package Cx + +import data.generic.terraform as tf_lib + +has_encryption_resource(doc, account_id) { + enc := doc.resource.azurerm_netapp_account_encryption[_] + check_id(enc.netapp_account_id, account_id) +} + +check_id(current, target) { + current == target +} + +check_id(current, target) { + current == sprintf("${%s}", [target]) +} + +has_inline_encryption(account) { + account.encryption.key_source == "Microsoft.KeyVault" +} + +# REGLA 1: La cuenta NetApp utiliza Platform-Managed Keys (falta configuración CMK). +CxPolicy[result] { + doc := input.document[i] + account := doc.resource.azurerm_netapp_account[name] + account_id := sprintf("azurerm_netapp_account.%s.id", [name]) + + not has_inline_encryption(account) + not has_encryption_resource(doc, account_id) + + result := { + "documentId": doc.id, + "resourceType": "azurerm_netapp_account", + "resourceName": tf_lib.get_resource_name(account, name), + "searchKey": sprintf("azurerm_netapp_account[%s]", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": sprintf("'azurerm_netapp_account.%s' should have 'encryption.key_source' set to 'Microsoft.KeyVault' or have an 'azurerm_netapp_account_encryption' resource associated", [name]), + "keyActualValue": sprintf("'azurerm_netapp_account.%s' is using Platform-Managed Keys (default)", [name]), + } +} diff --git a/assets/queries/terraform/azure/azure_paas_private_endpoint_missing/metadata.json b/assets/queries/terraform/azure/azure_paas_private_endpoint_missing/metadata.json index a17522eb843..323e9a36184 100644 --- a/assets/queries/terraform/azure/azure_paas_private_endpoint_missing/metadata.json +++ b/assets/queries/terraform/azure/azure_paas_private_endpoint_missing/metadata.json @@ -9,5 +9,5 @@ "cloudProvider": "azure", "cwe": "CWE-200", "descriptionID": "dda11a20", - "riskScore": 5.0 + "riskScore": "5.0" } \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_paas_private_endpoint_missing/query.rego b/assets/queries/terraform/azure/azure_paas_private_endpoint_missing/query.rego index fc1b7a752a0..e012f8ad5e4 100644 --- a/assets/queries/terraform/azure/azure_paas_private_endpoint_missing/query.rego +++ b/assets/queries/terraform/azure/azure_paas_private_endpoint_missing/query.rego @@ -1,54 +1,58 @@ -package Cx - -targets := { - "azurerm_cosmosdb_account", - "azurerm_storage_account", - "azurerm_mssql_server", - "azurerm_key_vault", - "azurerm_container_registry", - "azurerm_servicebus_namespace", - "azurerm_mariadb_server", - "azurerm_postgresql_server", - "azurerm_mysql_server", - "azurerm_redis_cache", - "azurerm_eventhub_namespace", - "azurerm_automation_account", - "azurerm_data_factory", - "azurerm_synapse_workspace", - "azurerm_search_service" -} - -# REGLA MAESTRA: Verifica si los recursos de la lista targets tienen un Private Endpoint vinculado. -CxPolicy[result] { - doc := input.document[i] - - resource_type := targets[t] - resource_instances := doc.resource[resource_type] - resource_instance := resource_instances[name] - - target_id := sprintf("%s.%s.id", [resource_type, name]) - - private_endpoints := [pe | - pe := doc.resource.azurerm_private_endpoint[_] - conn := pe.private_service_connection - check_resource_id(conn.private_connection_resource_id, target_id) - ] - - count(private_endpoints) == 0 - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.%s.%s", [resource_type, name]), - "issueType": "MissingAttribute", - "keyExpectedValue": sprintf("'%s.%s' should be linked to an 'azurerm_private_endpoint'", [resource_type, name]), - "keyActualValue": sprintf("'%s.%s' is not linked to any 'azurerm_private_endpoint' in this file", [resource_type, name]), - } -} - -check_resource_id(current, target) { - current == target -} - -check_resource_id(current, target) { - current == sprintf("${%s}", [target]) -} \ No newline at end of file +package Cx + +import data.generic.terraform as tf_lib + +targets := { + "azurerm_cosmosdb_account", + "azurerm_storage_account", + "azurerm_mssql_server", + "azurerm_key_vault", + "azurerm_container_registry", + "azurerm_servicebus_namespace", + "azurerm_mariadb_server", + "azurerm_postgresql_server", + "azurerm_mysql_server", + "azurerm_redis_cache", + "azurerm_eventhub_namespace", + "azurerm_automation_account", + "azurerm_data_factory", + "azurerm_synapse_workspace", + "azurerm_search_service" +} + +# REGLA MAESTRA: Verifica si los recursos de la lista targets tienen un Private Endpoint vinculado. +CxPolicy[result] { + doc := input.document[i] + + resource_type := targets[t] + resource_instances := doc.resource[resource_type] + resource_instance := resource_instances[name] + + target_id := sprintf("%s.%s.id", [resource_type, name]) + + private_endpoints := [pe | + pe := doc.resource.azurerm_private_endpoint[_] + conn := pe.private_service_connection + check_resource_id(conn.private_connection_resource_id, target_id) + ] + + count(private_endpoints) == 0 + + result := { + "documentId": doc.id, + "resourceType": resource_type, + "resourceName": tf_lib.get_resource_name(resource_instance, name), + "searchKey": sprintf("%s[%s]", [resource_type, name]), + "issueType": "MissingAttribute", + "keyExpectedValue": sprintf("'%s.%s' should be linked to an 'azurerm_private_endpoint'", [resource_type, name]), + "keyActualValue": sprintf("'%s.%s' is not linked to any 'azurerm_private_endpoint' in this file", [resource_type, name]), + } +} + +check_resource_id(current, target) { + current == target +} + +check_resource_id(current, target) { + current == sprintf("${%s}", [target]) +} diff --git a/assets/queries/terraform/azure/azure_production_workload_basic_consumption_sku/metadata.json b/assets/queries/terraform/azure/azure_production_workload_basic_consumption_sku/metadata.json index 4ff2bbda593..bc8edc09dec 100644 --- a/assets/queries/terraform/azure/azure_production_workload_basic_consumption_sku/metadata.json +++ b/assets/queries/terraform/azure/azure_production_workload_basic_consumption_sku/metadata.json @@ -9,5 +9,5 @@ "cloudProvider": "azure", "cwe": "CWE-1038", "descriptionID": "bd3a6fc3", - "riskScore": 2.0 + "riskScore": "2.0" } \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_production_workload_basic_consumption_sku/query.rego b/assets/queries/terraform/azure/azure_production_workload_basic_consumption_sku/query.rego index 4433742de6c..8da9f5e92c1 100644 --- a/assets/queries/terraform/azure/azure_production_workload_basic_consumption_sku/query.rego +++ b/assets/queries/terraform/azure/azure_production_workload_basic_consumption_sku/query.rego @@ -1,34 +1,40 @@ -package Cx - -# REGLA 1: Azure Service Plan usando SKU Basic (B), Free (F) o Consumption (Y1). -CxPolicy[result] { - doc := input.document[i] - plan := doc.resource.azurerm_service_plan[name] - - invalid_skus := ["B1", "B2", "B3", "F1", "FREE", "Y1"] - plan.sku_name == invalid_skus[_] - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.azurerm_service_plan.%s.sku_name", [name]), - "issueType": "IncorrectValue", - "keyExpectedValue": sprintf("'%s.sku_name' should be Standard (S), Premium (P) or Isolated (I) for production", [name]), - "keyActualValue": sprintf("'%s.sku_name' is set to '%s' (Basic/Free/Consumption)", [name, plan.sku_name]), - } -} - -# REGLA 2: Azure API Management usando SKU Basic o Consumption. -CxPolicy[result] { - doc := input.document[i] - apim := doc.resource.azurerm_api_management[name] - - regex.match("(Basic|Consumption).*", apim.sku_name) - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.azurerm_api_management.%s.sku_name", [name]), - "issueType": "IncorrectValue", - "keyExpectedValue": sprintf("'%s.sku_name' should be Standard or Premium for production features", [name]), - "keyActualValue": sprintf("'%s.sku_name' is set to '%s'", [name, apim.sku_name]), - } -} \ No newline at end of file +package Cx + +import data.generic.terraform as tf_lib + +# REGLA 1: Azure Service Plan usando SKU Basic (B), Free (F) o Consumption (Y1). +CxPolicy[result] { + doc := input.document[i] + plan := doc.resource.azurerm_service_plan[name] + + invalid_skus := ["B1", "B2", "B3", "F1", "FREE", "Y1"] + plan.sku_name == invalid_skus[_] + + result := { + "documentId": doc.id, + "resourceType": "azurerm_service_plan", + "resourceName": tf_lib.get_resource_name(plan, name), + "searchKey": sprintf("azurerm_service_plan[%s].sku_name", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": sprintf("'%s.sku_name' should be Standard (S), Premium (P) or Isolated (I) for production", [name]), + "keyActualValue": sprintf("'%s.sku_name' is set to '%s' (Basic/Free/Consumption)", [name, plan.sku_name]), + } +} + +# REGLA 2: Azure API Management usando SKU Basic o Consumption. +CxPolicy[result] { + doc := input.document[i] + apim := doc.resource.azurerm_api_management[name] + + regex.match("(Basic|Consumption).*", apim.sku_name) + + result := { + "documentId": doc.id, + "resourceType": "azurerm_api_management", + "resourceName": tf_lib.get_resource_name(apim, name), + "searchKey": sprintf("azurerm_api_management[%s].sku_name", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": sprintf("'%s.sku_name' should be Standard or Premium for production features", [name]), + "keyActualValue": sprintf("'%s.sku_name' is set to '%s'", [name, apim.sku_name]), + } +} diff --git a/assets/queries/terraform/azure/azure_recovery_services_vault_cmk_encryption_disabled/metadata.json b/assets/queries/terraform/azure/azure_recovery_services_vault_cmk_encryption_disabled/metadata.json index 588fbca70b7..5c419b027ff 100644 --- a/assets/queries/terraform/azure/azure_recovery_services_vault_cmk_encryption_disabled/metadata.json +++ b/assets/queries/terraform/azure/azure_recovery_services_vault_cmk_encryption_disabled/metadata.json @@ -9,5 +9,5 @@ "cloudProvider": "azure", "cwe": "CWE-326", "descriptionID": "9436d439", - "riskScore": 5.0 + "riskScore": "5.0" } \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_recovery_services_vault_cmk_encryption_disabled/query.rego b/assets/queries/terraform/azure/azure_recovery_services_vault_cmk_encryption_disabled/query.rego index 3926d795315..05df1852299 100644 --- a/assets/queries/terraform/azure/azure_recovery_services_vault_cmk_encryption_disabled/query.rego +++ b/assets/queries/terraform/azure/azure_recovery_services_vault_cmk_encryption_disabled/query.rego @@ -1,17 +1,21 @@ -package Cx - -# REGLA 1: El bloque de encriptación no está definido. -CxPolicy[result] { - doc := input.document[i] - vault := doc.resource.azurerm_recovery_services_vault[name] - - not vault.encryption - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.azurerm_recovery_services_vault.%s", [name]), - "issueType": "MissingAttribute", - "keyExpectedValue": sprintf("'azurerm_recovery_services_vault.%s' should have an 'encryption' block defined with a valid 'key_id'", [name]), - "keyActualValue": sprintf("'azurerm_recovery_services_vault.%s' is missing the 'encryption' block (Platform-Managed Keys by default)", [name]), - } -} \ No newline at end of file +package Cx + +import data.generic.terraform as tf_lib + +# REGLA 1: El bloque de encriptación no está definido. +CxPolicy[result] { + doc := input.document[i] + vault := doc.resource.azurerm_recovery_services_vault[name] + + not vault.encryption + + result := { + "documentId": doc.id, + "resourceType": "azurerm_recovery_services_vault", + "resourceName": tf_lib.get_resource_name(vault, name), + "searchKey": sprintf("azurerm_recovery_services_vault[%s]", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": sprintf("'azurerm_recovery_services_vault.%s' should have an 'encryption' block defined with a valid 'key_id'", [name]), + "keyActualValue": sprintf("'azurerm_recovery_services_vault.%s' is missing the 'encryption' block (Platform-Managed Keys by default)", [name]), + } +} diff --git a/assets/queries/terraform/azure/azure_recovery_services_vault_cross_region_restore_disabled/metadata.json b/assets/queries/terraform/azure/azure_recovery_services_vault_cross_region_restore_disabled/metadata.json index 5714b398bc0..a22b92d63c6 100644 --- a/assets/queries/terraform/azure/azure_recovery_services_vault_cross_region_restore_disabled/metadata.json +++ b/assets/queries/terraform/azure/azure_recovery_services_vault_cross_region_restore_disabled/metadata.json @@ -9,5 +9,5 @@ "cloudProvider": "azure", "cwe": "CWE-668", "descriptionID": "b7b0bc16", - "riskScore": 5.0 + "riskScore": "5.0" } \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_recovery_services_vault_cross_region_restore_disabled/query.rego b/assets/queries/terraform/azure/azure_recovery_services_vault_cross_region_restore_disabled/query.rego index bb16333f81d..e07bf3e96bf 100644 --- a/assets/queries/terraform/azure/azure_recovery_services_vault_cross_region_restore_disabled/query.rego +++ b/assets/queries/terraform/azure/azure_recovery_services_vault_cross_region_restore_disabled/query.rego @@ -1,33 +1,39 @@ -package Cx - -# REGLA 1: Atributo 'cross_region_restore_enabled' ausente. -CxPolicy[result] { - doc := input.document[i] - vault := doc.resource.azurerm_recovery_services_vault[name] - - object.get(vault, "cross_region_restore_enabled", "undefined") == "undefined" - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.azurerm_recovery_services_vault.%s", [name]), - "issueType": "MissingAttribute", - "keyExpectedValue": sprintf("'azurerm_recovery_services_vault.%s' should have 'cross_region_restore_enabled' set to true", [name]), - "keyActualValue": sprintf("'azurerm_recovery_services_vault.%s' is missing 'cross_region_restore_enabled'", [name]), - } -} - -# REGLA 2: Atributo 'cross_region_restore_enabled' establecido en false. -CxPolicy[result] { - doc := input.document[i] - vault := doc.resource.azurerm_recovery_services_vault[name] - - vault.cross_region_restore_enabled == false - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.azurerm_recovery_services_vault.%s.cross_region_restore_enabled", [name]), - "issueType": "IncorrectValue", - "keyExpectedValue": "'cross_region_restore_enabled' should be set to true", - "keyActualValue": "'cross_region_restore_enabled' is set to false", - } -} \ No newline at end of file +package Cx + +import data.generic.terraform as tf_lib + +# REGLA 1: Atributo 'cross_region_restore_enabled' ausente. +CxPolicy[result] { + doc := input.document[i] + vault := doc.resource.azurerm_recovery_services_vault[name] + + object.get(vault, "cross_region_restore_enabled", "undefined") == "undefined" + + result := { + "documentId": doc.id, + "resourceType": "azurerm_recovery_services_vault", + "resourceName": tf_lib.get_resource_name(vault, name), + "searchKey": sprintf("azurerm_recovery_services_vault[%s]", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": sprintf("'azurerm_recovery_services_vault.%s' should have 'cross_region_restore_enabled' set to true", [name]), + "keyActualValue": sprintf("'azurerm_recovery_services_vault.%s' is missing 'cross_region_restore_enabled'", [name]), + } +} + +# REGLA 2: Atributo 'cross_region_restore_enabled' establecido en false. +CxPolicy[result] { + doc := input.document[i] + vault := doc.resource.azurerm_recovery_services_vault[name] + + vault.cross_region_restore_enabled == false + + result := { + "documentId": doc.id, + "resourceType": "azurerm_recovery_services_vault", + "resourceName": tf_lib.get_resource_name(vault, name), + "searchKey": sprintf("azurerm_recovery_services_vault[%s].cross_region_restore_enabled", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "'cross_region_restore_enabled' should be set to true", + "keyActualValue": "'cross_region_restore_enabled' is set to false", + } +} diff --git a/assets/queries/terraform/azure/azure_recovery_services_vault_infrastructure_encryption_disabled/metadata.json b/assets/queries/terraform/azure/azure_recovery_services_vault_infrastructure_encryption_disabled/metadata.json index 44dd19a744d..b8b0f0581e1 100644 --- a/assets/queries/terraform/azure/azure_recovery_services_vault_infrastructure_encryption_disabled/metadata.json +++ b/assets/queries/terraform/azure/azure_recovery_services_vault_infrastructure_encryption_disabled/metadata.json @@ -9,5 +9,5 @@ "cloudProvider": "azure", "cwe": "CWE-312", "descriptionID": "95e32d9c", - "riskScore": 5.0 + "riskScore": "5.0" } \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_recovery_services_vault_infrastructure_encryption_disabled/query.rego b/assets/queries/terraform/azure/azure_recovery_services_vault_infrastructure_encryption_disabled/query.rego index bbdfc81beaf..198780181f1 100644 --- a/assets/queries/terraform/azure/azure_recovery_services_vault_infrastructure_encryption_disabled/query.rego +++ b/assets/queries/terraform/azure/azure_recovery_services_vault_infrastructure_encryption_disabled/query.rego @@ -1,34 +1,40 @@ -package Cx - -# REGLA 1: El bloque 'encryption' no está definido. -CxPolicy[result] { - doc := input.document[i] - vault := doc.resource.azurerm_recovery_services_vault[name] - - not vault.encryption - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.azurerm_recovery_services_vault.%s", [name]), - "issueType": "MissingAttribute", - "keyExpectedValue": sprintf("'azurerm_recovery_services_vault.%s' should have an 'encryption' block defined", [name]), - "keyActualValue": sprintf("'azurerm_recovery_services_vault.%s' is missing the 'encryption' block", [name]), - } -} - -# REGLA 2: El atributo 'infrastructure_encryption_enabled' no está en true. -CxPolicy[result] { - doc := input.document[i] - vault := doc.resource.azurerm_recovery_services_vault[name] - - vault.encryption - object.get(vault.encryption, "infrastructure_encryption_enabled", false) != true - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.azurerm_recovery_services_vault.%s.encryption.infrastructure_encryption_enabled", [name]), - "issueType": "IncorrectValue", - "keyExpectedValue": "encryption.infrastructure_encryption_enabled should be set to true", - "keyActualValue": sprintf("encryption.infrastructure_encryption_enabled is set to %v", [object.get(vault.encryption, "infrastructure_encryption_enabled", false)]), - } -} \ No newline at end of file +package Cx + +import data.generic.terraform as tf_lib + +# REGLA 1: El bloque 'encryption' no está definido. +CxPolicy[result] { + doc := input.document[i] + vault := doc.resource.azurerm_recovery_services_vault[name] + + not vault.encryption + + result := { + "documentId": doc.id, + "resourceType": "azurerm_recovery_services_vault", + "resourceName": tf_lib.get_resource_name(vault, name), + "searchKey": sprintf("azurerm_recovery_services_vault[%s]", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": sprintf("'azurerm_recovery_services_vault.%s' should have an 'encryption' block defined", [name]), + "keyActualValue": sprintf("'azurerm_recovery_services_vault.%s' is missing the 'encryption' block", [name]), + } +} + +# REGLA 2: El atributo 'infrastructure_encryption_enabled' no está en true. +CxPolicy[result] { + doc := input.document[i] + vault := doc.resource.azurerm_recovery_services_vault[name] + + vault.encryption + object.get(vault.encryption, "infrastructure_encryption_enabled", false) != true + + result := { + "documentId": doc.id, + "resourceType": "azurerm_recovery_services_vault", + "resourceName": tf_lib.get_resource_name(vault, name), + "searchKey": sprintf("azurerm_recovery_services_vault[%s].encryption.infrastructure_encryption_enabled", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "encryption.infrastructure_encryption_enabled should be set to true", + "keyActualValue": sprintf("encryption.infrastructure_encryption_enabled is set to %v", [object.get(vault.encryption, "infrastructure_encryption_enabled", false)]), + } +} diff --git a/assets/queries/terraform/azure/azure_sql_server_tde_cmk_disabled/metadata.json b/assets/queries/terraform/azure/azure_sql_server_tde_cmk_disabled/metadata.json index 8047f2f55df..7af9efd6682 100644 --- a/assets/queries/terraform/azure/azure_sql_server_tde_cmk_disabled/metadata.json +++ b/assets/queries/terraform/azure/azure_sql_server_tde_cmk_disabled/metadata.json @@ -9,5 +9,5 @@ "cloudProvider": "azure", "cwe": "CWE-326", "descriptionID": "745e82b3", - "riskScore": 5.0 + "riskScore": "5.0" } \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_sql_server_tde_cmk_disabled/query.rego b/assets/queries/terraform/azure/azure_sql_server_tde_cmk_disabled/query.rego index 22c9b17da63..224c2f7dd51 100644 --- a/assets/queries/terraform/azure/azure_sql_server_tde_cmk_disabled/query.rego +++ b/assets/queries/terraform/azure/azure_sql_server_tde_cmk_disabled/query.rego @@ -1,32 +1,36 @@ -package Cx - -has_cmk_tde(doc, server_id) { - tde := doc.resource.azurerm_mssql_server_transparent_data_encryption[_] - check_id(tde.server_id, server_id) - tde.key_vault_key_id -} - -check_id(current, target) { - current == target -} - -check_id(current, target) { - current == sprintf("${%s}", [target]) -} - -# REGLA 1: SQL Server sin configuración de TDE explícita o sin CMK. -CxPolicy[result] { - doc := input.document[i] - server := doc.resource.azurerm_mssql_server[name] - server_id := sprintf("azurerm_mssql_server.%s.id", [name]) - - not has_cmk_tde(doc, server_id) - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.azurerm_mssql_server.%s", [name]), - "issueType": "MissingAttribute", - "keyExpectedValue": sprintf("'azurerm_mssql_server.%s' should have an associated 'azurerm_mssql_server_transparent_data_encryption' resource with 'key_vault_key_id' set", [name]), - "keyActualValue": sprintf("'azurerm_mssql_server.%s' is using Service-Managed Key (default) or lacks TDE resource", [name]), - } -} \ No newline at end of file +package Cx + +import data.generic.terraform as tf_lib + +has_cmk_tde(doc, server_id) { + tde := doc.resource.azurerm_mssql_server_transparent_data_encryption[_] + check_id(tde.server_id, server_id) + tde.key_vault_key_id +} + +check_id(current, target) { + current == target +} + +check_id(current, target) { + current == sprintf("${%s}", [target]) +} + +# REGLA 1: SQL Server sin configuración de TDE explícita o sin CMK. +CxPolicy[result] { + doc := input.document[i] + server := doc.resource.azurerm_mssql_server[name] + server_id := sprintf("azurerm_mssql_server.%s.id", [name]) + + not has_cmk_tde(doc, server_id) + + result := { + "documentId": doc.id, + "resourceType": "azurerm_mssql_server", + "resourceName": tf_lib.get_resource_name(server, name), + "searchKey": sprintf("azurerm_mssql_server[%s]", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": sprintf("'azurerm_mssql_server.%s' should have an associated 'azurerm_mssql_server_transparent_data_encryption' resource with 'key_vault_key_id' set", [name]), + "keyActualValue": sprintf("'azurerm_mssql_server.%s' is using Service-Managed Key (default) or lacks TDE resource", [name]), + } +} diff --git a/assets/queries/terraform/azure/azure_storage_account_geo_redundancy_disabled/metadata.json b/assets/queries/terraform/azure/azure_storage_account_geo_redundancy_disabled/metadata.json index 7c5d7efb00d..802722e8f3d 100644 --- a/assets/queries/terraform/azure/azure_storage_account_geo_redundancy_disabled/metadata.json +++ b/assets/queries/terraform/azure/azure_storage_account_geo_redundancy_disabled/metadata.json @@ -9,5 +9,5 @@ "cloudProvider": "azure", "cwe": "CWE-668", "descriptionID": "56c748b8", - "riskScore": 5.0 + "riskScore": "5.0" } \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_account_geo_redundancy_disabled/query.rego b/assets/queries/terraform/azure/azure_storage_account_geo_redundancy_disabled/query.rego index daa11248e5c..e6ab72bdac2 100644 --- a/assets/queries/terraform/azure/azure_storage_account_geo_redundancy_disabled/query.rego +++ b/assets/queries/terraform/azure/azure_storage_account_geo_redundancy_disabled/query.rego @@ -1,21 +1,25 @@ -package Cx - -geo_redundant_types := {"GRS", "RAGRS", "GZRS", "RAGZRS"} - -# REGLA 1: El tipo de replicación no es Geo-Redundante (ej. es LRS o ZRS). -CxPolicy[result] { - doc := input.document[i] - sa := doc.resource.azurerm_storage_account[name] - - current_type := sa.account_replication_type - - not geo_redundant_types[current_type] - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.azurerm_storage_account.%s.account_replication_type", [name]), - "issueType": "IncorrectValue", - "keyExpectedValue": "'account_replication_type' should be 'GRS', 'RAGRS', 'GZRS', or 'RAGZRS'", - "keyActualValue": sprintf("'account_replication_type' is set to '%s'", [current_type]), - } -} \ No newline at end of file +package Cx + +import data.generic.terraform as tf_lib + +geo_redundant_types := {"GRS", "RAGRS", "GZRS", "RAGZRS"} + +# REGLA 1: El tipo de replicación no es Geo-Redundante (ej. es LRS o ZRS). +CxPolicy[result] { + doc := input.document[i] + sa := doc.resource.azurerm_storage_account[name] + + current_type := sa.account_replication_type + + not geo_redundant_types[current_type] + + result := { + "documentId": doc.id, + "resourceType": "azurerm_storage_account", + "resourceName": tf_lib.get_resource_name(sa, name), + "searchKey": sprintf("azurerm_storage_account[%s].account_replication_type", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "'account_replication_type' should be 'GRS', 'RAGRS', 'GZRS', or 'RAGZRS'", + "keyActualValue": sprintf("'account_replication_type' is set to '%s'", [current_type]), + } +} diff --git a/assets/queries/terraform/azure/azure_storage_account_infrastructure_encryption_disabled/metadata.json b/assets/queries/terraform/azure/azure_storage_account_infrastructure_encryption_disabled/metadata.json index 4b988f9c413..80e27e741fd 100644 --- a/assets/queries/terraform/azure/azure_storage_account_infrastructure_encryption_disabled/metadata.json +++ b/assets/queries/terraform/azure/azure_storage_account_infrastructure_encryption_disabled/metadata.json @@ -9,5 +9,5 @@ "cloudProvider": "azure", "cwe": "CWE-312", "descriptionID": "d02793e5", - "riskScore": 5.0 + "riskScore": "5.0" } \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_account_infrastructure_encryption_disabled/query.rego b/assets/queries/terraform/azure/azure_storage_account_infrastructure_encryption_disabled/query.rego index 149bbed7e9f..cc2460acdb6 100644 --- a/assets/queries/terraform/azure/azure_storage_account_infrastructure_encryption_disabled/query.rego +++ b/assets/queries/terraform/azure/azure_storage_account_infrastructure_encryption_disabled/query.rego @@ -1,33 +1,39 @@ -package Cx - -# REGLA 1: Atributo 'infrastructure_encryption_enabled' ausente. -CxPolicy[result] { - doc := input.document[i] - sa := doc.resource.azurerm_storage_account[name] - - object.get(sa, "infrastructure_encryption_enabled", "undefined") == "undefined" - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.azurerm_storage_account.%s", [name]), - "issueType": "MissingAttribute", - "keyExpectedValue": sprintf("'azurerm_storage_account.%s' should have 'infrastructure_encryption_enabled' set to true", [name]), - "keyActualValue": sprintf("'azurerm_storage_account.%s' is missing 'infrastructure_encryption_enabled'", [name]), - } -} - -# REGLA 2: Atributo 'infrastructure_encryption_enabled' establecido en false. -CxPolicy[result] { - doc := input.document[i] - sa := doc.resource.azurerm_storage_account[name] - - sa.infrastructure_encryption_enabled == false - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.azurerm_storage_account.%s.infrastructure_encryption_enabled", [name]), - "issueType": "IncorrectValue", - "keyExpectedValue": "'infrastructure_encryption_enabled' should be set to true", - "keyActualValue": "'infrastructure_encryption_enabled' is set to false", - } -} \ No newline at end of file +package Cx + +import data.generic.terraform as tf_lib + +# REGLA 1: Atributo 'infrastructure_encryption_enabled' ausente. +CxPolicy[result] { + doc := input.document[i] + sa := doc.resource.azurerm_storage_account[name] + + object.get(sa, "infrastructure_encryption_enabled", "undefined") == "undefined" + + result := { + "documentId": doc.id, + "resourceType": "azurerm_storage_account", + "resourceName": tf_lib.get_resource_name(sa, name), + "searchKey": sprintf("azurerm_storage_account[%s]", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": sprintf("'azurerm_storage_account.%s' should have 'infrastructure_encryption_enabled' set to true", [name]), + "keyActualValue": sprintf("'azurerm_storage_account.%s' is missing 'infrastructure_encryption_enabled'", [name]), + } +} + +# REGLA 2: Atributo 'infrastructure_encryption_enabled' establecido en false. +CxPolicy[result] { + doc := input.document[i] + sa := doc.resource.azurerm_storage_account[name] + + sa.infrastructure_encryption_enabled == false + + result := { + "documentId": doc.id, + "resourceType": "azurerm_storage_account", + "resourceName": tf_lib.get_resource_name(sa, name), + "searchKey": sprintf("azurerm_storage_account[%s].infrastructure_encryption_enabled", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "'infrastructure_encryption_enabled' should be set to true", + "keyActualValue": "'infrastructure_encryption_enabled' is set to false", + } +} diff --git a/assets/queries/terraform/azure/azure_storage_account_read_only_lock_missing/metadata.json b/assets/queries/terraform/azure/azure_storage_account_read_only_lock_missing/metadata.json index 99fa66216c4..47f3e578d38 100644 --- a/assets/queries/terraform/azure/azure_storage_account_read_only_lock_missing/metadata.json +++ b/assets/queries/terraform/azure/azure_storage_account_read_only_lock_missing/metadata.json @@ -9,5 +9,5 @@ "cloudProvider": "azure", "cwe": "CWE-400", "descriptionID": "3a716202", - "riskScore": 2.0 + "riskScore": "2.0" } \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_account_read_only_lock_missing/query.rego b/assets/queries/terraform/azure/azure_storage_account_read_only_lock_missing/query.rego index 560db7a509c..3b454baa130 100644 --- a/assets/queries/terraform/azure/azure_storage_account_read_only_lock_missing/query.rego +++ b/assets/queries/terraform/azure/azure_storage_account_read_only_lock_missing/query.rego @@ -1,58 +1,64 @@ -package Cx - -has_readonly_lock(doc, sa_id) { - l := doc.resource.azurerm_management_lock[_] - check_lock_scope(l.scope, sa_id) - l.lock_level == "ReadOnly" -} - -check_lock_scope(current, target) { - current == target -} - -check_lock_scope(current, target) { - current == sprintf("${%s}", [target]) -} - -# CASO 1: Storage Account totalmente desprotegido (sin bloqueos asociados) -CxPolicy[result] { - doc := input.document[i] - sa := doc.resource.azurerm_storage_account[sa_name] - sa_id := sprintf("azurerm_storage_account.%s.id", [sa_name]) - - associated_locks := [l | - l := doc.resource.azurerm_management_lock[_] - check_lock_scope(l.scope, sa_id) - ] - count(associated_locks) == 0 - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.azurerm_storage_account.%s", [sa_name]), - "issueType": "MissingAttribute", - "keyExpectedValue": sprintf("'azurerm_storage_account.%s' should have a 'ReadOnly' lock associated", [sa_name]), - "keyActualValue": sprintf("'azurerm_storage_account.%s' has no locks associated", [sa_name]), - } -} - -# CASO 2: Storage Account con bloqueo incorrecto (nivel distinto a ReadOnly) -CxPolicy[result] { - doc := input.document[i] - lock := doc.resource.azurerm_management_lock[lock_name] - - lock.lock_level != "ReadOnly" - - sa := doc.resource.azurerm_storage_account[sa_name] - sa_id := sprintf("azurerm_storage_account.%s.id", [sa_name]) - check_lock_scope(lock.scope, sa_id) - - not has_readonly_lock(doc, sa_id) - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.azurerm_management_lock.%s.lock_level", [lock_name]), - "issueType": "IncorrectValue", - "keyExpectedValue": sprintf("'azurerm_management_lock.%s.lock_level' should be 'ReadOnly'", [lock_name]), - "keyActualValue": sprintf("'azurerm_management_lock.%s.lock_level' is '%s'", [lock_name, lock.lock_level]), - } -} \ No newline at end of file +package Cx + +import data.generic.terraform as tf_lib + +has_readonly_lock(doc, sa_id) { + l := doc.resource.azurerm_management_lock[_] + check_lock_scope(l.scope, sa_id) + l.lock_level == "ReadOnly" +} + +check_lock_scope(current, target) { + current == target +} + +check_lock_scope(current, target) { + current == sprintf("${%s}", [target]) +} + +# CASO 1: Storage Account totalmente desprotegido (sin bloqueos asociados) +CxPolicy[result] { + doc := input.document[i] + sa := doc.resource.azurerm_storage_account[sa_name] + sa_id := sprintf("azurerm_storage_account.%s.id", [sa_name]) + + associated_locks := [l | + l := doc.resource.azurerm_management_lock[_] + check_lock_scope(l.scope, sa_id) + ] + count(associated_locks) == 0 + + result := { + "documentId": doc.id, + "resourceType": "azurerm_storage_account", + "resourceName": tf_lib.get_resource_name(sa, sa_name), + "searchKey": sprintf("azurerm_storage_account[%s]", [sa_name]), + "issueType": "MissingAttribute", + "keyExpectedValue": sprintf("'azurerm_storage_account.%s' should have a 'ReadOnly' lock associated", [sa_name]), + "keyActualValue": sprintf("'azurerm_storage_account.%s' has no locks associated", [sa_name]), + } +} + +# CASO 2: Storage Account con bloqueo incorrecto (nivel distinto a ReadOnly) +CxPolicy[result] { + doc := input.document[i] + lock := doc.resource.azurerm_management_lock[lock_name] + + lock.lock_level != "ReadOnly" + + sa := doc.resource.azurerm_storage_account[sa_name] + sa_id := sprintf("azurerm_storage_account.%s.id", [sa_name]) + check_lock_scope(lock.scope, sa_id) + + not has_readonly_lock(doc, sa_id) + + result := { + "documentId": doc.id, + "resourceType": "azurerm_management_lock", + "resourceName": tf_lib.get_resource_name(lock, lock_name), + "searchKey": sprintf("azurerm_management_lock[%s].lock_level", [lock_name]), + "issueType": "IncorrectValue", + "keyExpectedValue": sprintf("'azurerm_management_lock.%s.lock_level' should be 'ReadOnly'", [lock_name]), + "keyActualValue": sprintf("'azurerm_management_lock.%s.lock_level' is '%s'", [lock_name, lock.lock_level]), + } +} diff --git a/assets/queries/terraform/azure/azure_storage_account_versioning_disabled/metadata.json b/assets/queries/terraform/azure/azure_storage_account_versioning_disabled/metadata.json index 88efc35cafb..808f5d040b1 100644 --- a/assets/queries/terraform/azure/azure_storage_account_versioning_disabled/metadata.json +++ b/assets/queries/terraform/azure/azure_storage_account_versioning_disabled/metadata.json @@ -9,5 +9,5 @@ "cloudProvider": "azure", "cwe": "CWE-226", "descriptionID": "0f437572", - "riskScore": 5.0 + "riskScore": "5.0" } \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_account_versioning_disabled/query.rego b/assets/queries/terraform/azure/azure_storage_account_versioning_disabled/query.rego index 70bc0f27b04..b46220cd9d7 100644 --- a/assets/queries/terraform/azure/azure_storage_account_versioning_disabled/query.rego +++ b/assets/queries/terraform/azure/azure_storage_account_versioning_disabled/query.rego @@ -1,51 +1,59 @@ -package Cx - -# CASO 1: Falta el bloque 'blob_properties' completo. -CxPolicy[result] { - doc := input.document[i] - sa := doc.resource.azurerm_storage_account[name] - - not sa.blob_properties - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.azurerm_storage_account.%s", [name]), - "issueType": "MissingAttribute", - "keyExpectedValue": sprintf("'azurerm_storage_account.%s' should have 'blob_properties' defined", [name]), - "keyActualValue": sprintf("'azurerm_storage_account.%s' is missing 'blob_properties'", [name]), - } -} - -# CASO 2: Existe 'blob_properties' pero falta el atributo 'versioning_enabled'. -CxPolicy[result] { - doc := input.document[i] - sa := doc.resource.azurerm_storage_account[name] - - sa.blob_properties - - object.get(sa.blob_properties, "versioning_enabled", "undefined") == "undefined" - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.azurerm_storage_account.%s.blob_properties", [name]), - "issueType": "MissingAttribute", - "keyExpectedValue": "blob_properties.versioning_enabled should be defined and set to true", - "keyActualValue": "blob_properties.versioning_enabled is missing", - } -} - -# CASO 3: 'versioning_enabled' existe pero está explícitamente a false. -CxPolicy[result] { - doc := input.document[i] - sa := doc.resource.azurerm_storage_account[name] - - sa.blob_properties.versioning_enabled == false - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.azurerm_storage_account.%s.blob_properties.versioning_enabled", [name]), - "issueType": "IncorrectValue", - "keyExpectedValue": "blob_properties.versioning_enabled should be set to true", - "keyActualValue": "blob_properties.versioning_enabled is set to false", - } -} \ No newline at end of file +package Cx + +import data.generic.terraform as tf_lib + +# CASO 1: Falta el bloque 'blob_properties' completo. +CxPolicy[result] { + doc := input.document[i] + sa := doc.resource.azurerm_storage_account[name] + + not sa.blob_properties + + result := { + "documentId": doc.id, + "resourceType": "azurerm_storage_account", + "resourceName": tf_lib.get_resource_name(sa, name), + "searchKey": sprintf("azurerm_storage_account[%s]", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": sprintf("'azurerm_storage_account.%s' should have 'blob_properties' defined", [name]), + "keyActualValue": sprintf("'azurerm_storage_account.%s' is missing 'blob_properties'", [name]), + } +} + +# CASO 2: Existe 'blob_properties' pero falta el atributo 'versioning_enabled'. +CxPolicy[result] { + doc := input.document[i] + sa := doc.resource.azurerm_storage_account[name] + + sa.blob_properties + + object.get(sa.blob_properties, "versioning_enabled", "undefined") == "undefined" + + result := { + "documentId": doc.id, + "resourceType": "azurerm_storage_account", + "resourceName": tf_lib.get_resource_name(sa, name), + "searchKey": sprintf("azurerm_storage_account[%s].blob_properties", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "blob_properties.versioning_enabled should be defined and set to true", + "keyActualValue": "blob_properties.versioning_enabled is missing", + } +} + +# CASO 3: 'versioning_enabled' existe pero está explícitamente a false. +CxPolicy[result] { + doc := input.document[i] + sa := doc.resource.azurerm_storage_account[name] + + sa.blob_properties.versioning_enabled == false + + result := { + "documentId": doc.id, + "resourceType": "azurerm_storage_account", + "resourceName": tf_lib.get_resource_name(sa, name), + "searchKey": sprintf("azurerm_storage_account[%s].blob_properties.versioning_enabled", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "blob_properties.versioning_enabled should be set to true", + "keyActualValue": "blob_properties.versioning_enabled is set to false", + } +} diff --git a/assets/queries/terraform/azure/azure_storage_blob_logging_disabled/metadata.json b/assets/queries/terraform/azure/azure_storage_blob_logging_disabled/metadata.json index 6433e6ca03d..1856f0d8d87 100644 --- a/assets/queries/terraform/azure/azure_storage_blob_logging_disabled/metadata.json +++ b/assets/queries/terraform/azure/azure_storage_blob_logging_disabled/metadata.json @@ -9,5 +9,5 @@ "cloudProvider": "azure", "cwe": "CWE-778", "descriptionID": "11fa88c0", - "riskScore": 2.0 + "riskScore": "2.0" } \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_blob_logging_disabled/query.rego b/assets/queries/terraform/azure/azure_storage_blob_logging_disabled/query.rego index 1580109acde..a8f59e66791 100644 --- a/assets/queries/terraform/azure/azure_storage_blob_logging_disabled/query.rego +++ b/assets/queries/terraform/azure/azure_storage_blob_logging_disabled/query.rego @@ -1,76 +1,84 @@ -package Cx - -is_target_linked(target, sa_name) { - ref := sprintf("azurerm_storage_account.%s.id", [sa_name]) - contains(target, ref) - contains(target, "blobServices/default") -} - -is_target_linked(target, sa_name) { - ref := sprintf("${azurerm_storage_account.%s.id}", [sa_name]) - contains(target, ref) - contains(target, "blobServices/default") -} - -# CASO 1: La cuenta de almacenamiento no tiene ningún Diagnostic Setting para Blobs. -CxPolicy[result] { - doc := input.document[i] - sa := doc.resource.azurerm_storage_account[name] - - not diag_exists_for_sa(doc, name) - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.azurerm_storage_account.%s", [name]), - "issueType": "MissingAttribute", - "keyExpectedValue": sprintf("'azurerm_storage_account.%s' should have an 'azurerm_monitor_diagnostic_setting' for its blob service", [name]), - "keyActualValue": sprintf("'azurerm_storage_account.%s' does not have diagnostic logging enabled for blobs", [name]), - } -} - -diag_exists_for_sa(doc, sa_name) { - diag := doc.resource.azurerm_monitor_diagnostic_setting[_] - is_target_linked(diag.target_resource_id, sa_name) -} - -# CASO 2: El Diagnostic Setting existe pero no tiene ningún bloque 'enabled_log'. -CxPolicy[result] { - doc := input.document[i] - diag := doc.resource.azurerm_monitor_diagnostic_setting[diag_name] - - contains(diag.target_resource_id, "blobServices/default") - not diag.enabled_log - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.azurerm_monitor_diagnostic_setting.%s", [diag_name]), - "issueType": "MissingAttribute", - "keyExpectedValue": "Diagnostic Setting should have 'enabled_log' blocks defined", - "keyActualValue": "Diagnostic Setting has no 'enabled_log' blocks", - } -} - -# CASO 3: El Diagnostic Setting tiene bloques 'enabled_log' pero el conjunto está incompleto. -CxPolicy[result] { - doc := input.document[i] - diag := doc.resource.azurerm_monitor_diagnostic_setting[diag_name] - - contains(diag.target_resource_id, "blobServices/default") - diag.enabled_log - - required_categories := {"StorageRead", "StorageWrite", "StorageDelete"} - present_categories := {cat | - log := diag.enabled_log[_] - cat := log.category - } - - not count(required_categories - present_categories) == 0 - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.azurerm_monitor_diagnostic_setting.%s.enabled_log", [diag_name]), - "issueType": "IncorrectValue", - "keyExpectedValue": "All required log categories (StorageRead, StorageWrite, StorageDelete) should be present", - "keyActualValue": "One or more required log categories are missing in the 'enabled_log' configuration", - } -} \ No newline at end of file +package Cx + +import data.generic.terraform as tf_lib + +is_target_linked(target, sa_name) { + ref := sprintf("azurerm_storage_account.%s.id", [sa_name]) + contains(target, ref) + contains(target, "blobServices/default") +} + +is_target_linked(target, sa_name) { + ref := sprintf("${azurerm_storage_account.%s.id}", [sa_name]) + contains(target, ref) + contains(target, "blobServices/default") +} + +# CASO 1: La cuenta de almacenamiento no tiene ningún Diagnostic Setting para Blobs. +CxPolicy[result] { + doc := input.document[i] + sa := doc.resource.azurerm_storage_account[name] + + not diag_exists_for_sa(doc, name) + + result := { + "documentId": doc.id, + "resourceType": "azurerm_storage_account", + "resourceName": tf_lib.get_resource_name(sa, name), + "searchKey": sprintf("azurerm_storage_account[%s]", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": sprintf("'azurerm_storage_account.%s' should have an 'azurerm_monitor_diagnostic_setting' for its blob service", [name]), + "keyActualValue": sprintf("'azurerm_storage_account.%s' does not have diagnostic logging enabled for blobs", [name]), + } +} + +diag_exists_for_sa(doc, sa_name) { + diag := doc.resource.azurerm_monitor_diagnostic_setting[_] + is_target_linked(diag.target_resource_id, sa_name) +} + +# CASO 2: El Diagnostic Setting existe pero no tiene ningún bloque 'enabled_log'. +CxPolicy[result] { + doc := input.document[i] + diag := doc.resource.azurerm_monitor_diagnostic_setting[diag_name] + + contains(diag.target_resource_id, "blobServices/default") + not diag.enabled_log + + result := { + "documentId": doc.id, + "resourceType": "azurerm_monitor_diagnostic_setting", + "resourceName": tf_lib.get_resource_name(diag, diag_name), + "searchKey": sprintf("azurerm_monitor_diagnostic_setting[%s]", [diag_name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "Diagnostic Setting should have 'enabled_log' blocks defined", + "keyActualValue": "Diagnostic Setting has no 'enabled_log' blocks", + } +} + +# CASO 3: El Diagnostic Setting tiene bloques 'enabled_log' pero el conjunto está incompleto. +CxPolicy[result] { + doc := input.document[i] + diag := doc.resource.azurerm_monitor_diagnostic_setting[diag_name] + + contains(diag.target_resource_id, "blobServices/default") + diag.enabled_log + + required_categories := {"StorageRead", "StorageWrite", "StorageDelete"} + present_categories := {cat | + log := diag.enabled_log[_] + cat := log.category + } + + not count(required_categories - present_categories) == 0 + + result := { + "documentId": doc.id, + "resourceType": "azurerm_monitor_diagnostic_setting", + "resourceName": tf_lib.get_resource_name(diag, diag_name), + "searchKey": sprintf("azurerm_monitor_diagnostic_setting[%s].enabled_log", [diag_name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "All required log categories (StorageRead, StorageWrite, StorageDelete) should be present", + "keyActualValue": "One or more required log categories are missing in the 'enabled_log' configuration", + } +} diff --git a/assets/queries/terraform/azure/azure_storage_container_immutability_not_locked/metadata.json b/assets/queries/terraform/azure/azure_storage_container_immutability_not_locked/metadata.json index eaf61fafe48..8721a47bf2a 100644 --- a/assets/queries/terraform/azure/azure_storage_container_immutability_not_locked/metadata.json +++ b/assets/queries/terraform/azure/azure_storage_container_immutability_not_locked/metadata.json @@ -9,5 +9,5 @@ "cloudProvider": "azure", "cwe": "CWE-284", "descriptionID": "9b959753", - "riskScore": 5.0 + "riskScore": "5.0" } \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_container_immutability_not_locked/query.rego b/assets/queries/terraform/azure/azure_storage_container_immutability_not_locked/query.rego index 233dc06fe1c..4994a66b173 100644 --- a/assets/queries/terraform/azure/azure_storage_container_immutability_not_locked/query.rego +++ b/assets/queries/terraform/azure/azure_storage_container_immutability_not_locked/query.rego @@ -1,33 +1,39 @@ -package Cx - -# REGLA 1: El atributo 'locked' no está definido (Default es false/unlocked). -CxPolicy[result] { - doc := input.document[i] - policy := doc.resource.azurerm_storage_container_immutability_policy[name] - - object.get(policy, "locked", "undefined") == "undefined" - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.azurerm_storage_container_immutability_policy.%s", [name]), - "issueType": "MissingAttribute", - "keyExpectedValue": sprintf("'azurerm_storage_container_immutability_policy.%s' should have 'locked' set to true", [name]), - "keyActualValue": sprintf("'azurerm_storage_container_immutability_policy.%s' is missing 'locked' attribute (default is false)", [name]), - } -} - -# REGLA 2: El atributo 'locked' está explícitamente a false. -CxPolicy[result] { - doc := input.document[i] - policy := doc.resource.azurerm_storage_container_immutability_policy[name] - - policy.locked == false - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.azurerm_storage_container_immutability_policy.%s.locked", [name]), - "issueType": "IncorrectValue", - "keyExpectedValue": "'locked' should be set to true", - "keyActualValue": "'locked' is set to false", - } -} \ No newline at end of file +package Cx + +import data.generic.terraform as tf_lib + +# REGLA 1: El atributo 'locked' no está definido (Default es false/unlocked). +CxPolicy[result] { + doc := input.document[i] + policy := doc.resource.azurerm_storage_container_immutability_policy[name] + + object.get(policy, "locked", "undefined") == "undefined" + + result := { + "documentId": doc.id, + "resourceType": "azurerm_storage_container_immutability_policy", + "resourceName": tf_lib.get_resource_name(policy, name), + "searchKey": sprintf("azurerm_storage_container_immutability_policy[%s]", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": sprintf("'azurerm_storage_container_immutability_policy.%s' should have 'locked' set to true", [name]), + "keyActualValue": sprintf("'azurerm_storage_container_immutability_policy.%s' is missing 'locked' attribute (default is false)", [name]), + } +} + +# REGLA 2: El atributo 'locked' está explícitamente a false. +CxPolicy[result] { + doc := input.document[i] + policy := doc.resource.azurerm_storage_container_immutability_policy[name] + + policy.locked == false + + result := { + "documentId": doc.id, + "resourceType": "azurerm_storage_container_immutability_policy", + "resourceName": tf_lib.get_resource_name(policy, name), + "searchKey": sprintf("azurerm_storage_container_immutability_policy[%s].locked", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "'locked' should be set to true", + "keyActualValue": "'locked' is set to false", + } +} diff --git a/assets/queries/terraform/azure/azure_storage_critical_data_cmk_manual/metadata.json b/assets/queries/terraform/azure/azure_storage_critical_data_cmk_manual/metadata.json index 0c21a11ee0b..2fdd8d5d5f7 100644 --- a/assets/queries/terraform/azure/azure_storage_critical_data_cmk_manual/metadata.json +++ b/assets/queries/terraform/azure/azure_storage_critical_data_cmk_manual/metadata.json @@ -9,5 +9,5 @@ "cloudProvider": "azure", "cwe": "CWE-312", "descriptionID": "20fcb3ef", - "riskScore": 0.0 + "riskScore": "0.0" } \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_critical_data_cmk_manual/query.rego b/assets/queries/terraform/azure/azure_storage_critical_data_cmk_manual/query.rego index e888549ca7b..49fb3983e7d 100644 --- a/assets/queries/terraform/azure/azure_storage_critical_data_cmk_manual/query.rego +++ b/assets/queries/terraform/azure/azure_storage_critical_data_cmk_manual/query.rego @@ -1,34 +1,40 @@ -package Cx - -# REGLA 1: El bloque 'customer_managed_key' no existe. -CxPolicy[result] { - doc := input.document[i] - sa := doc.resource.azurerm_storage_account[name] - - not sa.customer_managed_key - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.azurerm_storage_account.%s", [name]), - "issueType": "MissingAttribute", - "keyExpectedValue": sprintf("'azurerm_storage_account.%s' should use CMK if hosting critical data (Manual Verification)", [name]), - "keyActualValue": sprintf("'azurerm_storage_account.%s' is using Platform-Managed Keys", [name]), - } -} - -# REGLA 2: El bloque existe pero el atributo 'key_vault_key_id' no está definido. -CxPolicy[result] { - doc := input.document[i] - sa := doc.resource.azurerm_storage_account[name] - - sa.customer_managed_key - object.get(sa.customer_managed_key, "key_vault_key_id", "undefined") == "undefined" - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.azurerm_storage_account.%s.customer_managed_key", [name]), - "issueType": "IncorrectValue", - "keyExpectedValue": "If 'customer_managed_key' block is defined, it should include 'key_vault_key_id' for CMK encryption", - "keyActualValue": "'key_vault_key_id' is not defined within the 'customer_managed_key' block", - } -} \ No newline at end of file +package Cx + +import data.generic.terraform as tf_lib + +# REGLA 1: El bloque 'customer_managed_key' no existe. +CxPolicy[result] { + doc := input.document[i] + sa := doc.resource.azurerm_storage_account[name] + + not sa.customer_managed_key + + result := { + "documentId": doc.id, + "resourceType": "azurerm_storage_account", + "resourceName": tf_lib.get_resource_name(sa, name), + "searchKey": sprintf("azurerm_storage_account[%s]", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": sprintf("'azurerm_storage_account.%s' should use CMK if hosting critical data (Manual Verification)", [name]), + "keyActualValue": sprintf("'azurerm_storage_account.%s' is using Platform-Managed Keys", [name]), + } +} + +# REGLA 2: El bloque existe pero el atributo 'key_vault_key_id' no está definido. +CxPolicy[result] { + doc := input.document[i] + sa := doc.resource.azurerm_storage_account[name] + + sa.customer_managed_key + object.get(sa.customer_managed_key, "key_vault_key_id", "undefined") == "undefined" + + result := { + "documentId": doc.id, + "resourceType": "azurerm_storage_account", + "resourceName": tf_lib.get_resource_name(sa, name), + "searchKey": sprintf("azurerm_storage_account[%s].customer_managed_key", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "If 'customer_managed_key' block is defined, it should include 'key_vault_key_id' for CMK encryption", + "keyActualValue": "'key_vault_key_id' is not defined within the 'customer_managed_key' block", + } +} diff --git a/assets/queries/terraform/azure/azure_storage_queue_logging_disabled/metadata.json b/assets/queries/terraform/azure/azure_storage_queue_logging_disabled/metadata.json index 24c682371b3..6a397c44d81 100644 --- a/assets/queries/terraform/azure/azure_storage_queue_logging_disabled/metadata.json +++ b/assets/queries/terraform/azure/azure_storage_queue_logging_disabled/metadata.json @@ -9,5 +9,5 @@ "cloudProvider": "azure", "cwe": "CWE-778", "descriptionID": "cddbdd74", - "riskScore": 2.0 + "riskScore": "2.0" } \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_queue_logging_disabled/query.rego b/assets/queries/terraform/azure/azure_storage_queue_logging_disabled/query.rego index 0c5fbe8a745..4301ed2999d 100644 --- a/assets/queries/terraform/azure/azure_storage_queue_logging_disabled/query.rego +++ b/assets/queries/terraform/azure/azure_storage_queue_logging_disabled/query.rego @@ -1,74 +1,84 @@ -package Cx - -is_logging_valid(logging) { - logging.read == true - logging.write == true - logging.delete == true -} - -# CASO 1: Bloque 'logging' ausente en 'queue_properties' de azurerm_storage_account. -CxPolicy[result] { - doc := input.document[i] - sa := doc.resource.azurerm_storage_account[name] - - sa.queue_properties - not sa.queue_properties.logging - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.azurerm_storage_account.%s.queue_properties", [name]), - "issueType": "MissingAttribute", - "keyExpectedValue": "queue_properties.logging should be defined with read, write, and delete enabled", - "keyActualValue": "queue_properties.logging is missing", - } -} - -# CASO 2: Configuración de 'logging' incorrecta en azurerm_storage_account. -CxPolicy[result] { - doc := input.document[i] - sa := doc.resource.azurerm_storage_account[name] - - logging := sa.queue_properties.logging - not is_logging_valid(logging) - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.azurerm_storage_account.%s.queue_properties.logging", [name]), - "issueType": "IncorrectValue", - "keyExpectedValue": "logging should have read, write, and delete set to true", - "keyActualValue": "logging has one or more required actions (read, write, delete) disabled", - } -} - -# CASO 3: Bloque 'logging' ausente en el recurso azurerm_storage_account_queue_properties. -CxPolicy[result] { - doc := input.document[i] - props := doc.resource.azurerm_storage_account_queue_properties[name] - - not props.logging - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.azurerm_storage_account_queue_properties.%s", [name]), - "issueType": "MissingAttribute", - "keyExpectedValue": "logging block should be defined in queue properties", - "keyActualValue": "logging block is missing", - } -} - -# CASO 4: Configuración de 'logging' incorrecta en azurerm_storage_account_queue_properties. -CxPolicy[result] { - doc := input.document[i] - props := doc.resource.azurerm_storage_account_queue_properties[name] - - logging := props.logging - not is_logging_valid(logging) - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.azurerm_storage_account_queue_properties.%s.logging", [name]), - "issueType": "IncorrectValue", - "keyExpectedValue": "logging should have read, write, and delete set to true", - "keyActualValue": "logging is missing one or more required actions", - } -} \ No newline at end of file +package Cx + +import data.generic.terraform as tf_lib + +is_logging_valid(logging) { + logging.read == true + logging.write == true + logging.delete == true +} + +# CASO 1: Bloque 'logging' ausente en 'queue_properties' de azurerm_storage_account. +CxPolicy[result] { + doc := input.document[i] + sa := doc.resource.azurerm_storage_account[name] + + sa.queue_properties + not sa.queue_properties.logging + + result := { + "documentId": doc.id, + "resourceType": "azurerm_storage_account", + "resourceName": tf_lib.get_resource_name(sa, name), + "searchKey": sprintf("azurerm_storage_account[%s].queue_properties", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "queue_properties.logging should be defined with read, write, and delete enabled", + "keyActualValue": "queue_properties.logging is missing", + } +} + +# CASO 2: Configuración de 'logging' incorrecta en azurerm_storage_account. +CxPolicy[result] { + doc := input.document[i] + sa := doc.resource.azurerm_storage_account[name] + + logging := sa.queue_properties.logging + not is_logging_valid(logging) + + result := { + "documentId": doc.id, + "resourceType": "azurerm_storage_account", + "resourceName": tf_lib.get_resource_name(sa, name), + "searchKey": sprintf("azurerm_storage_account[%s].queue_properties.logging", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "logging should have read, write, and delete set to true", + "keyActualValue": "logging has one or more required actions (read, write, delete) disabled", + } +} + +# CASO 3: Bloque 'logging' ausente en el recurso azurerm_storage_account_queue_properties. +CxPolicy[result] { + doc := input.document[i] + props := doc.resource.azurerm_storage_account_queue_properties[name] + + not props.logging + + result := { + "documentId": doc.id, + "resourceType": "azurerm_storage_account_queue_properties", + "resourceName": tf_lib.get_resource_name(props, name), + "searchKey": sprintf("azurerm_storage_account_queue_properties[%s]", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "logging block should be defined in queue properties", + "keyActualValue": "logging block is missing", + } +} + +# CASO 4: Configuración de 'logging' incorrecta en azurerm_storage_account_queue_properties. +CxPolicy[result] { + doc := input.document[i] + props := doc.resource.azurerm_storage_account_queue_properties[name] + + logging := props.logging + not is_logging_valid(logging) + + result := { + "documentId": doc.id, + "resourceType": "azurerm_storage_account_queue_properties", + "resourceName": tf_lib.get_resource_name(props, name), + "searchKey": sprintf("azurerm_storage_account_queue_properties[%s].logging", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "logging should have read, write, and delete set to true", + "keyActualValue": "logging is missing one or more required actions", + } +} diff --git a/assets/queries/terraform/azure/azure_storage_table_logging_disabled/metadata.json b/assets/queries/terraform/azure/azure_storage_table_logging_disabled/metadata.json index 7ab0ddb59aa..0a4ecadcbdc 100644 --- a/assets/queries/terraform/azure/azure_storage_table_logging_disabled/metadata.json +++ b/assets/queries/terraform/azure/azure_storage_table_logging_disabled/metadata.json @@ -9,5 +9,5 @@ "cloudProvider": "azure", "cwe": "CWE-778", "descriptionID": "0d29e3cf", - "riskScore": 2.0 + "riskScore": "2.0" } \ No newline at end of file diff --git a/assets/queries/terraform/azure/azure_storage_table_logging_disabled/query.rego b/assets/queries/terraform/azure/azure_storage_table_logging_disabled/query.rego index 0def7c4add9..f42e178f480 100644 --- a/assets/queries/terraform/azure/azure_storage_table_logging_disabled/query.rego +++ b/assets/queries/terraform/azure/azure_storage_table_logging_disabled/query.rego @@ -1,76 +1,84 @@ -package Cx - -is_target_linked(target, sa_name) { - ref := sprintf("azurerm_storage_account.%s.id", [sa_name]) - contains(target, ref) - contains(target, "tableServices/default") -} - -is_target_linked(target, sa_name) { - ref := sprintf("${azurerm_storage_account.%s.id}", [sa_name]) - contains(target, ref) - contains(target, "tableServices/default") -} - -# CASO 1: La cuenta de almacenamiento no tiene ningún Diagnostic Setting para Tablas. -CxPolicy[result] { - doc := input.document[i] - sa := doc.resource.azurerm_storage_account[name] - - not diag_exists_for_sa(doc, name) - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.azurerm_storage_account.%s", [name]), - "issueType": "MissingAttribute", - "keyExpectedValue": sprintf("'azurerm_storage_account.%s' should have an 'azurerm_monitor_diagnostic_setting' for its table service", [name]), - "keyActualValue": sprintf("'azurerm_storage_account.%s' does not have diagnostic logging enabled for tables", [name]), - } -} - -diag_exists_for_sa(doc, sa_name) { - diag := doc.resource.azurerm_monitor_diagnostic_setting[_] - is_target_linked(diag.target_resource_id, sa_name) -} - -# CASO 2: El Diagnostic Setting existe pero no tiene ningún bloque 'enabled_log'. -CxPolicy[result] { - doc := input.document[i] - diag := doc.resource.azurerm_monitor_diagnostic_setting[diag_name] - - contains(diag.target_resource_id, "tableServices/default") - not diag.enabled_log - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.azurerm_monitor_diagnostic_setting.%s", [diag_name]), - "issueType": "MissingAttribute", - "keyExpectedValue": "Diagnostic Setting should have 'enabled_log' blocks defined", - "keyActualValue": "Diagnostic Setting has no 'enabled_log' blocks", - } -} - -# CASO 3: El Diagnostic Setting tiene bloques 'enabled_log' pero el conjunto está incompleto. -CxPolicy[result] { - doc := input.document[i] - diag := doc.resource.azurerm_monitor_diagnostic_setting[diag_name] - - contains(diag.target_resource_id, "tableServices/default") - diag.enabled_log - - required_categories := {"StorageRead", "StorageWrite", "StorageDelete"} - present_categories := {cat | - log := diag.enabled_log[_] - cat := log.category - } - - not count(required_categories - present_categories) == 0 - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.azurerm_monitor_diagnostic_setting.%s.enabled_log", [diag_name]), - "issueType": "IncorrectValue", - "keyExpectedValue": "All required log categories (StorageRead, StorageWrite, StorageDelete) should be present", - "keyActualValue": "One or more required log categories are missing in the 'enabled_log' configuration for Table service", - } -} \ No newline at end of file +package Cx + +import data.generic.terraform as tf_lib + +is_target_linked(target, sa_name) { + ref := sprintf("azurerm_storage_account.%s.id", [sa_name]) + contains(target, ref) + contains(target, "tableServices/default") +} + +is_target_linked(target, sa_name) { + ref := sprintf("${azurerm_storage_account.%s.id}", [sa_name]) + contains(target, ref) + contains(target, "tableServices/default") +} + +# CASO 1: La cuenta de almacenamiento no tiene ningún Diagnostic Setting para Tablas. +CxPolicy[result] { + doc := input.document[i] + sa := doc.resource.azurerm_storage_account[name] + + not diag_exists_for_sa(doc, name) + + result := { + "documentId": doc.id, + "resourceType": "azurerm_storage_account", + "resourceName": tf_lib.get_resource_name(sa, name), + "searchKey": sprintf("azurerm_storage_account[%s]", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": sprintf("'azurerm_storage_account.%s' should have an 'azurerm_monitor_diagnostic_setting' for its table service", [name]), + "keyActualValue": sprintf("'azurerm_storage_account.%s' does not have diagnostic logging enabled for tables", [name]), + } +} + +diag_exists_for_sa(doc, sa_name) { + diag := doc.resource.azurerm_monitor_diagnostic_setting[_] + is_target_linked(diag.target_resource_id, sa_name) +} + +# CASO 2: El Diagnostic Setting existe pero no tiene ningún bloque 'enabled_log'. +CxPolicy[result] { + doc := input.document[i] + diag := doc.resource.azurerm_monitor_diagnostic_setting[diag_name] + + contains(diag.target_resource_id, "tableServices/default") + not diag.enabled_log + + result := { + "documentId": doc.id, + "resourceType": "azurerm_monitor_diagnostic_setting", + "resourceName": tf_lib.get_resource_name(diag, diag_name), + "searchKey": sprintf("azurerm_monitor_diagnostic_setting[%s]", [diag_name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "Diagnostic Setting should have 'enabled_log' blocks defined", + "keyActualValue": "Diagnostic Setting has no 'enabled_log' blocks", + } +} + +# CASO 3: El Diagnostic Setting tiene bloques 'enabled_log' pero el conjunto está incompleto. +CxPolicy[result] { + doc := input.document[i] + diag := doc.resource.azurerm_monitor_diagnostic_setting[diag_name] + + contains(diag.target_resource_id, "tableServices/default") + diag.enabled_log + + required_categories := {"StorageRead", "StorageWrite", "StorageDelete"} + present_categories := {cat | + log := diag.enabled_log[_] + cat := log.category + } + + not count(required_categories - present_categories) == 0 + + result := { + "documentId": doc.id, + "resourceType": "azurerm_monitor_diagnostic_setting", + "resourceName": tf_lib.get_resource_name(diag, diag_name), + "searchKey": sprintf("azurerm_monitor_diagnostic_setting[%s].enabled_log", [diag_name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "All required log categories (StorageRead, StorageWrite, StorageDelete) should be present", + "keyActualValue": "One or more required log categories are missing in the 'enabled_log' configuration for Table service", + } +} diff --git a/assets/queries/terraform/gcp/gcp_access_approval_disabled/metadata.json b/assets/queries/terraform/gcp/gcp_access_approval_disabled/metadata.json index 64cbcb48dff..ed307e448eb 100644 --- a/assets/queries/terraform/gcp/gcp_access_approval_disabled/metadata.json +++ b/assets/queries/terraform/gcp/gcp_access_approval_disabled/metadata.json @@ -9,5 +9,5 @@ "descriptionID": "d7df9989", "cloudProvider": "gcp", "cwe": "CWE-284", - "riskScore": 5.0 + "riskScore": "5.0" } \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_access_approval_disabled/query.rego b/assets/queries/terraform/gcp/gcp_access_approval_disabled/query.rego index f6a7dfce92f..afc70b2e7cf 100644 --- a/assets/queries/terraform/gcp/gcp_access_approval_disabled/query.rego +++ b/assets/queries/terraform/gcp/gcp_access_approval_disabled/query.rego @@ -1,38 +1,44 @@ -package Cx - -# CASO 1: Proyecto sin configuración de Access Approval. -CxPolicy[result] { - doc := input.document[i] - project := doc.resource.google_project[name] - - settings := [s | - s := doc.resource.google_access_approval_project_settings[_] - contains(s.project_id, name) - ] - - count(settings) == 0 - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.google_project.%s", [name]), - "issueType": "MissingAttribute", - "keyExpectedValue": sprintf("'google_project.%s' should have 'google_access_approval_project_settings' associated", [name]), - "keyActualValue": sprintf("'google_project.%s' does not have Access Approval configured", [name]), - } -} - -# CASO 2: Access Approval configurado pero sin servicios inscritos. -CxPolicy[result] { - doc := input.document[i] - settings := doc.resource.google_access_approval_project_settings[name] - - not settings.enrolled_services - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.google_access_approval_project_settings.%s", [name]), - "issueType": "MissingAttribute", - "keyExpectedValue": "Must have at least one 'enrolled_services' block defined", - "keyActualValue": "'enrolled_services' is missing", - } -} \ No newline at end of file +package Cx + +import data.generic.terraform as tf_lib + +# CASO 1: Proyecto sin configuración de Access Approval. +CxPolicy[result] { + doc := input.document[i] + project := doc.resource.google_project[name] + + settings := [s | + s := doc.resource.google_access_approval_project_settings[_] + contains(s.project_id, name) + ] + + count(settings) == 0 + + result := { + "documentId": doc.id, + "resourceType": "google_project", + "resourceName": tf_lib.get_resource_name(project, name), + "searchKey": sprintf("google_project[%s]", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": sprintf("'google_project.%s' should have 'google_access_approval_project_settings' associated", [name]), + "keyActualValue": sprintf("'google_project.%s' does not have Access Approval configured", [name]), + } +} + +# CASO 2: Access Approval configurado pero sin servicios inscritos. +CxPolicy[result] { + doc := input.document[i] + settings := doc.resource.google_access_approval_project_settings[name] + + not settings.enrolled_services + + result := { + "documentId": doc.id, + "resourceType": "google_access_approval_project_settings", + "resourceName": tf_lib.get_resource_name(settings, name), + "searchKey": sprintf("google_access_approval_project_settings[%s]", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "Must have at least one 'enrolled_services' block defined", + "keyActualValue": "'enrolled_services' is missing", + } +} diff --git a/assets/queries/terraform/gcp/gcp_api_key_api_targets_missing/metadata.json b/assets/queries/terraform/gcp/gcp_api_key_api_targets_missing/metadata.json index e4a80048519..f7d03e3db5d 100644 --- a/assets/queries/terraform/gcp/gcp_api_key_api_targets_missing/metadata.json +++ b/assets/queries/terraform/gcp/gcp_api_key_api_targets_missing/metadata.json @@ -9,5 +9,5 @@ "descriptionID": "8097afd6", "cloudProvider": "gcp", "cwe": "CWE-284", - "riskScore": 5.0 + "riskScore": "5.0" } \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_api_key_api_targets_missing/query.rego b/assets/queries/terraform/gcp/gcp_api_key_api_targets_missing/query.rego index ad76b2d3db5..14b7ad5a894 100644 --- a/assets/queries/terraform/gcp/gcp_api_key_api_targets_missing/query.rego +++ b/assets/queries/terraform/gcp/gcp_api_key_api_targets_missing/query.rego @@ -1,34 +1,40 @@ -package Cx - -# CASO 1: No existe el bloque 'restrictions'. -CxPolicy[result] { - doc := input.document[i] - key := doc.resource.google_apikeys_key[name] - - not key.restrictions - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.google_apikeys_key.%s", [name]), - "issueType": "MissingAttribute", - "keyExpectedValue": sprintf("'google_apikeys_key.%s' should have 'restrictions.api_targets' defined", [name]), - "keyActualValue": sprintf("'google_apikeys_key.%s' is missing the 'restrictions' block", [name]), - } -} - -# CASO 2: Existe 'restrictions', pero falta 'api_targets'. -CxPolicy[result] { - doc := input.document[i] - key := doc.resource.google_apikeys_key[name] - - key.restrictions - not key.restrictions.api_targets - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.google_apikeys_key.%s.restrictions", [name]), - "issueType": "MissingAttribute", - "keyExpectedValue": "restrictions.api_targets should be defined", - "keyActualValue": "restrictions.api_targets is missing", - } -} \ No newline at end of file +package Cx + +import data.generic.terraform as tf_lib + +# CASO 1: No existe el bloque 'restrictions'. +CxPolicy[result] { + doc := input.document[i] + key := doc.resource.google_apikeys_key[name] + + not key.restrictions + + result := { + "documentId": doc.id, + "resourceType": "google_apikeys_key", + "resourceName": tf_lib.get_resource_name(key, name), + "searchKey": sprintf("google_apikeys_key[%s]", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": sprintf("'google_apikeys_key.%s' should have 'restrictions.api_targets' defined", [name]), + "keyActualValue": sprintf("'google_apikeys_key.%s' is missing the 'restrictions' block", [name]), + } +} + +# CASO 2: Existe 'restrictions', pero falta 'api_targets'. +CxPolicy[result] { + doc := input.document[i] + key := doc.resource.google_apikeys_key[name] + + key.restrictions + not key.restrictions.api_targets + + result := { + "documentId": doc.id, + "resourceType": "google_apikeys_key", + "resourceName": tf_lib.get_resource_name(key, name), + "searchKey": sprintf("google_apikeys_key[%s].restrictions", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "restrictions.api_targets should be defined", + "keyActualValue": "restrictions.api_targets is missing", + } +} diff --git a/assets/queries/terraform/gcp/gcp_api_key_restrictions_manual/metadata.json b/assets/queries/terraform/gcp/gcp_api_key_restrictions_manual/metadata.json index a91205ab304..0e1d85897c0 100644 --- a/assets/queries/terraform/gcp/gcp_api_key_restrictions_manual/metadata.json +++ b/assets/queries/terraform/gcp/gcp_api_key_restrictions_manual/metadata.json @@ -9,5 +9,5 @@ "descriptionID": "7b3ba800", "cloudProvider": "gcp", "cwe": "CWE-284", - "riskScore": 0.0 + "riskScore": "0.0" } \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_api_key_restrictions_manual/query.rego b/assets/queries/terraform/gcp/gcp_api_key_restrictions_manual/query.rego index c3edd6d961b..775874fadec 100644 --- a/assets/queries/terraform/gcp/gcp_api_key_restrictions_manual/query.rego +++ b/assets/queries/terraform/gcp/gcp_api_key_restrictions_manual/query.rego @@ -1,33 +1,39 @@ -package Cx - -# CASO 1: No hay restricciones definidas. -CxPolicy[result] { - doc := input.document[i] - key := doc.resource.google_apikeys_key[name] - - not key.restrictions - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.google_apikeys_key.%s", [name]), - "issueType": "MissingAttribute", - "keyExpectedValue": sprintf("'google_apikeys_key.%s' should have a 'restrictions' block defined", [name]), - "keyActualValue": sprintf("'google_apikeys_key.%s' is missing the 'restrictions' block", [name]), - } -} - -# CASO 2: Hay restricciones definidas. -CxPolicy[result] { - doc := input.document[i] - key := doc.resource.google_apikeys_key[name] - - key.restrictions - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.google_apikeys_key.%s.restrictions", [name]), - "issueType": "IncorrectValue", - "keyExpectedValue": "Restrictions should be verified against allowed IPs/Referrers", - "keyActualValue": "Restrictions are present. Manual verification required.", - } -} \ No newline at end of file +package Cx + +import data.generic.terraform as tf_lib + +# CASO 1: No hay restricciones definidas. +CxPolicy[result] { + doc := input.document[i] + key := doc.resource.google_apikeys_key[name] + + not key.restrictions + + result := { + "documentId": doc.id, + "resourceType": "google_apikeys_key", + "resourceName": tf_lib.get_resource_name(key, name), + "searchKey": sprintf("google_apikeys_key[%s]", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": sprintf("'google_apikeys_key.%s' should have a 'restrictions' block defined", [name]), + "keyActualValue": sprintf("'google_apikeys_key.%s' is missing the 'restrictions' block", [name]), + } +} + +# CASO 2: Hay restricciones definidas. +CxPolicy[result] { + doc := input.document[i] + key := doc.resource.google_apikeys_key[name] + + key.restrictions + + result := { + "documentId": doc.id, + "resourceType": "google_apikeys_key", + "resourceName": tf_lib.get_resource_name(key, name), + "searchKey": sprintf("google_apikeys_key[%s].restrictions", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "Restrictions should be verified against allowed IPs/Referrers", + "keyActualValue": "Restrictions are present. Manual verification required.", + } +} diff --git a/assets/queries/terraform/gcp/gcp_app_engine_https_enforcement_manual/metadata.json b/assets/queries/terraform/gcp/gcp_app_engine_https_enforcement_manual/metadata.json index 38b32c311e5..71665ae005d 100644 --- a/assets/queries/terraform/gcp/gcp_app_engine_https_enforcement_manual/metadata.json +++ b/assets/queries/terraform/gcp/gcp_app_engine_https_enforcement_manual/metadata.json @@ -9,5 +9,5 @@ "descriptionID": "52b7bc15", "cloudProvider": "gcp", "cwe": "CWE-319", - "riskScore": 0.0 + "riskScore": "0.0" } \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_app_engine_https_enforcement_manual/query.rego b/assets/queries/terraform/gcp/gcp_app_engine_https_enforcement_manual/query.rego index 13dd18563ce..99bb9112a85 100644 --- a/assets/queries/terraform/gcp/gcp_app_engine_https_enforcement_manual/query.rego +++ b/assets/queries/terraform/gcp/gcp_app_engine_https_enforcement_manual/query.rego @@ -1,42 +1,48 @@ -package Cx - -ensure_array(x) = x { is_array(x) } -ensure_array(x) = [x] { not is_array(x) } - -# CASO 1: Configuración Insegura en los handlers definidos. -CxPolicy[result] { - doc := input.document[i] - app := doc.resource.google_app_engine_standard_app_version[name] - - app.handlers - - handlers_list := ensure_array(app.handlers) - handler := handlers_list[_] - - sec_level := object.get(handler, "security_level", "UNSPECIFIED") - sec_level != "SECURE_ALWAYS" - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.google_app_engine_standard_app_version.%s.handlers", [name]), - "issueType": "IncorrectValue", - "keyExpectedValue": "'handlers.security_level' should be set to 'SECURE_ALWAYS'", - "keyActualValue": sprintf("'handlers.security_level' is set to '%s'", [sec_level]), - } -} - -# CASO 2: Configuración Ausente en Terraform (Requiere revisión de app.yaml). -CxPolicy[result] { - doc := input.document[i] - app := doc.resource.google_app_engine_standard_app_version[name] - - not app.handlers - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.google_app_engine_standard_app_version.%s", [name]), - "issueType": "MissingAttribute", - "keyExpectedValue": "If handlers are not defined in Terraform, verify 'app.yaml' contains 'secure: always'", - "keyActualValue": "Terraform does not define handlers. Manual verification of 'app.yaml' required.", - } -} \ No newline at end of file +package Cx + +import data.generic.terraform as tf_lib + +ensure_array(x) = x { is_array(x) } +ensure_array(x) = [x] { not is_array(x) } + +# CASO 1: Configuración Insegura en los handlers definidos. +CxPolicy[result] { + doc := input.document[i] + app := doc.resource.google_app_engine_standard_app_version[name] + + app.handlers + + handlers_list := ensure_array(app.handlers) + handler := handlers_list[_] + + sec_level := object.get(handler, "security_level", "UNSPECIFIED") + sec_level != "SECURE_ALWAYS" + + result := { + "documentId": doc.id, + "resourceType": "google_app_engine_standard_app_version", + "resourceName": tf_lib.get_resource_name(app, name), + "searchKey": sprintf("google_app_engine_standard_app_version[%s].handlers", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "'handlers.security_level' should be set to 'SECURE_ALWAYS'", + "keyActualValue": sprintf("'handlers.security_level' is set to '%s'", [sec_level]), + } +} + +# CASO 2: Configuración Ausente en Terraform (Requiere revisión de app.yaml). +CxPolicy[result] { + doc := input.document[i] + app := doc.resource.google_app_engine_standard_app_version[name] + + not app.handlers + + result := { + "documentId": doc.id, + "resourceType": "google_app_engine_standard_app_version", + "resourceName": tf_lib.get_resource_name(app, name), + "searchKey": sprintf("google_app_engine_standard_app_version[%s]", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "If handlers are not defined in Terraform, verify 'app.yaml' contains 'secure: always'", + "keyActualValue": "Terraform does not define handlers. Manual verification of 'app.yaml' required.", + } +} diff --git a/assets/queries/terraform/gcp/gcp_compute_logging_service_disabled/metadata.json b/assets/queries/terraform/gcp/gcp_compute_logging_service_disabled/metadata.json index c2038150816..e61fd761d2f 100644 --- a/assets/queries/terraform/gcp/gcp_compute_logging_service_disabled/metadata.json +++ b/assets/queries/terraform/gcp/gcp_compute_logging_service_disabled/metadata.json @@ -9,5 +9,5 @@ "descriptionID": "ccefc589", "cloudProvider": "gcp", "cwe": "CWE-778", - "riskScore": 5.0 + "riskScore": "5.0" } \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_compute_logging_service_disabled/query.rego b/assets/queries/terraform/gcp/gcp_compute_logging_service_disabled/query.rego index c0f0b0534a6..9ffa856729b 100644 --- a/assets/queries/terraform/gcp/gcp_compute_logging_service_disabled/query.rego +++ b/assets/queries/terraform/gcp/gcp_compute_logging_service_disabled/query.rego @@ -1,51 +1,59 @@ -package Cx - -# REGLA 1: El bloque 'metadata' no existe. -CxPolicy[result] { - doc := input.document[i] - instance := doc.resource.google_compute_instance[name] - - not instance.metadata - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.google_compute_instance.%s", [name]), - "issueType": "MissingAttribute", - "keyExpectedValue": "'metadata' block should be defined and contain 'google-logging-enabled'", - "keyActualValue": "'metadata' block is missing", - } -} - -# REGLA 2: El bloque 'metadata' existe pero le falta la clave 'google-logging-enabled'. -CxPolicy[result] { - doc := input.document[i] - instance := doc.resource.google_compute_instance[name] - - instance.metadata - not instance.metadata["google-logging-enabled"] - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.google_compute_instance.%s.metadata", [name]), - "issueType": "MissingAttribute", - "keyExpectedValue": "'google-logging-enabled' should be defined within metadata", - "keyActualValue": "'google-logging-enabled' is missing in metadata", - } -} - -# REGLA 3: La clave existe pero su valor es 'false'. -CxPolicy[result] { - doc := input.document[i] - instance := doc.resource.google_compute_instance[name] - - val := instance.metadata["google-logging-enabled"] - val == "false" - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.google_compute_instance.%s.metadata.google-logging-enabled", [name]), - "issueType": "IncorrectValue", - "keyExpectedValue": "'google-logging-enabled' should be set to 'true'", - "keyActualValue": "'google-logging-enabled' is set to 'false'", - } -} \ No newline at end of file +package Cx + +import data.generic.terraform as tf_lib + +# REGLA 1: El bloque 'metadata' no existe. +CxPolicy[result] { + doc := input.document[i] + instance := doc.resource.google_compute_instance[name] + + not instance.metadata + + result := { + "documentId": doc.id, + "resourceType": "google_compute_instance", + "resourceName": tf_lib.get_resource_name(instance, name), + "searchKey": sprintf("google_compute_instance[%s]", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "'metadata' block should be defined and contain 'google-logging-enabled'", + "keyActualValue": "'metadata' block is missing", + } +} + +# REGLA 2: El bloque 'metadata' existe pero le falta la clave 'google-logging-enabled'. +CxPolicy[result] { + doc := input.document[i] + instance := doc.resource.google_compute_instance[name] + + instance.metadata + not instance.metadata["google-logging-enabled"] + + result := { + "documentId": doc.id, + "resourceType": "google_compute_instance", + "resourceName": tf_lib.get_resource_name(instance, name), + "searchKey": sprintf("google_compute_instance[%s].metadata", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "'google-logging-enabled' should be defined within metadata", + "keyActualValue": "'google-logging-enabled' is missing in metadata", + } +} + +# REGLA 3: La clave existe pero su valor es 'false'. +CxPolicy[result] { + doc := input.document[i] + instance := doc.resource.google_compute_instance[name] + + val := instance.metadata["google-logging-enabled"] + val == "false" + + result := { + "documentId": doc.id, + "resourceType": "google_compute_instance", + "resourceName": tf_lib.get_resource_name(instance, name), + "searchKey": sprintf("google_compute_instance[%s].metadata.google-logging-enabled", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "'google-logging-enabled' should be set to 'true'", + "keyActualValue": "'google-logging-enabled' is set to 'false'", + } +} diff --git a/assets/queries/terraform/gcp/gcp_gke_default_service_account_used/metadata.json b/assets/queries/terraform/gcp/gcp_gke_default_service_account_used/metadata.json index 6fd64f265d2..1e34cb0e1c0 100644 --- a/assets/queries/terraform/gcp/gcp_gke_default_service_account_used/metadata.json +++ b/assets/queries/terraform/gcp/gcp_gke_default_service_account_used/metadata.json @@ -9,5 +9,5 @@ "descriptionID": "8ef3f681", "cloudProvider": "gcp", "cwe": "CWE-276", - "riskScore": 9.0 + "riskScore": "9.0" } \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_default_service_account_used/query.rego b/assets/queries/terraform/gcp/gcp_gke_default_service_account_used/query.rego index ecb14c5bfcc..b9ceb1e587f 100644 --- a/assets/queries/terraform/gcp/gcp_gke_default_service_account_used/query.rego +++ b/assets/queries/terraform/gcp/gcp_gke_default_service_account_used/query.rego @@ -1,33 +1,39 @@ -package Cx - -# REGLA 1: Service Account ausente en google_container_cluster. -CxPolicy[result] { - doc := input.document[i] - resource := doc.resource.google_container_cluster[name] - - not resource.node_config.service_account - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.google_container_cluster.%s.node_config", [name]), - "issueType": "MissingAttribute", - "keyExpectedValue": "'service_account' should be explicitly defined in node_config", - "keyActualValue": "'service_account' is missing, defaulting to the Compute Engine default service account", - } -} - -# REGLA 2: Service Account ausente en google_container_node_pool. -CxPolicy[result] { - doc := input.document[i] - resource := doc.resource.google_container_node_pool[name] - - not resource.node_config.service_account - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.google_container_node_pool.%s.node_config", [name]), - "issueType": "MissingAttribute", - "keyExpectedValue": "'service_account' should be explicitly defined in node_config", - "keyActualValue": "'service_account' is missing, defaulting to the Compute Engine default service account", - } -} \ No newline at end of file +package Cx + +import data.generic.terraform as tf_lib + +# REGLA 1: Service Account ausente en google_container_cluster. +CxPolicy[result] { + doc := input.document[i] + resource := doc.resource.google_container_cluster[name] + + not resource.node_config.service_account + + result := { + "documentId": doc.id, + "resourceType": "google_container_cluster", + "resourceName": tf_lib.get_resource_name(resource, name), + "searchKey": sprintf("google_container_cluster[%s].node_config", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "'service_account' should be explicitly defined in node_config", + "keyActualValue": "'service_account' is missing, defaulting to the Compute Engine default service account", + } +} + +# REGLA 2: Service Account ausente en google_container_node_pool. +CxPolicy[result] { + doc := input.document[i] + resource := doc.resource.google_container_node_pool[name] + + not resource.node_config.service_account + + result := { + "documentId": doc.id, + "resourceType": "google_container_node_pool", + "resourceName": tf_lib.get_resource_name(resource, name), + "searchKey": sprintf("google_container_node_pool[%s].node_config", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "'service_account' should be explicitly defined in node_config", + "keyActualValue": "'service_account' is missing, defaulting to the Compute Engine default service account", + } +} diff --git a/assets/queries/terraform/gcp/gcp_gke_image_vulnerability_scanning_disabled/metadata.json b/assets/queries/terraform/gcp/gcp_gke_image_vulnerability_scanning_disabled/metadata.json index 129e38d16fe..98d0c94186c 100644 --- a/assets/queries/terraform/gcp/gcp_gke_image_vulnerability_scanning_disabled/metadata.json +++ b/assets/queries/terraform/gcp/gcp_gke_image_vulnerability_scanning_disabled/metadata.json @@ -9,5 +9,5 @@ "descriptionID": "30b5b4bb", "cloudProvider": "gcp", "cwe": "CWE-1395", - "riskScore": 9.0 + "riskScore": "9.0" } \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_image_vulnerability_scanning_disabled/query.rego b/assets/queries/terraform/gcp/gcp_gke_image_vulnerability_scanning_disabled/query.rego index d3736459d35..c5017869da3 100644 --- a/assets/queries/terraform/gcp/gcp_gke_image_vulnerability_scanning_disabled/query.rego +++ b/assets/queries/terraform/gcp/gcp_gke_image_vulnerability_scanning_disabled/query.rego @@ -1,51 +1,59 @@ -package Cx - -# REGLA 1: Bloque 'security_posture_config' ausente. -CxPolicy[result] { - doc := input.document[i] - cluster := doc.resource.google_container_cluster[name] - - not cluster.security_posture_config - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.google_container_cluster.%s", [name]), - "issueType": "MissingAttribute", - "keyExpectedValue": "'security_posture_config' block should be defined", - "keyActualValue": "'security_posture_config' block is missing", - } -} - -# REGLA 2: Atributo 'vulnerability_mode' ausente dentro del bloque. -CxPolicy[result] { - doc := input.document[i] - cluster := doc.resource.google_container_cluster[name] - - cluster.security_posture_config - not cluster.security_posture_config.vulnerability_mode - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.google_container_cluster.%s.security_posture_config", [name]), - "issueType": "MissingAttribute", - "keyExpectedValue": "'vulnerability_mode' should be defined within security_posture_config", - "keyActualValue": "'vulnerability_mode' is missing", - } -} - -# REGLA 3: Atributo 'vulnerability_mode' configurado como 'VULNERABILITY_DISABLED'. -CxPolicy[result] { - doc := input.document[i] - cluster := doc.resource.google_container_cluster[name] - - mode := cluster.security_posture_config.vulnerability_mode - mode == "VULNERABILITY_DISABLED" - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.google_container_cluster.%s.security_posture_config.vulnerability_mode", [name]), - "issueType": "IncorrectValue", - "keyExpectedValue": "'vulnerability_mode' should be 'VULNERABILITY_BASIC' or 'VULNERABILITY_ENTERPRISE'", - "keyActualValue": "'vulnerability_mode' is set to 'VULNERABILITY_DISABLED'", - } -} \ No newline at end of file +package Cx + +import data.generic.terraform as tf_lib + +# REGLA 1: Bloque 'security_posture_config' ausente. +CxPolicy[result] { + doc := input.document[i] + cluster := doc.resource.google_container_cluster[name] + + not cluster.security_posture_config + + result := { + "documentId": doc.id, + "resourceType": "google_container_cluster", + "resourceName": tf_lib.get_resource_name(cluster, name), + "searchKey": sprintf("google_container_cluster[%s]", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "'security_posture_config' block should be defined", + "keyActualValue": "'security_posture_config' block is missing", + } +} + +# REGLA 2: Atributo 'vulnerability_mode' ausente dentro del bloque. +CxPolicy[result] { + doc := input.document[i] + cluster := doc.resource.google_container_cluster[name] + + cluster.security_posture_config + not cluster.security_posture_config.vulnerability_mode + + result := { + "documentId": doc.id, + "resourceType": "google_container_cluster", + "resourceName": tf_lib.get_resource_name(cluster, name), + "searchKey": sprintf("google_container_cluster[%s].security_posture_config", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "'vulnerability_mode' should be defined within security_posture_config", + "keyActualValue": "'vulnerability_mode' is missing", + } +} + +# REGLA 3: Atributo 'vulnerability_mode' configurado como 'VULNERABILITY_DISABLED'. +CxPolicy[result] { + doc := input.document[i] + cluster := doc.resource.google_container_cluster[name] + + mode := cluster.security_posture_config.vulnerability_mode + mode == "VULNERABILITY_DISABLED" + + result := { + "documentId": doc.id, + "resourceType": "google_container_cluster", + "resourceName": tf_lib.get_resource_name(cluster, name), + "searchKey": sprintf("google_container_cluster[%s].security_posture_config.vulnerability_mode", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "'vulnerability_mode' should be 'VULNERABILITY_BASIC' or 'VULNERABILITY_ENTERPRISE'", + "keyActualValue": "'vulnerability_mode' is set to 'VULNERABILITY_DISABLED'", + } +} diff --git a/assets/queries/terraform/gcp/gcp_gke_manual_iam_check/metadata.json b/assets/queries/terraform/gcp/gcp_gke_manual_iam_check/metadata.json index 5ace58c4a29..b554166f1d8 100644 --- a/assets/queries/terraform/gcp/gcp_gke_manual_iam_check/metadata.json +++ b/assets/queries/terraform/gcp/gcp_gke_manual_iam_check/metadata.json @@ -9,5 +9,5 @@ "descriptionID": "b29ff836", "cloudProvider": "gcp", "cwe": "CWE-276", - "riskScore": 0.0 + "riskScore": "0.0" } \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_manual_iam_check/query.rego b/assets/queries/terraform/gcp/gcp_gke_manual_iam_check/query.rego index a3c5faa3751..7b9f30995d0 100644 --- a/assets/queries/terraform/gcp/gcp_gke_manual_iam_check/query.rego +++ b/assets/queries/terraform/gcp/gcp_gke_manual_iam_check/query.rego @@ -1,33 +1,39 @@ -package Cx - -# REGLA 1: Detección de SA personalizada en google_container_cluster. -CxPolicy[result] { - doc := input.document[i] - resource := doc.resource.google_container_cluster[name] - - sa := resource.node_config.service_account - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.google_container_cluster.%s.node_config.service_account", [name]), - "issueType": "IncorrectValue", - "keyExpectedValue": "Custom Service Account IAM roles should be verified as Read-Only", - "keyActualValue": sprintf("Custom Service Account '%s' found. Manual IAM verification required.", [sa]), - } -} - -# REGLA 2: Detección de SA personalizada en google_container_node_pool. -CxPolicy[result] { - doc := input.document[i] - resource := doc.resource.google_container_node_pool[name] - - sa := resource.node_config.service_account - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.google_container_node_pool.%s.node_config.service_account", [name]), - "issueType": "IncorrectValue", - "keyExpectedValue": "Custom Service Account IAM roles should be verified as Read-Only", - "keyActualValue": sprintf("Custom Service Account '%s' found. Manual IAM verification required.", [sa]), - } -} \ No newline at end of file +package Cx + +import data.generic.terraform as tf_lib + +# REGLA 1: Detección de SA personalizada en google_container_cluster. +CxPolicy[result] { + doc := input.document[i] + resource := doc.resource.google_container_cluster[name] + + sa := resource.node_config.service_account + + result := { + "documentId": doc.id, + "resourceType": "google_container_cluster", + "resourceName": tf_lib.get_resource_name(resource, name), + "searchKey": sprintf("google_container_cluster[%s].node_config.service_account", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "Custom Service Account IAM roles should be verified as Read-Only", + "keyActualValue": sprintf("Custom Service Account '%s' found. Manual IAM verification required.", [sa]), + } +} + +# REGLA 2: Detección de SA personalizada en google_container_node_pool. +CxPolicy[result] { + doc := input.document[i] + resource := doc.resource.google_container_node_pool[name] + + sa := resource.node_config.service_account + + result := { + "documentId": doc.id, + "resourceType": "google_container_node_pool", + "resourceName": tf_lib.get_resource_name(resource, name), + "searchKey": sprintf("google_container_node_pool[%s].node_config.service_account", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "Custom Service Account IAM roles should be verified as Read-Only", + "keyActualValue": sprintf("Custom Service Account '%s' found. Manual IAM verification required.", [sa]), + } +} diff --git a/assets/queries/terraform/gcp/gcp_gke_metadata_server_disabled/metadata.json b/assets/queries/terraform/gcp/gcp_gke_metadata_server_disabled/metadata.json index e15967fcbb7..8d3b7f7228a 100644 --- a/assets/queries/terraform/gcp/gcp_gke_metadata_server_disabled/metadata.json +++ b/assets/queries/terraform/gcp/gcp_gke_metadata_server_disabled/metadata.json @@ -9,5 +9,5 @@ "descriptionID": "e858428b", "cloudProvider": "gcp", "cwe": "CWE-284", - "riskScore": 9.0 + "riskScore": "9.0" } \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_metadata_server_disabled/query.rego b/assets/queries/terraform/gcp/gcp_gke_metadata_server_disabled/query.rego index d853b869a5c..039d2b33472 100644 --- a/assets/queries/terraform/gcp/gcp_gke_metadata_server_disabled/query.rego +++ b/assets/queries/terraform/gcp/gcp_gke_metadata_server_disabled/query.rego @@ -1,65 +1,75 @@ -package Cx - -# REGLA 1: 'workload_metadata_config' ausente en google_container_cluster -CxPolicy[result] { - doc := input.document[i] - resource := doc.resource.google_container_cluster[name] - - not resource.node_config.workload_metadata_config - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.google_container_cluster.%s.node_config", [name]), - "issueType": "MissingAttribute", - "keyExpectedValue": "'workload_metadata_config' should be defined with mode 'GKE_METADATA'", - "keyActualValue": "'workload_metadata_config' is missing", - } -} - -# REGLA 2: 'workload_metadata_config' ausente en google_container_node_pool -CxPolicy[result] { - doc := input.document[i] - resource := doc.resource.google_container_node_pool[name] - - not resource.node_config.workload_metadata_config - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.google_container_node_pool.%s.node_config", [name]), - "issueType": "MissingAttribute", - "keyExpectedValue": "'workload_metadata_config' should be defined with mode 'GKE_METADATA'", - "keyActualValue": "'workload_metadata_config' is missing", - } -} - -# REGLA 3: Modo incorrecto en google_container_cluster -CxPolicy[result] { - doc := input.document[i] - resource := doc.resource.google_container_cluster[name] - - resource.node_config.workload_metadata_config.mode != "GKE_METADATA" - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.google_container_cluster.%s.node_config.workload_metadata_config.mode", [name]), - "issueType": "IncorrectValue", - "keyExpectedValue": "'mode' should be set to 'GKE_METADATA'", - "keyActualValue": sprintf("'mode' is set to '%s'", [resource.node_config.workload_metadata_config.mode]), - } -} - -# REGLA 4: Modo incorrecto en google_container_node_pool -CxPolicy[result] { - doc := input.document[i] - resource := doc.resource.google_container_node_pool[name] - - resource.node_config.workload_metadata_config.mode != "GKE_METADATA" - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.google_container_node_pool.%s.node_config.workload_metadata_config.mode", [name]), - "issueType": "IncorrectValue", - "keyExpectedValue": "'mode' should be set to 'GKE_METADATA'", - "keyActualValue": sprintf("'mode' is set to '%s'", [resource.node_config.workload_metadata_config.mode]), - } -} \ No newline at end of file +package Cx + +import data.generic.terraform as tf_lib + +# REGLA 1: 'workload_metadata_config' ausente en google_container_cluster +CxPolicy[result] { + doc := input.document[i] + resource := doc.resource.google_container_cluster[name] + + not resource.node_config.workload_metadata_config + + result := { + "documentId": doc.id, + "resourceType": "google_container_cluster", + "resourceName": tf_lib.get_resource_name(resource, name), + "searchKey": sprintf("google_container_cluster[%s].node_config", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "'workload_metadata_config' should be defined with mode 'GKE_METADATA'", + "keyActualValue": "'workload_metadata_config' is missing", + } +} + +# REGLA 2: 'workload_metadata_config' ausente en google_container_node_pool +CxPolicy[result] { + doc := input.document[i] + resource := doc.resource.google_container_node_pool[name] + + not resource.node_config.workload_metadata_config + + result := { + "documentId": doc.id, + "resourceType": "google_container_node_pool", + "resourceName": tf_lib.get_resource_name(resource, name), + "searchKey": sprintf("google_container_node_pool[%s].node_config", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "'workload_metadata_config' should be defined with mode 'GKE_METADATA'", + "keyActualValue": "'workload_metadata_config' is missing", + } +} + +# REGLA 3: Modo incorrecto en google_container_cluster +CxPolicy[result] { + doc := input.document[i] + resource := doc.resource.google_container_cluster[name] + + resource.node_config.workload_metadata_config.mode != "GKE_METADATA" + + result := { + "documentId": doc.id, + "resourceType": "google_container_cluster", + "resourceName": tf_lib.get_resource_name(resource, name), + "searchKey": sprintf("google_container_cluster[%s].node_config.workload_metadata_config.mode", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "'mode' should be set to 'GKE_METADATA'", + "keyActualValue": sprintf("'mode' is set to '%s'", [resource.node_config.workload_metadata_config.mode]), + } +} + +# REGLA 4: Modo incorrecto en google_container_node_pool +CxPolicy[result] { + doc := input.document[i] + resource := doc.resource.google_container_node_pool[name] + + resource.node_config.workload_metadata_config.mode != "GKE_METADATA" + + result := { + "documentId": doc.id, + "resourceType": "google_container_node_pool", + "resourceName": tf_lib.get_resource_name(resource, name), + "searchKey": sprintf("google_container_node_pool[%s].node_config.workload_metadata_config.mode", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "'mode' should be set to 'GKE_METADATA'", + "keyActualValue": sprintf("'mode' is set to '%s'", [resource.node_config.workload_metadata_config.mode]), + } +} diff --git a/assets/queries/terraform/gcp/gcp_gke_sandbox_disabled/metadata.json b/assets/queries/terraform/gcp/gcp_gke_sandbox_disabled/metadata.json index 327a2de5107..775e4ae2bf4 100644 --- a/assets/queries/terraform/gcp/gcp_gke_sandbox_disabled/metadata.json +++ b/assets/queries/terraform/gcp/gcp_gke_sandbox_disabled/metadata.json @@ -9,5 +9,5 @@ "descriptionID": "6c8a93a9", "cloudProvider": "gcp", "cwe": "CWE-1038", - "riskScore": 3.0 + "riskScore": "3.0" } \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_sandbox_disabled/query.rego b/assets/queries/terraform/gcp/gcp_gke_sandbox_disabled/query.rego index 7a81e5cd947..dce3885c859 100644 --- a/assets/queries/terraform/gcp/gcp_gke_sandbox_disabled/query.rego +++ b/assets/queries/terraform/gcp/gcp_gke_sandbox_disabled/query.rego @@ -1,65 +1,75 @@ -package Cx - -# REGLA 1: Bloque 'sandbox_config' ausente en google_container_cluster. -CxPolicy[result] { - doc := input.document[i] - resource := doc.resource.google_container_cluster[name] - - not resource.node_config.sandbox_config - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.google_container_cluster.%s.node_config", [name]), - "issueType": "MissingAttribute", - "keyExpectedValue": "'sandbox_config' should be defined with sandbox_type 'gvisor'", - "keyActualValue": "'sandbox_config' is missing", - } -} - -# REGLA 2: Bloque 'sandbox_config' ausente en google_container_node_pool. -CxPolicy[result] { - doc := input.document[i] - resource := doc.resource.google_container_node_pool[name] - - not resource.node_config.sandbox_config - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.google_container_node_pool.%s.node_config", [name]), - "issueType": "MissingAttribute", - "keyExpectedValue": "'sandbox_config' should be defined with sandbox_type 'gvisor'", - "keyActualValue": "'sandbox_config' is missing", - } -} - -# REGLA 3: sandbox_type incorrecto en google_container_cluster. -CxPolicy[result] { - doc := input.document[i] - resource := doc.resource.google_container_cluster[name] - - resource.node_config.sandbox_config.sandbox_type != "gvisor" - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.google_container_cluster.%s.node_config.sandbox_config.sandbox_type", [name]), - "issueType": "IncorrectValue", - "keyExpectedValue": "'sandbox_type' should be 'gvisor'", - "keyActualValue": sprintf("'sandbox_type' is set to '%s'", [resource.node_config.sandbox_config.sandbox_type]), - } -} - -# REGLA 4: sandbox_type incorrecto en google_container_node_pool. -CxPolicy[result] { - doc := input.document[i] - resource := doc.resource.google_container_node_pool[name] - - resource.node_config.sandbox_config.sandbox_type != "gvisor" - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.google_container_node_pool.%s.node_config.sandbox_config.sandbox_type", [name]), - "issueType": "IncorrectValue", - "keyExpectedValue": "'sandbox_type' should be 'gvisor'", - "keyActualValue": sprintf("'sandbox_type' is set to '%s'", [resource.node_config.sandbox_config.sandbox_type]), - } -} \ No newline at end of file +package Cx + +import data.generic.terraform as tf_lib + +# REGLA 1: Bloque 'sandbox_config' ausente en google_container_cluster. +CxPolicy[result] { + doc := input.document[i] + resource := doc.resource.google_container_cluster[name] + + not resource.node_config.sandbox_config + + result := { + "documentId": doc.id, + "resourceType": "google_container_cluster", + "resourceName": tf_lib.get_resource_name(resource, name), + "searchKey": sprintf("google_container_cluster[%s].node_config", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "'sandbox_config' should be defined with sandbox_type 'gvisor'", + "keyActualValue": "'sandbox_config' is missing", + } +} + +# REGLA 2: Bloque 'sandbox_config' ausente en google_container_node_pool. +CxPolicy[result] { + doc := input.document[i] + resource := doc.resource.google_container_node_pool[name] + + not resource.node_config.sandbox_config + + result := { + "documentId": doc.id, + "resourceType": "google_container_node_pool", + "resourceName": tf_lib.get_resource_name(resource, name), + "searchKey": sprintf("google_container_node_pool[%s].node_config", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "'sandbox_config' should be defined with sandbox_type 'gvisor'", + "keyActualValue": "'sandbox_config' is missing", + } +} + +# REGLA 3: sandbox_type incorrecto en google_container_cluster. +CxPolicy[result] { + doc := input.document[i] + resource := doc.resource.google_container_cluster[name] + + resource.node_config.sandbox_config.sandbox_type != "gvisor" + + result := { + "documentId": doc.id, + "resourceType": "google_container_cluster", + "resourceName": tf_lib.get_resource_name(resource, name), + "searchKey": sprintf("google_container_cluster[%s].node_config.sandbox_config.sandbox_type", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "'sandbox_type' should be 'gvisor'", + "keyActualValue": sprintf("'sandbox_type' is set to '%s'", [resource.node_config.sandbox_config.sandbox_type]), + } +} + +# REGLA 4: sandbox_type incorrecto en google_container_node_pool. +CxPolicy[result] { + doc := input.document[i] + resource := doc.resource.google_container_node_pool[name] + + resource.node_config.sandbox_config.sandbox_type != "gvisor" + + result := { + "documentId": doc.id, + "resourceType": "google_container_node_pool", + "resourceName": tf_lib.get_resource_name(resource, name), + "searchKey": sprintf("google_container_node_pool[%s].node_config.sandbox_config.sandbox_type", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "'sandbox_type' should be 'gvisor'", + "keyActualValue": sprintf("'sandbox_type' is set to '%s'", [resource.node_config.sandbox_config.sandbox_type]), + } +} diff --git a/assets/queries/terraform/gcp/gcp_gke_secrets_encryption_cmek_disabled/metadata.json b/assets/queries/terraform/gcp/gcp_gke_secrets_encryption_cmek_disabled/metadata.json index e577ed16253..b0353ca7a29 100644 --- a/assets/queries/terraform/gcp/gcp_gke_secrets_encryption_cmek_disabled/metadata.json +++ b/assets/queries/terraform/gcp/gcp_gke_secrets_encryption_cmek_disabled/metadata.json @@ -9,5 +9,5 @@ "descriptionID": "83d1c89f", "cloudProvider": "gcp", "cwe": "CWE-312", - "riskScore": 9.0 + "riskScore": "9.0" } \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_secrets_encryption_cmek_disabled/query.rego b/assets/queries/terraform/gcp/gcp_gke_secrets_encryption_cmek_disabled/query.rego index 65cce79b0e4..0b7e70b2178 100644 --- a/assets/queries/terraform/gcp/gcp_gke_secrets_encryption_cmek_disabled/query.rego +++ b/assets/queries/terraform/gcp/gcp_gke_secrets_encryption_cmek_disabled/query.rego @@ -1,52 +1,60 @@ -package Cx - -# REGLA 1: Bloque 'database_encryption' ausente. -CxPolicy[result] { - doc := input.document[i] - cluster := doc.resource.google_container_cluster[name] - - not cluster.database_encryption - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.google_container_cluster.%s", [name]), - "issueType": "MissingAttribute", - "keyExpectedValue": "'database_encryption' block should be defined", - "keyActualValue": "'database_encryption' block is missing", - } -} - -# REGLA 2: Estado de cifrado incorrecto (DECRYPTED). -CxPolicy[result] { - doc := input.document[i] - cluster := doc.resource.google_container_cluster[name] - - cluster.database_encryption.state != "ENCRYPTED" - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.google_container_cluster.%s.database_encryption.state", [name]), - "issueType": "IncorrectValue", - "keyExpectedValue": "'state' should be set to 'ENCRYPTED'", - "keyActualValue": sprintf("'state' is set to '%s'", [cluster.database_encryption.state]), - } -} - -# REGLA 3: Estado ENCRYPTED pero falta el nombre de la clave (key_name). -CxPolicy[result] { - doc := input.document[i] - cluster := doc.resource.google_container_cluster[name] - - cluster.database_encryption.state == "ENCRYPTED" - - key_name := object.get(cluster.database_encryption, "key_name", "") - key_name == "" - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.google_container_cluster.%s.database_encryption.key_name", [name]), - "issueType": "MissingAttribute", - "keyExpectedValue": "'key_name' should be defined with a valid KMS key ID", - "keyActualValue": "'key_name' is missing or empty", - } -} \ No newline at end of file +package Cx + +import data.generic.terraform as tf_lib + +# REGLA 1: Bloque 'database_encryption' ausente. +CxPolicy[result] { + doc := input.document[i] + cluster := doc.resource.google_container_cluster[name] + + not cluster.database_encryption + + result := { + "documentId": doc.id, + "resourceType": "google_container_cluster", + "resourceName": tf_lib.get_resource_name(cluster, name), + "searchKey": sprintf("google_container_cluster[%s]", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "'database_encryption' block should be defined", + "keyActualValue": "'database_encryption' block is missing", + } +} + +# REGLA 2: Estado de cifrado incorrecto (DECRYPTED). +CxPolicy[result] { + doc := input.document[i] + cluster := doc.resource.google_container_cluster[name] + + cluster.database_encryption.state != "ENCRYPTED" + + result := { + "documentId": doc.id, + "resourceType": "google_container_cluster", + "resourceName": tf_lib.get_resource_name(cluster, name), + "searchKey": sprintf("google_container_cluster[%s].database_encryption.state", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "'state' should be set to 'ENCRYPTED'", + "keyActualValue": sprintf("'state' is set to '%s'", [cluster.database_encryption.state]), + } +} + +# REGLA 3: Estado ENCRYPTED pero falta el nombre de la clave (key_name). +CxPolicy[result] { + doc := input.document[i] + cluster := doc.resource.google_container_cluster[name] + + cluster.database_encryption.state == "ENCRYPTED" + + key_name := object.get(cluster.database_encryption, "key_name", "") + key_name == "" + + result := { + "documentId": doc.id, + "resourceType": "google_container_cluster", + "resourceName": tf_lib.get_resource_name(cluster, name), + "searchKey": sprintf("google_container_cluster[%s].database_encryption.key_name", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "'key_name' should be defined with a valid KMS key ID", + "keyActualValue": "'key_name' is missing or empty", + } +} diff --git a/assets/queries/terraform/gcp/gcp_gke_security_posture_manual/metadata.json b/assets/queries/terraform/gcp/gcp_gke_security_posture_manual/metadata.json index a119b6d3e04..b5d56dace4c 100644 --- a/assets/queries/terraform/gcp/gcp_gke_security_posture_manual/metadata.json +++ b/assets/queries/terraform/gcp/gcp_gke_security_posture_manual/metadata.json @@ -9,5 +9,5 @@ "descriptionID": "0d688e9b", "cloudProvider": "gcp", "cwe": "CWE-1038", - "riskScore": 0.0 + "riskScore": "0.0" } \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_security_posture_manual/query.rego b/assets/queries/terraform/gcp/gcp_gke_security_posture_manual/query.rego index cfeb14b6cb9..a7bade30f23 100644 --- a/assets/queries/terraform/gcp/gcp_gke_security_posture_manual/query.rego +++ b/assets/queries/terraform/gcp/gcp_gke_security_posture_manual/query.rego @@ -1,33 +1,39 @@ -package Cx - -# REGLA 1: Bloque 'security_posture_config' ausente. -CxPolicy[result] { - doc := input.document[i] - cluster := doc.resource.google_container_cluster[name] - - not cluster.security_posture_config - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.google_container_cluster.%s", [name]), - "issueType": "MissingAttribute", - "keyExpectedValue": "'security_posture_config' block should be defined with mode 'BASIC' or 'ENTERPRISE'", - "keyActualValue": "'security_posture_config' block is missing", - } -} - -# REGLA 2: Bloque presente, pero 'mode' es 'DISABLED'. -CxPolicy[result] { - doc := input.document[i] - cluster := doc.resource.google_container_cluster[name] - - cluster.security_posture_config.mode == "DISABLED" - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.google_container_cluster.%s.security_posture_config.mode", [name]), - "issueType": "IncorrectValue", - "keyExpectedValue": "'mode' should be set to 'BASIC' or 'ENTERPRISE'", - "keyActualValue": "'mode' is set to 'DISABLED'", - } -} \ No newline at end of file +package Cx + +import data.generic.terraform as tf_lib + +# REGLA 1: Bloque 'security_posture_config' ausente. +CxPolicy[result] { + doc := input.document[i] + cluster := doc.resource.google_container_cluster[name] + + not cluster.security_posture_config + + result := { + "documentId": doc.id, + "resourceType": "google_container_cluster", + "resourceName": tf_lib.get_resource_name(cluster, name), + "searchKey": sprintf("google_container_cluster[%s]", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "'security_posture_config' block should be defined with mode 'BASIC' or 'ENTERPRISE'", + "keyActualValue": "'security_posture_config' block is missing", + } +} + +# REGLA 2: Bloque presente, pero 'mode' es 'DISABLED'. +CxPolicy[result] { + doc := input.document[i] + cluster := doc.resource.google_container_cluster[name] + + cluster.security_posture_config.mode == "DISABLED" + + result := { + "documentId": doc.id, + "resourceType": "google_container_cluster", + "resourceName": tf_lib.get_resource_name(cluster, name), + "searchKey": sprintf("google_container_cluster[%s].security_posture_config.mode", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "'mode' should be set to 'BASIC' or 'ENTERPRISE'", + "keyActualValue": "'mode' is set to 'DISABLED'", + } +} diff --git a/assets/queries/terraform/gcp/gcp_gke_workload_identity_manual/metadata.json b/assets/queries/terraform/gcp/gcp_gke_workload_identity_manual/metadata.json index fb66fe944b8..a936faa9f6f 100644 --- a/assets/queries/terraform/gcp/gcp_gke_workload_identity_manual/metadata.json +++ b/assets/queries/terraform/gcp/gcp_gke_workload_identity_manual/metadata.json @@ -9,5 +9,5 @@ "descriptionID": "edea054b", "cloudProvider": "gcp", "cwe": "CWE-284", - "riskScore": 0.0 + "riskScore": "0.0" } \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_gke_workload_identity_manual/query.rego b/assets/queries/terraform/gcp/gcp_gke_workload_identity_manual/query.rego index d10a078e579..5e1499fe702 100644 --- a/assets/queries/terraform/gcp/gcp_gke_workload_identity_manual/query.rego +++ b/assets/queries/terraform/gcp/gcp_gke_workload_identity_manual/query.rego @@ -1,33 +1,39 @@ -package Cx - -# REGLA 1: Workload Identity no habilitado (Missing Attribute). -CxPolicy[result] { - doc := input.document[i] - cluster := doc.resource.google_container_cluster[name] - - not cluster.workload_identity_config - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.google_container_cluster.%s", [name]), - "issueType": "MissingAttribute", - "keyExpectedValue": "'workload_identity_config' should be enabled to support dedicated Service Accounts", - "keyActualValue": "'workload_identity_config' is missing", - } -} - -# REGLA 2: Workload Identity habilitado (Verificación Manual de Bindings). -CxPolicy[result] { - doc := input.document[i] - cluster := doc.resource.google_container_cluster[name] - - cluster.workload_identity_config - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.google_container_cluster.%s.workload_identity_config", [name]), - "issueType": "IncorrectValue", - "keyExpectedValue": "Verify that workloads use dedicated Service Accounts (no shared SAs)", - "keyActualValue": "Workload Identity is enabled. Manual verification of bindings required.", - } -} \ No newline at end of file +package Cx + +import data.generic.terraform as tf_lib + +# REGLA 1: Workload Identity no habilitado (Missing Attribute). +CxPolicy[result] { + doc := input.document[i] + cluster := doc.resource.google_container_cluster[name] + + not cluster.workload_identity_config + + result := { + "documentId": doc.id, + "resourceType": "google_container_cluster", + "resourceName": tf_lib.get_resource_name(cluster, name), + "searchKey": sprintf("google_container_cluster[%s]", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "'workload_identity_config' should be enabled to support dedicated Service Accounts", + "keyActualValue": "'workload_identity_config' is missing", + } +} + +# REGLA 2: Workload Identity habilitado (Verificación Manual de Bindings). +CxPolicy[result] { + doc := input.document[i] + cluster := doc.resource.google_container_cluster[name] + + cluster.workload_identity_config + + result := { + "documentId": doc.id, + "resourceType": "google_container_cluster", + "resourceName": tf_lib.get_resource_name(cluster, name), + "searchKey": sprintf("google_container_cluster[%s].workload_identity_config", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "Verify that workloads use dedicated Service Accounts (no shared SAs)", + "keyActualValue": "Workload Identity is enabled. Manual verification of bindings required.", + } +} diff --git a/assets/queries/terraform/gcp/gcp_http_load_balancer_logging_disabled/metadata.json b/assets/queries/terraform/gcp/gcp_http_load_balancer_logging_disabled/metadata.json index 3e26bf5e632..3b02d6fc606 100644 --- a/assets/queries/terraform/gcp/gcp_http_load_balancer_logging_disabled/metadata.json +++ b/assets/queries/terraform/gcp/gcp_http_load_balancer_logging_disabled/metadata.json @@ -9,5 +9,5 @@ "descriptionID": "0ef3f3f1", "cloudProvider": "gcp", "cwe": "CWE-778", - "riskScore": 5.0 + "riskScore": "5.0" } \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_http_load_balancer_logging_disabled/query.rego b/assets/queries/terraform/gcp/gcp_http_load_balancer_logging_disabled/query.rego index e15697ad864..4477185b7c6 100644 --- a/assets/queries/terraform/gcp/gcp_http_load_balancer_logging_disabled/query.rego +++ b/assets/queries/terraform/gcp/gcp_http_load_balancer_logging_disabled/query.rego @@ -1,33 +1,39 @@ -package Cx - -# REGLA 1: Bloque 'log_config' ausente. -CxPolicy[result] { - doc := input.document[i] - bs := doc.resource.google_compute_backend_service[name] - - not bs.log_config - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.google_compute_backend_service.%s", [name]), - "issueType": "MissingAttribute", - "keyExpectedValue": "log_config should be defined with enable set to true", - "keyActualValue": "log_config is missing", - } -} - -# REGLA 2: Bloque 'log_config' existe pero 'enable' es false. -CxPolicy[result] { - doc := input.document[i] - bs := doc.resource.google_compute_backend_service[name] - - bs.log_config.enable == false - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.google_compute_backend_service.%s.log_config.enable", [name]), - "issueType": "IncorrectValue", - "keyExpectedValue": "log_config.enable should be set to true", - "keyActualValue": "log_config.enable is set to false", - } -} \ No newline at end of file +package Cx + +import data.generic.terraform as tf_lib + +# REGLA 1: Bloque 'log_config' ausente. +CxPolicy[result] { + doc := input.document[i] + bs := doc.resource.google_compute_backend_service[name] + + not bs.log_config + + result := { + "documentId": doc.id, + "resourceType": "google_compute_backend_service", + "resourceName": tf_lib.get_resource_name(bs, name), + "searchKey": sprintf("google_compute_backend_service[%s]", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "log_config should be defined with enable set to true", + "keyActualValue": "log_config is missing", + } +} + +# REGLA 2: Bloque 'log_config' existe pero 'enable' es false. +CxPolicy[result] { + doc := input.document[i] + bs := doc.resource.google_compute_backend_service[name] + + bs.log_config.enable == false + + result := { + "documentId": doc.id, + "resourceType": "google_compute_backend_service", + "resourceName": tf_lib.get_resource_name(bs, name), + "searchKey": sprintf("google_compute_backend_service[%s].log_config.enable", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "log_config.enable should be set to true", + "keyActualValue": "log_config.enable is set to false", + } +} diff --git a/assets/queries/terraform/gcp/gcp_iap_backend_service_disabled/metadata.json b/assets/queries/terraform/gcp/gcp_iap_backend_service_disabled/metadata.json index a9432c792f8..8a52a6eb6ed 100644 --- a/assets/queries/terraform/gcp/gcp_iap_backend_service_disabled/metadata.json +++ b/assets/queries/terraform/gcp/gcp_iap_backend_service_disabled/metadata.json @@ -9,5 +9,5 @@ "descriptionID": "3f1222a9", "cloudProvider": "gcp", "cwe": "CWE-284", - "riskScore": 5.0 + "riskScore": "5.0" } \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_iap_backend_service_disabled/query.rego b/assets/queries/terraform/gcp/gcp_iap_backend_service_disabled/query.rego index 7ba18522e71..b4b25a906fe 100644 --- a/assets/queries/terraform/gcp/gcp_iap_backend_service_disabled/query.rego +++ b/assets/queries/terraform/gcp/gcp_iap_backend_service_disabled/query.rego @@ -1,51 +1,59 @@ -package Cx - -# REGLA 1: El bloque 'iap' está ausente en google_compute_backend_service. -CxPolicy[result] { - doc := input.document[i] - bs := doc.resource.google_compute_backend_service[name] - - not bs.iap - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.google_compute_backend_service.%s", [name]), - "issueType": "MissingAttribute", - "keyExpectedValue": sprintf("'google_compute_backend_service.%s' should have an 'iap' block defined", [name]), - "keyActualValue": sprintf("'google_compute_backend_service.%s' is missing the 'iap' block", [name]), - } -} - -# REGLA 2: El bloque 'iap' existe pero falta 'oauth2_client_id'. -CxPolicy[result] { - doc := input.document[i] - bs := doc.resource.google_compute_backend_service[name] - - bs.iap - not bs.iap.oauth2_client_id - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.google_compute_backend_service.%s.iap", [name]), - "issueType": "MissingAttribute", - "keyExpectedValue": "'oauth2_client_id' should be defined within the 'iap' block", - "keyActualValue": "'oauth2_client_id' is missing", - } -} - -# REGLA 3: El bloque 'iap' existe pero falta 'oauth2_client_secret'. -CxPolicy[result] { - doc := input.document[i] - bs := doc.resource.google_compute_backend_service[name] - - bs.iap - not bs.iap.oauth2_client_secret - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.google_compute_backend_service.%s.iap", [name]), - "issueType": "MissingAttribute", - "keyExpectedValue": "'oauth2_client_secret' should be defined within the 'iap' block", - "keyActualValue": "'oauth2_client_secret' is missing", - } -} \ No newline at end of file +package Cx + +import data.generic.terraform as tf_lib + +# REGLA 1: El bloque 'iap' está ausente en google_compute_backend_service. +CxPolicy[result] { + doc := input.document[i] + bs := doc.resource.google_compute_backend_service[name] + + not bs.iap + + result := { + "documentId": doc.id, + "resourceType": "google_compute_backend_service", + "resourceName": tf_lib.get_resource_name(bs, name), + "searchKey": sprintf("google_compute_backend_service[%s]", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": sprintf("'google_compute_backend_service.%s' should have an 'iap' block defined", [name]), + "keyActualValue": sprintf("'google_compute_backend_service.%s' is missing the 'iap' block", [name]), + } +} + +# REGLA 2: El bloque 'iap' existe pero falta 'oauth2_client_id'. +CxPolicy[result] { + doc := input.document[i] + bs := doc.resource.google_compute_backend_service[name] + + bs.iap + not bs.iap.oauth2_client_id + + result := { + "documentId": doc.id, + "resourceType": "google_compute_backend_service", + "resourceName": tf_lib.get_resource_name(bs, name), + "searchKey": sprintf("google_compute_backend_service[%s].iap", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "'oauth2_client_id' should be defined within the 'iap' block", + "keyActualValue": "'oauth2_client_id' is missing", + } +} + +# REGLA 3: El bloque 'iap' existe pero falta 'oauth2_client_secret'. +CxPolicy[result] { + doc := input.document[i] + bs := doc.resource.google_compute_backend_service[name] + + bs.iap + not bs.iap.oauth2_client_secret + + result := { + "documentId": doc.id, + "resourceType": "google_compute_backend_service", + "resourceName": tf_lib.get_resource_name(bs, name), + "searchKey": sprintf("google_compute_backend_service[%s].iap", [name]), + "issueType": "MissingAttribute", + "keyExpectedValue": "'oauth2_client_secret' should be defined within the 'iap' block", + "keyActualValue": "'oauth2_client_secret' is missing", + } +} diff --git a/assets/queries/terraform/gcp/gcp_sql_postgresql_log_error_verbosity_verbose/metadata.json b/assets/queries/terraform/gcp/gcp_sql_postgresql_log_error_verbosity_verbose/metadata.json index ac5563b73c0..f22e4df4ca1 100644 --- a/assets/queries/terraform/gcp/gcp_sql_postgresql_log_error_verbosity_verbose/metadata.json +++ b/assets/queries/terraform/gcp/gcp_sql_postgresql_log_error_verbosity_verbose/metadata.json @@ -9,5 +9,5 @@ "descriptionID": "80d98d7b", "cloudProvider": "gcp", "cwe": "CWE-532", - "riskScore": 5.0 + "riskScore": "5.0" } \ No newline at end of file diff --git a/assets/queries/terraform/gcp/gcp_sql_postgresql_log_error_verbosity_verbose/query.rego b/assets/queries/terraform/gcp/gcp_sql_postgresql_log_error_verbosity_verbose/query.rego index 080bfd7bc97..9a7d4410a0d 100644 --- a/assets/queries/terraform/gcp/gcp_sql_postgresql_log_error_verbosity_verbose/query.rego +++ b/assets/queries/terraform/gcp/gcp_sql_postgresql_log_error_verbosity_verbose/query.rego @@ -1,25 +1,29 @@ -package Cx - -ensure_array(x) = x { is_array(x) } -ensure_array(x) = [x] { is_object(x) } - -CxPolicy[result] { - doc := input.document[i] - resource := doc.resource.google_sql_database_instance[name] - - contains(resource.database_version, "POSTGRES") - - flags := ensure_array(resource.settings.database_flags) - flag := flags[j] - - flag.name == "log_error_verbosity" - lower(flag.value) == "verbose" - - result := { - "documentId": doc.id, - "searchKey": sprintf("resource.google_sql_database_instance.%s.settings.database_flags", [name]), - "issueType": "IncorrectValue", - "keyExpectedValue": "'log_error_verbosity' should be set to 'default' or 'terse'", - "keyActualValue": sprintf("'log_error_verbosity' is set to '%s'", [flag.value]), - } -} \ No newline at end of file +package Cx + +import data.generic.terraform as tf_lib + +ensure_array(x) = x { is_array(x) } +ensure_array(x) = [x] { is_object(x) } + +CxPolicy[result] { + doc := input.document[i] + resource := doc.resource.google_sql_database_instance[name] + + contains(resource.database_version, "POSTGRES") + + flags := ensure_array(resource.settings.database_flags) + flag := flags[j] + + flag.name == "log_error_verbosity" + lower(flag.value) == "verbose" + + result := { + "documentId": doc.id, + "resourceType": "google_sql_database_instance", + "resourceName": tf_lib.get_resource_name(resource, name), + "searchKey": sprintf("google_sql_database_instance[%s].settings.database_flags", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": "'log_error_verbosity' should be set to 'default' or 'terse'", + "keyActualValue": sprintf("'log_error_verbosity' is set to '%s'", [flag.value]), + } +}