Skip to content

Writing Rules⚓︎

Custom rule development generally has four parts:

Types of rules⚓︎


For a tutorial on writing a simple custom rule, see Example: Writing a Simple Rule.

Regula rules are written in Rego and use the same format as Fugue Custom Rules. This means there are (currently) two kinds of rules: simple rules and advanced rules.

Simple rules⚓︎

Simple rules are useful when the policy applies to a single resource type only, and you want to make simple yes/no decision.

# Rules must always be located right below the `rules` package.
package rules.my_simple_rule

# Simple rules must specify the resource type they will police.
resource_type = "aws_ebs_volume"

# Simple rules must specify `allow` or `deny`.  For this example, we use
# an `allow` rule to check that the EBS volume is encrypted.
default allow = false
allow {
  input.encrypted == true

Custom error messages and attributes (simple rules)⚓︎

If you want to return more information to the user, you can also define a custom error message in a simple rule. This is done by writing a deny[msg] style rule.

package rules.simple_rule_custom_message
resource_type = "aws_ebs_volume"

deny[msg] {
  not input.encrypted
  msg = "EBS volumes should be encrypted"

Advanced rules⚓︎

Advanced rules are harder to write, but more powerful. They allow you to observe different kinds of resource types and decide which specific resources are valid or invalid.

# Rules still must be located in the `rules` package.
package rules.user_attached_policy

# Advanced rules typically use functions from the `fugue` library.
import data.fugue

# We mark an advanced rule by setting `resource_type` to `MULTIPLE`.
resource_type = "MULTIPLE"

# `fugue.resources` is a function that allows querying for resources of a
# specific type.  In our case, we are just going to ask for the EBS volumes
# again.
ebs_volumes = fugue.resources("aws_ebs_volume")

# Auxiliary function.
is_encrypted(resource) {
  resource.encrypted == true

# Regula expects advanced rules to contain a `policy` rule that holds a set
# of _judgements_.
policy[p] {
  resource = ebs_volumes[_]
  p = fugue.allow_resource(resource)
} {
  resource = ebs_volumes[_]
  not is_encrypted(resource)
  p = fugue.deny_resource(resource)

The fugue API consists of these functions for advanced rules:

  • fugue.resources(resource_type) returns an object with all resources of the requested type.
  • fugue.allow_resource(resource) marks a resource as valid.
  • fugue.deny_resource(resource) marks a resource as invalid.
  • fugue.deny_resource_with_message(resource, msg) marks a resource as invalid and displays a custom rule_message in the report.
  • fugue.missing_resource(resource_type) marks a resource as missing. This is useful if you for example require a log group to be present.
  • fugue.missing_resource_with_message(resource_type, msg) marks a resource as missing and displays a custom rule_message in the report.

Custom error messages (advanced rules)⚓︎

As stated above, the functions fugue.deny_resource_with_message(resource, msg) and fugue.missing_resource_with_message(resource_type, msg) allow Regula to display a custom rule_message in its report. This rule demonstrates both functions:

package rules.account_password_policy

import data.fugue

resource_type = "MULTIPLE"

password_policies = fugue.resources("aws_iam_account_password_policy")

policy[r] {
  password_policy = password_policies[_]
  password_policy.minimum_password_length >= 16
  r = fugue.allow_resource(password_policy)
} {
  password_policy = password_policies[_]
  not password_policy.minimum_password_length >= 16
  msg = "Password policy is too short. It must be at least 16 characters."
  r = fugue.deny_resource_with_message(password_policy, msg)
} {
  count(password_policies) == 0
  msg = "No password policy exists."
  r = fugue.missing_resource_with_message("aws_iam_account_password_policy", msg)

Here's an example rule result demonstrating a missing resource message:

      "controls": [
      "filepath": "",
      "input_type": "tf",
      "provider": "",
      "resource_id": "",
      "resource_type": "aws_iam_account_password_policy",
      "rule_description": "Per company policy, an AWS account must have a password policy, and it must require a minimum of 16 characters",
      "rule_id": "CUSTOM_0001",
      "rule_message": "No password policy exists.",
      "rule_name": "account_password_policy",
      "rule_result": "FAIL",
      "rule_severity": "Medium",
      "rule_summary": "An AWS account must have a password policy requiring a minimum of 16 characters"

Adding rule metadata⚓︎

You can add metadata to a rule to enhance Regula's report:

__rego__metadoc__ := {
  "id": "CUSTOM_0001",
  "title": "IAM policies must have a description of at least 25 characters",
  "description": "Per company policy, it is required for all IAM policies to have a description of at least 25 characters.",
  "custom": {
    "controls": {
    "severity": "Low"

Regula supports the following metadata properties:

  • id: Rule ID
  • title: Short summary of the rule
  • description: Longer description of the rule
  • controls: An object where the key is the compliance family name and the value is an array of controls
  • severity: One of Critical, High, Medium, Low, Informational

Here's an example rule result to show how this metadata looks in the report:

      "controls": [
      "filepath": "../regula-ci-example/infra_tf/",
      "input_type": "tf",
      "provider": "aws",
      "resource_id": "aws_iam_policy.basically_allow_all",
      "resource_type": "aws_iam_policy",
      "rule_description": "Per company policy, it is required for all IAM policies to have a description of at least 25 characters.",
      "rule_id": "CUSTOM_0001",
      "rule_message": "",
      "rule_name": "long_description",
      "rule_result": "FAIL",
      "rule_severity": "Low",
      "rule_summary": "IAM policies must have a description of at least 25 characters"

CloudFormation vs. Terraform rules⚓︎

CloudFormation rules are written the same way Terraform rules are, but require the line input_type := "cfn", as shown in the simple rule below:

package rules.cfn_ebs_volume_encryption

input_type := "cfn"

resource_type := "AWS::EC2::Volume"

default allow = false

allow {
  input.Encrypted == true

Terraform rules do not require input_type to be explicitly set.

Additionally, the resource_type is specified differently for CloudFormation and Terraform: