open-policy-agent / conftest

Write tests against structured configuration data using the Open Policy Agent Rego query language
https://conftest.dev
Other
2.85k stars 301 forks source link

parse_config_file doesn't seem to work for terraform file #973

Closed Gaurang033 closed 5 days ago

Gaurang033 commented 1 month ago

I am writing a unit test for my conftest and I was looking into the following documention, which suggest that parse_config_file would parse the terraform file. https://www.conftest.dev/

However, if my understanding is correct. Conftest doesn't yet support terraform code and so you need to convvert terraform plan file to json file and write a test for json.

Which I did. something like this and it works when I run on terraform json plan.

package main 

mandatory_labels:= {"owner", "owner_email", "cost_center"}

deny_dataset_without_labels[msg] {
  resource := input.resource_changes[_]
  resource_labels := object.keys(resource.change.after.labels)
  resource.type == "google_bigquery_dataset"
  resource.change.actions[_] != "no-op"
  missing_labels := mandatory_labels - resource_labels
  count(missing_labels) > 0
  msg := sprintf("New resource '%v' must have the following labels: %v.", [resource.change.after.dataset_id, missing_labels])
}

However, unit tests. are not working for the same terraform file.

test_label_enforcer_for_dataset{
  cfg := parse_config_file("../policy_test_data/main.tf")
  deny_dataset_without_labels with input as cfg
  deny_dataset_without_labels["New resource 'dataset2' must have the following labels: {\"cost_center\", \"owner_email\"}."] with input as cfg
}

The reason being parse_config_file won't convert the terraform file to a json on which I have write policy. seems like the document is mis leading. or am I missing something. ?

This is the trace from conftest verify

file: label_enforcer_test.rego | query: test_label_enforcer_for_dataset
TRAC   Enter data.main.test_label_enforcer_for_dataset = _
TRAC   | Eval data.main.test_label_enforcer_for_dataset = _
TRAC   | Unify data.main.test_label_enforcer_for_dataset = _
TRAC   | Index data.main.test_label_enforcer_for_dataset (matched 1 rule, early exit)
TRAC   | Enter data.main.test_label_enforcer_for_dataset
TRAC   | | Eval parse_config_file("../policy_test_data/main.tf", __local15__)
TRAC   | | Unify __local15__ = {"module": {"this_should_fail_for_labels": {"bq_dataset": "this_should_fail", "bq_labels": {"owner": "xxx", "owner_email": "yyy", "req-num": "CLDENG-123"}, "production_bigquery": false, "project_id": "${var.project_id}", "source": "../../../modules/bigquery/"}}}
TRAC   | | Eval cfg = __local15__
TRAC   | | Unify cfg = {"module": {"this_should_fail_for_labels": {"bq_dataset": "this_should_fail", "bq_labels": {"owner": "xxx", "owner_email": "yyy", "req-num": "CLDENG-123"}, "production_bigquery": false, "project_id": "${var.project_id}", "source": "../../../modules/bigquery/"}}}
TRAC   | | Eval data.main.deny_dataset_without_labels with input as cfg
TRAC   | | Unify data.main.deny_dataset_without_labels = _
TRAC   | | Index data.main.deny_dataset_without_labels (matched 1 rule)
TRAC   | | Enter data.main.deny_dataset_without_labels
TRAC   | | | Eval resource = input.resource_changes[_]
TRAC   | | | Unify resource = input.resource_changes[_]
TRAC   | | | Fail resource = input.resource_changes[_]
TRAC   | | Unify set() = _
TRAC   | | Eval data.main.deny_dataset_without_labels["New resource 'dataset2' must have the following labels: {\"cost_center\", \"owner_email\"}."] with input as cfg
TRAC   | | Unify data.main.deny_dataset_without_labels["New resource 'dataset2' must have the following labels: {\"cost_center\", \"owner_email\"}."] = _
TRAC   | | Index data.main.deny_dataset_without_labels (matched 1 rule)
TRAC   | | Enter data.main.deny_dataset_without_labels
TRAC   | | | Unify "New resource 'dataset2' must have the following labels: {\"cost_center\", \"owner_email\"}." = msg
TRAC   | | | Eval resource = input.resource_changes[_]
TRAC   | | | Unify resource = input.resource_changes[_]
TRAC   | | | Fail resource = input.resource_changes[_]
TRAC   | | Fail data.main.deny_dataset_without_labels["New resource 'dataset2' must have the following labels: {\"cost_center\", \"owner_email\"}."] with input as cfg
TRAC   | | Redo data.main.deny_dataset_without_labels with input as cfg
TRAC   | | Redo cfg = __local15__
TRAC   | | Redo parse_config_file("../policy_test_data/main.tf", __local15__)
TRAC   | Fail data.main.test_label_enforcer_for_dataset = _
jalseth commented 1 month ago

The output from converting a .tf file is not a Terraform plan, the structure is different. If you're writing your Rego against the Terraform plan structure, you must provide a plan for the test input. See https://github.com/open-policy-agent/conftest/blob/master/examples/hcl2/policy/deny.rego as an example of Rego written to test against .tf files.

Gaurang033 commented 1 month ago

https://github.com/open-policy-agent/conftest/blob/master/examples/hcl2/policy/deny.rego

I agree those I two different thing. my understanding is opa doesn't support tf. correct me if I am wrong. and so I covert my plan to json and write test for it. however, i so in documentation that unit test can. which is weird. does that mean my actual test can support terraform as well ?

jalseth commented 1 month ago

Conftest uses the OPA engine, but is not OPA. Conftest can accept and parse Terraform files or JSON, but the structure for these is not the same, so you need to write your Rego accordingly.

jalseth commented 5 days ago

Closing due to inactivity, feel free to reopen if needed.