From 259600001c018eaffba905824f334913cea5211b Mon Sep 17 00:00:00 2001 From: Ricardo Jesus <219317970+cx-ricardo-jesus@users.noreply.github.com> Date: Tue, 21 Oct 2025 12:08:59 +0100 Subject: [PATCH 1/8] feat(query): implemented query Beta - Key Vault Purge Protection Is Enabled --- .../metadata.json | 14 +++++ .../query.rego | 52 +++++++++++++++++++ .../test/negative.tf | 9 ++++ .../test/positive1.tf | 9 ++++ .../test/positive2.tf | 8 +++ .../test/positive_expected_result.json | 14 +++++ 6 files changed, 106 insertions(+) create mode 100644 assets/queries/terraform/azure/key_vault_purge_protection_is_enabled/metadata.json create mode 100644 assets/queries/terraform/azure/key_vault_purge_protection_is_enabled/query.rego create mode 100644 assets/queries/terraform/azure/key_vault_purge_protection_is_enabled/test/negative.tf create mode 100644 assets/queries/terraform/azure/key_vault_purge_protection_is_enabled/test/positive1.tf create mode 100644 assets/queries/terraform/azure/key_vault_purge_protection_is_enabled/test/positive2.tf create mode 100644 assets/queries/terraform/azure/key_vault_purge_protection_is_enabled/test/positive_expected_result.json diff --git a/assets/queries/terraform/azure/key_vault_purge_protection_is_enabled/metadata.json b/assets/queries/terraform/azure/key_vault_purge_protection_is_enabled/metadata.json new file mode 100644 index 00000000000..27b5b7b2258 --- /dev/null +++ b/assets/queries/terraform/azure/key_vault_purge_protection_is_enabled/metadata.json @@ -0,0 +1,14 @@ +{ + "id": "cec6e005-9309-46eb-b34b-456f6eae818b", + "queryName": "Beta - Key Vault Purge Protection Is Enabled", + "severity": "High", + "category": "Backup", + "descriptionText": "Deleting an Azure Key Vault without purge protection enabled can cause permanent loss of keys, secrets, and certificates, leading to unrecoverable data loss and disruption of dependent services.", + "descriptionUrl": "https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/key_vault", + "platform": "Terraform", + "descriptionID": "cec6e005", + "cloudProvider": "azure", + "cwe": "530", + "riskScore": "8.5", + "experimental": true +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/key_vault_purge_protection_is_enabled/query.rego b/assets/queries/terraform/azure/key_vault_purge_protection_is_enabled/query.rego new file mode 100644 index 00000000000..21938d431da --- /dev/null +++ b/assets/queries/terraform/azure/key_vault_purge_protection_is_enabled/query.rego @@ -0,0 +1,52 @@ +package Cx + +import data.generic.terraform as tf_lib +import data.generic.common as common_lib + +CxPolicy[result] { + key_vault := input.document[i].resource.azurerm_key_vault[name] + + res := get_res(key_vault, name) + + result := { + "documentId": input.document[i].id, + "resourceType": "azurerm_key_vault", + "resourceName": tf_lib.get_resource_name(key_vault, name), + "searchKey": res["sk"], + "searchLine": res["sl"], + "issueType": res["it"], + "keyExpectedValue": res["kev"], + "keyActualValue": res["kav"], + "remediation": res["rem"], + "remediationType": res["rtype"], + } +} + +get_res(resource, name) = res { + not common_lib.valid_key(resource, "purge_protection_enabled") + + res := { + "it": "MissingAttribute", + "sk": sprintf("azurerm_key_vault[%s]", [name]), + "sl": common_lib.build_search_line(["resource", "azurerm_key_vault", name], []), + "kev": "'purge_protection_enabled' should be defined and set to true", + "kav": "'purge_protection_enabled' is not defined", + "rem": "\npurge_protection_enabled = true\n", + "rtype": "addition" + } +} else = res { + not resource.purge_protection_enabled == true + + res := { + "it": "IncorrectValue", + "sk": sprintf("azurerm_key_vault[%s].purge_protection_enabled", [name]), + "sl": common_lib.build_search_line(["resource", name, "purge_protection_enabled"], []), + "kev": "'purge_protection_enabled' field should be set to true", + "kav": "'purge_protection_enabled' is not set to true", + "rem": json.marshal({ + "before": "false", + "after": "true" + }), + "rtype": "replacement" + } +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/key_vault_purge_protection_is_enabled/test/negative.tf b/assets/queries/terraform/azure/key_vault_purge_protection_is_enabled/test/negative.tf new file mode 100644 index 00000000000..468c06ad18a --- /dev/null +++ b/assets/queries/terraform/azure/key_vault_purge_protection_is_enabled/test/negative.tf @@ -0,0 +1,9 @@ +resource "azurerm_key_vault" "negative1" { + name = "examplekeyvault" + location = azurerm_resource_group.example.location + resource_group_name = azurerm_resource_group.example.name + tenant_id = data.azurerm_client_config.current.tenant_id + sku_name = "standard" + soft_delete_retention_days = 7 + purge_protection_enabled = true +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/key_vault_purge_protection_is_enabled/test/positive1.tf b/assets/queries/terraform/azure/key_vault_purge_protection_is_enabled/test/positive1.tf new file mode 100644 index 00000000000..13dcaea1b52 --- /dev/null +++ b/assets/queries/terraform/azure/key_vault_purge_protection_is_enabled/test/positive1.tf @@ -0,0 +1,9 @@ +resource "azurerm_key_vault" "positive1" { + name = "examplekeyvault" + location = azurerm_resource_group.example.location + resource_group_name = azurerm_resource_group.example.name + tenant_id = data.azurerm_client_config.current.tenant_id + sku_name = "standard" + soft_delete_retention_days = 7 + purge_protection_enabled = false +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/key_vault_purge_protection_is_enabled/test/positive2.tf b/assets/queries/terraform/azure/key_vault_purge_protection_is_enabled/test/positive2.tf new file mode 100644 index 00000000000..8468a1dc29d --- /dev/null +++ b/assets/queries/terraform/azure/key_vault_purge_protection_is_enabled/test/positive2.tf @@ -0,0 +1,8 @@ +resource "azurerm_key_vault" "positive2" { + name = "examplekeyvault" + location = azurerm_resource_group.example.location + resource_group_name = azurerm_resource_group.example.name + tenant_id = data.azurerm_client_config.current.tenant_id + sku_name = "standard" + soft_delete_retention_days = 7 +} \ No newline at end of file diff --git a/assets/queries/terraform/azure/key_vault_purge_protection_is_enabled/test/positive_expected_result.json b/assets/queries/terraform/azure/key_vault_purge_protection_is_enabled/test/positive_expected_result.json new file mode 100644 index 00000000000..c8042f12aba --- /dev/null +++ b/assets/queries/terraform/azure/key_vault_purge_protection_is_enabled/test/positive_expected_result.json @@ -0,0 +1,14 @@ +[ + { + "queryName": "Beta - Key Vault Purge Protection Is Enabled", + "severity": "", + "line": 8, + "fileName": "positive1.tf" + }, + { + "queryName": "Beta - Key Vault Purge Protection Is Enabled", + "severity": "", + "line": 1, + "fileName": "positive2.tf" + } +] \ No newline at end of file From 1bb7d6f0c8fa0182d86e8cde921c3f47a391c087 Mon Sep 17 00:00:00 2001 From: Ricardo Jesus <219317970+cx-ricardo-jesus@users.noreply.github.com> Date: Tue, 21 Oct 2025 12:28:05 +0100 Subject: [PATCH 2/8] fixed severity on metadata --- .../azure/key_vault_purge_protection_is_enabled/metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/queries/terraform/azure/key_vault_purge_protection_is_enabled/metadata.json b/assets/queries/terraform/azure/key_vault_purge_protection_is_enabled/metadata.json index 27b5b7b2258..f515321c93a 100644 --- a/assets/queries/terraform/azure/key_vault_purge_protection_is_enabled/metadata.json +++ b/assets/queries/terraform/azure/key_vault_purge_protection_is_enabled/metadata.json @@ -1,7 +1,7 @@ { "id": "cec6e005-9309-46eb-b34b-456f6eae818b", "queryName": "Beta - Key Vault Purge Protection Is Enabled", - "severity": "High", + "severity": "HIGH", "category": "Backup", "descriptionText": "Deleting an Azure Key Vault without purge protection enabled can cause permanent loss of keys, secrets, and certificates, leading to unrecoverable data loss and disruption of dependent services.", "descriptionUrl": "https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/key_vault", From dac4948a0fe584e5ffb3eceda9b58249d1991499 Mon Sep 17 00:00:00 2001 From: Ricardo Jesus <219317970+cx-ricardo-jesus@users.noreply.github.com> Date: Tue, 21 Oct 2025 12:30:47 +0100 Subject: [PATCH 3/8] changed experimental value to string --- .../azure/key_vault_purge_protection_is_enabled/metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/queries/terraform/azure/key_vault_purge_protection_is_enabled/metadata.json b/assets/queries/terraform/azure/key_vault_purge_protection_is_enabled/metadata.json index f515321c93a..64982d247a7 100644 --- a/assets/queries/terraform/azure/key_vault_purge_protection_is_enabled/metadata.json +++ b/assets/queries/terraform/azure/key_vault_purge_protection_is_enabled/metadata.json @@ -10,5 +10,5 @@ "cloudProvider": "azure", "cwe": "530", "riskScore": "8.5", - "experimental": true + "experimental": "true" } \ No newline at end of file From 07aa3de0a1604d6228a6adf481114d0e88fa98d1 Mon Sep 17 00:00:00 2001 From: Ricardo Jesus <219317970+cx-ricardo-jesus@users.noreply.github.com> Date: Tue, 21 Oct 2025 12:50:48 +0100 Subject: [PATCH 4/8] changed severity from HIGH to MEDIUM --- .../azure/key_vault_purge_protection_is_enabled/metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/queries/terraform/azure/key_vault_purge_protection_is_enabled/metadata.json b/assets/queries/terraform/azure/key_vault_purge_protection_is_enabled/metadata.json index 64982d247a7..82bd2aad92f 100644 --- a/assets/queries/terraform/azure/key_vault_purge_protection_is_enabled/metadata.json +++ b/assets/queries/terraform/azure/key_vault_purge_protection_is_enabled/metadata.json @@ -1,7 +1,7 @@ { "id": "cec6e005-9309-46eb-b34b-456f6eae818b", "queryName": "Beta - Key Vault Purge Protection Is Enabled", - "severity": "HIGH", + "severity": "MEDIUM", "category": "Backup", "descriptionText": "Deleting an Azure Key Vault without purge protection enabled can cause permanent loss of keys, secrets, and certificates, leading to unrecoverable data loss and disruption of dependent services.", "descriptionUrl": "https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/key_vault", From c9cb6d75426141dbf0b29406d959ffd0967d5e1d Mon Sep 17 00:00:00 2001 From: Ricardo Jesus <219317970+cx-ricardo-jesus@users.noreply.github.com> Date: Tue, 21 Oct 2025 12:51:46 +0100 Subject: [PATCH 5/8] fixed positve_expected_result --- .../azure/key_vault_purge_protection_is_enabled/metadata.json | 2 +- .../test/positive_expected_result.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/assets/queries/terraform/azure/key_vault_purge_protection_is_enabled/metadata.json b/assets/queries/terraform/azure/key_vault_purge_protection_is_enabled/metadata.json index 82bd2aad92f..64982d247a7 100644 --- a/assets/queries/terraform/azure/key_vault_purge_protection_is_enabled/metadata.json +++ b/assets/queries/terraform/azure/key_vault_purge_protection_is_enabled/metadata.json @@ -1,7 +1,7 @@ { "id": "cec6e005-9309-46eb-b34b-456f6eae818b", "queryName": "Beta - Key Vault Purge Protection Is Enabled", - "severity": "MEDIUM", + "severity": "HIGH", "category": "Backup", "descriptionText": "Deleting an Azure Key Vault without purge protection enabled can cause permanent loss of keys, secrets, and certificates, leading to unrecoverable data loss and disruption of dependent services.", "descriptionUrl": "https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/key_vault", diff --git a/assets/queries/terraform/azure/key_vault_purge_protection_is_enabled/test/positive_expected_result.json b/assets/queries/terraform/azure/key_vault_purge_protection_is_enabled/test/positive_expected_result.json index c8042f12aba..97bfb0544c9 100644 --- a/assets/queries/terraform/azure/key_vault_purge_protection_is_enabled/test/positive_expected_result.json +++ b/assets/queries/terraform/azure/key_vault_purge_protection_is_enabled/test/positive_expected_result.json @@ -1,13 +1,13 @@ [ { "queryName": "Beta - Key Vault Purge Protection Is Enabled", - "severity": "", + "severity": "HIGH", "line": 8, "fileName": "positive1.tf" }, { "queryName": "Beta - Key Vault Purge Protection Is Enabled", - "severity": "", + "severity": "HIGH", "line": 1, "fileName": "positive2.tf" } From 826d22a7bc4cf242c485ea4ab11833f0afca9399 Mon Sep 17 00:00:00 2001 From: Ricardo Jesus <219317970+cx-ricardo-jesus@users.noreply.github.com> Date: Tue, 28 Oct 2025 11:49:20 +0000 Subject: [PATCH 6/8] fixed remediation --- pkg/remediation/remediation.go | 6 ++++-- pkg/remediation/scan.go | 10 ++++++---- pkg/remediation/utils.go | 17 +++++++++-------- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/pkg/remediation/remediation.go b/pkg/remediation/remediation.go index 1354543d354..1e721476f75 100644 --- a/pkg/remediation/remediation.go +++ b/pkg/remediation/remediation.go @@ -17,8 +17,9 @@ type Report struct { // Query includes all the files that presents a result related to the queryID type Query struct { - Files []File `json:"files"` - QueryID string `json:"query_id"` + Files []File `json:"files"` + QueryID string `json:"query_id"` + Experimental bool `json:"experimental"` } // File presents the result information related to the file @@ -42,6 +43,7 @@ type Remediation struct { SearchKey string ExpectedValue string ActualValue string + Experimental bool } // Set includes all the replacements and additions related to a file diff --git a/pkg/remediation/scan.go b/pkg/remediation/scan.go index 8afbd4855c0..b443aa6adfb 100644 --- a/pkg/remediation/scan.go +++ b/pkg/remediation/scan.go @@ -43,7 +43,8 @@ func scanTmpFile( tmpFile, queryID string, remediated []byte, openAPIResolveReferences bool, - maxResolverDepth int) ([]model.Vulnerability, error) { + maxResolverDepth int, + experimental bool) ([]model.Vulnerability, error) { // get payload files, err := getPayload(tmpFile, remediated, openAPIResolveReferences, maxResolverDepth) @@ -60,7 +61,7 @@ func scanTmpFile( payload := files.Combine(false) // init scan - inspector, err := initScan(queryID) + inspector, err := initScan(queryID, experimental) if err != nil { log.Err(err) @@ -194,7 +195,7 @@ func runQuery(r *runQueryInfo) []model.Vulnerability { return decoded } -func initScan(queryID string) (*engine.Inspector, error) { +func initScan(queryID string, experimental bool) (*engine.Inspector, error) { scanParams := &scan.Parameters{ QueriesPath: flags.GetMultiStrFlag(flags.QueriesPath), Platform: flags.GetMultiStrFlag(flags.TypeFlag), @@ -228,6 +229,7 @@ func initScan(queryID string) (*engine.Inspector, error) { queryFilter := source.QueryInspectorParameters{ IncludeQueries: includeQueries, + ExperimentalQueries: experimental, } t, err := tracker.NewTracker(c.ScanParams.PreviewLines) @@ -271,5 +273,5 @@ func loadQuery(inspector *engine.Inspector, queryID string) (*engine.PreparedQue return query, nil } - return &engine.PreparedQuery{}, errors.New("unable to load query" + queryID) + return &engine.PreparedQuery{}, errors.New("unable to load query " + queryID) } diff --git a/pkg/remediation/utils.go b/pkg/remediation/utils.go index e279d0386dd..8a714a75e26 100644 --- a/pkg/remediation/utils.go +++ b/pkg/remediation/utils.go @@ -80,7 +80,7 @@ func willRemediate( } // scan the temporary file to verify if the remediation removed the result - results, err := scanTmpFile(tmpFile, remediation.QueryID, content, openAPIResolveReferences, maxResolverDepth) + results, err := scanTmpFile(tmpFile, remediation.QueryID, content, openAPIResolveReferences, maxResolverDepth, remediation.Experimental) if err != nil { log.Error().Msgf("failed to get results of query %s: %s", remediation.QueryID, err) @@ -159,7 +159,6 @@ func (s *Summary) GetRemediationSetsFromVulns(vulnerabilities []model.Vulnerabil } var remediationSet Set - if shouldRemediate(&file, include) { s.SelectedRemediationNumber++ r := &Remediation{ @@ -170,26 +169,27 @@ func (s *Summary) GetRemediationSetsFromVulns(vulnerabilities []model.Vulnerabil SearchKey: vuln.SearchKey, ExpectedValue: vuln.KeyExpectedValue, ActualValue: vuln.KeyActualValue, + Experimental: vuln.Experimental, } - + if file.RemediationType == "replacement" { remediationSet.Replacement = append(remediationSet.Replacement, *r) } - + if file.RemediationType == "addition" { remediationSet.Addition = append(remediationSet.Addition, *r) } - + if _, ok := remediationSets[file.FilePath]; !ok { remediationSets[file.FilePath] = remediationSet continue } - + updatedRemediationSet := remediationSets[file.FilePath].(Set) - + updatedRemediationSet.Addition = append(updatedRemediationSet.Addition, remediationSet.Addition...) updatedRemediationSet.Replacement = append(updatedRemediationSet.Replacement, remediationSet.Replacement...) - + remediationSets[file.FilePath] = updatedRemediationSet } } @@ -213,6 +213,7 @@ func getVulns(results Report) []model.Vulnerability { SearchKey: file.SearchKey, KeyExpectedValue: file.ExpectedValue, KeyActualValue: file.ActualValue, + Experimental: query.Experimental, } vulns = append(vulns, *vuln) From 87d30ed8017643c2541560f9e0c5ec3b27ee841e Mon Sep 17 00:00:00 2001 From: Ricardo Jesus <219317970+cx-ricardo-jesus@users.noreply.github.com> Date: Tue, 28 Oct 2025 11:56:10 +0000 Subject: [PATCH 7/8] fixed searchLine --- .../azure/key_vault_purge_protection_is_enabled/query.rego | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/queries/terraform/azure/key_vault_purge_protection_is_enabled/query.rego b/assets/queries/terraform/azure/key_vault_purge_protection_is_enabled/query.rego index 21938d431da..da69739b6c8 100644 --- a/assets/queries/terraform/azure/key_vault_purge_protection_is_enabled/query.rego +++ b/assets/queries/terraform/azure/key_vault_purge_protection_is_enabled/query.rego @@ -31,7 +31,7 @@ get_res(resource, name) = res { "sl": common_lib.build_search_line(["resource", "azurerm_key_vault", name], []), "kev": "'purge_protection_enabled' should be defined and set to true", "kav": "'purge_protection_enabled' is not defined", - "rem": "\npurge_protection_enabled = true\n", + "rem": "purge_protection_enabled = true", "rtype": "addition" } } else = res { @@ -40,7 +40,7 @@ get_res(resource, name) = res { res := { "it": "IncorrectValue", "sk": sprintf("azurerm_key_vault[%s].purge_protection_enabled", [name]), - "sl": common_lib.build_search_line(["resource", name, "purge_protection_enabled"], []), + "sl": common_lib.build_search_line(["resource", "azurerm_key_vault", name, "purge_protection_enabled"], []), "kev": "'purge_protection_enabled' field should be set to true", "kav": "'purge_protection_enabled' is not set to true", "rem": json.marshal({ From fd85c91f3f28a5fe676e58018d391d41b88d646d Mon Sep 17 00:00:00 2001 From: Ricardo Jesus <219317970+cx-ricardo-jesus@users.noreply.github.com> Date: Tue, 28 Oct 2025 12:22:03 +0000 Subject: [PATCH 8/8] fixed fmt error --- pkg/remediation/remediation.go | 2 +- pkg/remediation/scan.go | 2 +- pkg/remediation/utils.go | 14 +++++++------- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pkg/remediation/remediation.go b/pkg/remediation/remediation.go index 1e721476f75..242eaf92aef 100644 --- a/pkg/remediation/remediation.go +++ b/pkg/remediation/remediation.go @@ -19,7 +19,7 @@ type Report struct { type Query struct { Files []File `json:"files"` QueryID string `json:"query_id"` - Experimental bool `json:"experimental"` + Experimental bool `json:"experimental"` } // File presents the result information related to the file diff --git a/pkg/remediation/scan.go b/pkg/remediation/scan.go index b443aa6adfb..e48f5648ee0 100644 --- a/pkg/remediation/scan.go +++ b/pkg/remediation/scan.go @@ -228,7 +228,7 @@ func initScan(queryID string, experimental bool) (*engine.Inspector, error) { } queryFilter := source.QueryInspectorParameters{ - IncludeQueries: includeQueries, + IncludeQueries: includeQueries, ExperimentalQueries: experimental, } diff --git a/pkg/remediation/utils.go b/pkg/remediation/utils.go index 8a714a75e26..39c43e8428d 100644 --- a/pkg/remediation/utils.go +++ b/pkg/remediation/utils.go @@ -171,25 +171,25 @@ func (s *Summary) GetRemediationSetsFromVulns(vulnerabilities []model.Vulnerabil ActualValue: vuln.KeyActualValue, Experimental: vuln.Experimental, } - + if file.RemediationType == "replacement" { remediationSet.Replacement = append(remediationSet.Replacement, *r) } - + if file.RemediationType == "addition" { remediationSet.Addition = append(remediationSet.Addition, *r) } - + if _, ok := remediationSets[file.FilePath]; !ok { remediationSets[file.FilePath] = remediationSet continue } - + updatedRemediationSet := remediationSets[file.FilePath].(Set) - + updatedRemediationSet.Addition = append(updatedRemediationSet.Addition, remediationSet.Addition...) updatedRemediationSet.Replacement = append(updatedRemediationSet.Replacement, remediationSet.Replacement...) - + remediationSets[file.FilePath] = updatedRemediationSet } } @@ -213,7 +213,7 @@ func getVulns(results Report) []model.Vulnerability { SearchKey: file.SearchKey, KeyExpectedValue: file.ExpectedValue, KeyActualValue: file.ActualValue, - Experimental: query.Experimental, + Experimental: query.Experimental, } vulns = append(vulns, *vuln)