Writing a rule
Rules in Rego
Rules are written in Rego. When you are writing Rego, you do two things:
Write rules that make policy decisions. A rule is a conditional assignment.
Organize rules into policies. A policy is a set of rules with a hierarchical name
To learn more about the Policy Language, visit the official OPA Policy Language Documentation Page.
How to generate a new rule
There are two options to get started:
Use the
template
command to generate the required files for writing a rule:snyk-iac-rules template --rule <RULE-NAME> --format <hcl2|json|yaml|tf-plan>
This generates the scaffolding for the rule, including fixture files based on the provided configuration format. For more details, read the documentation about the template command.
Create a Rego policy from scratch and match the expected file and folder structure on your own:
rules
└── my_rule
├── main.rego
└── main_test.rego
Structure of the rule
In Rego, you can write statements that allow or deny a request, such as:
allow { input.name == "alice" }
or deny { input.name == "alice" }
This is what a generated skeleton of a deny rule looks like when we run snyk-iac-rules template --rule NEW-RULE --format hcl2
:
package rules
deny[msg] {
resource := input.resource.test[name]
resource.todo
msg := {
# Mandatory fields
"publicId": "NEW-RULE",
"title": "Default title",
"severity": "low",
"msg": sprintf("input.resource.test[%s].todo", [name]),
# Optional fields
"issue": "",
"impact": "",
"remediation": "",
"references": [],
}
}
You must follow this format of the msg property to ensure the output correctly displays on the Snyk IaC CLI.
The attributes are:
publicId: a naming convention unique to yourselves, such as COMPANY-001. This should not contain/start with “SNYK-” to differentiate from the internal Snyk rules.
title: a short title that should summarise the issue.
severity: this can be one of low/medium/high/critical.
msg: we recommend only changing the resource name and property e.g.
input.aws_s3_bucket[%s].tags
to match your example. The functionsprintf
is provided by Rego and enables us to provide a dynamic error message explaining exactly where the issue was found.
The following attributes are optional but can be used to enhance the scan results in the Snyk CLI:
issue: a more detailed string explanation of what the exact issue is.
impact: a more detailed string explanation of what the impact of not resolving this issue is.
remediation: a more detailed string explanation of how to resolve the issue. We recommend providing a code snippet here.
references: you can provide an array of strings with URLs to documentation
The generated test for the rule uses two generated Terraform files to verify if the correct msg
field is returned by the rule for allowed and denied fixtures:
package rules
import data.lib
import data.lib.testing
test_NEW_RULE {
# array containing test cases where the rule is allowed
allowed_test_cases := [{
"want_msgs": [],
"fixture": "allowed.tf",
}]
# array containing cases where the rule is denied
denied_test_cases := [{
"want_msgs": ["input.resource.test[denied].todo"], # verifies that the correct msg is returned by the denied rule
"fixture": "denied.tf",
}]
test_cases := array.concat(allowed_test_cases, denied_test_cases)
testing.evaluate_test_cases("NEW-RULE", "./rules/NEW-RULE/fixtures", test_cases)
}
Example of a rule
For this example, we modified our templated rule to assign a msg
when a resource does not have an owner
tag:
package rules
deny[msg] {
resource := input.resource.aws_redshift_cluster[name]
not resource.tags.owner
msg := {
"publicId": "MY_RULE",
"title": "Missing an owner from tag",
"severity": "medium",
"msg": sprintf("input.resource.aws_redshift_cluster[%s].tags", [name]),
"issue": "",
"impact": "",
"remediation": "",
"references": [],
}
}
Limitations and notes
As we compile Rego policies into Wasm modules, you can only use built-in functions that support Wasm. There is a table at the bottom of the Policy Reference Documentation that can help you identify those.
A rule may be defined multiple times with the same name, either in a file, or in separate files under the same package, e.g:
packages rules
deny[msg] {
resource.this
}
...
deny[msg] {
resource.that
}
...
These rules are referred as incremental
as each definition is additive. You can read more about Incremental Definitions here. Note that these same named rules have to return a different value, or OPA will return an error. You can read more about Complete Definitions here.
For more complex topics, check how OPA resolves Conflict Resolution.
Last updated
Was this helpful?