• HashiCorp Developer

Products

  • HashiCorp Cloud Platform
  • Terraform
  • Packer
  • Consul
  • Vault
  • Boundary
  • Nomad
  • Waypoint
  • Vagrant
  • Home
    • General Documentation
    • Configuration Language
    • Terraform CLI
    • Terraform Cloud
    • Terraform Enterprise
    • CDK for Terraform
    • Provider Use
    • Plugin Development
    • Registry Publishing
    • Integration Program
  • Tutorials
  • Install
  • Registry
Sign Up
Sign Up
Terraform Home

Terraform Cloud

Skip to main content
  • Terraform Cloud

  • Overview
  • Plans and Features
  • Getting Started
  • Migrating to Terraform Cloud
    • Overview
      • Defining Policies
      • Policy Set VCS Repositories
    • Managing Policy Sets
    • Policy Results

  • Terraform Cloud Agents

  • Resources

  • Tutorial Library
  • Community Forum
  • Support
  • GitHub
  • Terraform Registry
  1. Developer
  2. Terraform
  3. Terraform Cloud
  4. Policy Enforcement
  5. OPA Policies

»Defining OPA Policies

Note: Policies are available in the Terraform Cloud Team and Governance tier, and OPA policies are in beta. OPA Policies are not available in Terraform Enterprise.

Policies are rules that Terraform Cloud enforces on runs. You use the Rego policy language to write policies for the Open Policy Agent (OPA) framework. After you define policies, you must add them to policy sets that Terraform Cloud can enforce on workspaces.

Hands-on: Try the Detect Infrastructure Drift and Enforce OPA Policies tutorial.

Defining Policies

You can write Rego policies to check for any number of conditions. Common use cases include checking whether infrastructure configuration adheres to security standards or best practices. For example, you may want to write a policy to check whether Terraform plans to deploy production infrastructure to the correct region.

You can also use policies to enforce standards for your organization’s workflows. For example, you could write a policy to prevent new infrastructure deployments on Fridays, reducing the risk of production incidents outside of your team’s working hours.

Refer to How Do I Write Rego Policies? in the Rego documentation for more details. We also recommend using the Rego Policy Playground to iterate on new policies.

OPA Query

You must write a query to identify a specific policy rule within your Rego code. The query may evaluate code from multiple Rego files.

The result of each query must return an array, which Terraform Cloud uses to determine whether the policy has passed or failed. If the array is empty, Terraform Cloud reports that the policy has passed.

The query is typically a combination of the policy package name and rule name, such as data.terraform.deny.

OPA Input

Terraform Cloud combines the output from the Terraform run and plan into a single JSON file and passes that file to OPA as input. Refer to the OPA Overview documentation for more details about how OPA uses JSON input data.

The run data contains information like workspace details and the organization name. To access the properties from the Terraform plan data in your policies, use input.plan. To access properties from the Terraform run, use input.run.

The following example shows sample OPA input data.

{
"plan": {
 "format_version": "1.1",
 "output_changes": {
 },
 "planned_values": {
  },
  "resource_changes": [
 ],
 "terraform_version": "1.2.7"
},

"run": {
  "organization": {
  "name": "hashicorp"
  },
  "workspace": {
  }
}
}
{
"plan": {
 "format_version": "1.1",
 "output_changes": {
 },
 "planned_values": {
  },
  "resource_changes": [
 ],
 "terraform_version": "1.2.7"
},

"run": {
  "organization": {
  "name": "hashicorp"
  },
  "workspace": {
  }
}
}

Use the Retrieve JSON Execution Plan endpoint to retrieve Terraform plan output data for testing. Refer to Terraform Run Data for the properties included in Terraform run output data.

Example Policies

The following example policy parses a Terraform plan and checks whether it includes security group updates that allow ingress traffic from all CIDRs (0.0.0.0/0).

The OPA query for this example policy is data.terraform.policies.public_ingress.deny.

package terraform.policies.public_ingress

import input.plan as plan

deny[msg] {
  r := plan.resource_changes[_]
  r.type == "aws_security_group"
  r.change.after.ingress[_].cidr_blocks[_] == "0.0.0.0/0"
  msg := sprintf("%v has 0.0.0.0/0 as allowed ingress", [r.address])
}
package terraform.policies.public_ingress

import input.plan as plan

deny[msg] {
  r := plan.resource_changes[_]
  r.type == "aws_security_group"
  r.change.after.ingress[_].cidr_blocks[_] == "0.0.0.0/0"
  msg := sprintf("%v has 0.0.0.0/0 as allowed ingress", [r.address])
}

The following example policy ensures that databases are no larger than 128 GB.

The OPA query for this policy is data.terraform.policies.fws.database.fws_db_001.rule.

package terraform.policies.fws.database.fws_db_001

import future.keywords.in
import input.plan as tfplan

actions := [
    ["no-op"],
    ["create"],
    ["update"],
]

db_size := 128

resources := [resource_changes |
    resource_changes := tfplan.resource_changes[_]
    resource_changes.type == "fakewebservices_database"
    resource_changes.mode == "managed"
    resource_changes.change.actions in actions
]

violations := [resource |
    resource := resources[_]
    not resource.change.after.size == db_size
]

violators[address] {
    address := violations[_].address
}

rule[msg] {
    count(violations) != 0
  msg := sprintf(
    "%d %q severity resource violation(s) have been detected.",
        [count(violations), rego.metadata.rule().custom.severity]
    )
}
package terraform.policies.fws.database.fws_db_001

import future.keywords.in
import input.plan as tfplan

actions := [
    ["no-op"],
    ["create"],
    ["update"],
]

db_size := 128

resources := [resource_changes |
    resource_changes := tfplan.resource_changes[_]
    resource_changes.type == "fakewebservices_database"
    resource_changes.mode == "managed"
    resource_changes.change.actions in actions
]

violations := [resource |
    resource := resources[_]
    not resource.change.after.size == db_size
]

violators[address] {
    address := violations[_].address
}

rule[msg] {
    count(violations) != 0
  msg := sprintf(
    "%d %q severity resource violation(s) have been detected.",
        [count(violations), rego.metadata.rule().custom.severity]
    )
}

Testing Policies

You can write tests for your policies by mocking the input data the policies use during Terraform runs.

The following example policy called policy1 checks whether a workspace has provider tags.

package terraform.policies.policy1

import input.plan as plan
import input.run as run

array_contains(arr, elem) {
  arr[_] = elem
}

get_basename(path) = basename{
    arr := split(path, "/")
    basename:= arr[count(arr) - 1]
}

deny[reason] {
    resource := plan.resource_changes[_]
    action := resource.change.actions[count(resource.change.actions) - 1]
    array_contains(["create", "update"], action)

    cloud_tag := get_basename(resource.provider_name)

    not run.workspace.tags[cloud_tag]

    reason := sprintf("Workspace must be marked with '%s' tag to create resources in %s cloud",
                      [cloud_tag, cloud_tag])
}
package terraform.policies.policy1

import input.plan as plan
import input.run as run

array_contains(arr, elem) {
  arr[_] = elem
}

get_basename(path) = basename{
    arr := split(path, "/")
    basename:= arr[count(arr) - 1]
}

deny[reason] {
    resource := plan.resource_changes[_]
    action := resource.change.actions[count(resource.change.actions) - 1]
    array_contains(["create", "update"], action)

    cloud_tag := get_basename(resource.provider_name)

    not run.workspace.tags[cloud_tag]

    reason := sprintf("Workspace must be marked with '%s' tag to create resources in %s cloud",
                      [cloud_tag, cloud_tag])
}

The following example shows mock JSON input data from Terraform that you could use to write tests for policy1.

{
   "mock":{
      "valid_tags":{
         "plan":{
            "resource_changes":[
               {
                  "address":"aws_instance.instance1",
                  "mode":"managed",
                  "type":"aws_instance",
                  "name":"instance1",
                  "provider_name":"registry.terraform.io/hashicorp/aws",
                  "change":{
                     "actions":[
                        "create",
                        "update"
                     ]
                  }
               },
               {
                  "address":"google_compute_instance.instance2",
                  "mode":"managed",
                  "type":"google_compute_instance",
                  "name":"instance2",
                  "provider_name":"registry.terraform.io/hashicorp/google",
                  "change":{
                     "actions":[
                        "create"
                     ]
                  }
               }
            ]
         },
         "run":{
            "workspace":{
               "tags":{
                  "aws":"",
                  "google":""
               }
            }
         }
      },
      "missing_tag":{
         "plan":{
            "resource_changes":[
               {
                  "address":"aws_instance.instance1",
                  "mode":"managed",
                  "type":"aws_instance",
                  "name":"instance1",
                  "provider_name":"registry.terraform.io/hashicorp/aws",
                  "change":{
                     "actions":[
                        "create",
                        "update"
                     ]
                  }
               },
               {
                  "address":"google_compute_instance.instance2",
                  "mode":"managed",
                  "type":"google_compute_instance",
                  "name":"instance2",
                  "provider_name":"registry.terraform.io/hashicorp/google",
                  "change":{
                     "actions":[
                        "create"
                     ]
                  }
               }
            ]
         },
         "run":{
            "workspace":{
               "tags":{
                  "google":""
               }
            }
         }
      }
   }
}
{
   "mock":{
      "valid_tags":{
         "plan":{
            "resource_changes":[
               {
                  "address":"aws_instance.instance1",
                  "mode":"managed",
                  "type":"aws_instance",
                  "name":"instance1",
                  "provider_name":"registry.terraform.io/hashicorp/aws",
                  "change":{
                     "actions":[
                        "create",
                        "update"
                     ]
                  }
               },
               {
                  "address":"google_compute_instance.instance2",
                  "mode":"managed",
                  "type":"google_compute_instance",
                  "name":"instance2",
                  "provider_name":"registry.terraform.io/hashicorp/google",
                  "change":{
                     "actions":[
                        "create"
                     ]
                  }
               }
            ]
         },
         "run":{
            "workspace":{
               "tags":{
                  "aws":"",
                  "google":""
               }
            }
         }
      },
      "missing_tag":{
         "plan":{
            "resource_changes":[
               {
                  "address":"aws_instance.instance1",
                  "mode":"managed",
                  "type":"aws_instance",
                  "name":"instance1",
                  "provider_name":"registry.terraform.io/hashicorp/aws",
                  "change":{
                     "actions":[
                        "create",
                        "update"
                     ]
                  }
               },
               {
                  "address":"google_compute_instance.instance2",
                  "mode":"managed",
                  "type":"google_compute_instance",
                  "name":"instance2",
                  "provider_name":"registry.terraform.io/hashicorp/google",
                  "change":{
                     "actions":[
                        "create"
                     ]
                  }
               }
            ]
         },
         "run":{
            "workspace":{
               "tags":{
                  "google":""
               }
            }
         }
      }
   }
}

The following test validates policy1. The test checks for valid tags and missing tags in the workspace. You can run this test with the opa test CLI command. Refer to Policy Testing in the OPA documentation for more details.

package terraform.policies.policy1

test_workspace_tags_allowed {
    result = deny with input as data.mock.valid_tags
    count(result) == 0
}

test_workspace_tags_missing {
    result = deny with input as data.mock.missing_tag
    count(result) == 1
}
package terraform.policies.policy1

test_workspace_tags_allowed {
    result = deny with input as data.mock.valid_tags
    count(result) == 0
}

test_workspace_tags_missing {
    result = deny with input as data.mock.missing_tag
    count(result) == 1
}

Terraform Run Data

Each Terraform run outputs data describing the run settings and the associated workspace.

Schema

The following code shows the schema for Terraform run data.

run
├── id (string)
├── created_at (string)
├── message (string)
├── commit_sha (string)
├── is_destroy (boolean)
├── refresh (boolean)
├── refresh_only (boolean)
├── replace_addrs (array of strings)
├── speculative (boolean)
├── target_addrs (array of strings)
├── variables (map of keys)
├── organization
│   └── name (string)
├── workspace
│   ├── id (string)
│   ├── name (string)
│   ├── created_at (string)
│   ├── description (string)
│   ├── auto_apply (bool)
│   ├── tags (array of strings)
│   ├── working_directory (string)
│   └── vcs_repo (map of keys)
run
├── id (string)
├── created_at (string)
├── message (string)
├── commit_sha (string)
├── is_destroy (boolean)
├── refresh (boolean)
├── refresh_only (boolean)
├── replace_addrs (array of strings)
├── speculative (boolean)
├── target_addrs (array of strings)
├── variables (map of keys)
├── organization
│   └── name (string)
├── workspace
│   ├── id (string)
│   ├── name (string)
│   ├── created_at (string)
│   ├── description (string)
│   ├── auto_apply (bool)
│   ├── tags (array of strings)
│   ├── working_directory (string)
│   └── vcs_repo (map of keys)

Properties

The following sections contain details about each property in Terraform run data.

Run Namespace

The following table contains the attributes for the run namespace.

Properties NameTypeDescription
idStringThe ID associated with the current Terraform run
created_atStringThe time Terraform created the run. The timestamp follows the standard timestamp format in RFC 3339.
messageStringThe message associated with the Terraform run. The default value is "Queued manually via the Terraform Enterprise API".
commit_shaStringThe checksum hash (SHA) that identifies the commit
is_destroyBooleanWhether the plan is a destroy plan that destroys all provisioned resources
refreshBooleanWhether the state refreshed prior to the plan
refresh_onlyBooleanWhether the plan is in refresh-only mode. In refresh-only mode, Terraform ignores configuration changes and updates state with any changes made outside of Terraform.
replace_addrsAn array of strings representing resource addressesThe targets specified using the -replace flag in the CLI or the replace-addrs property in the API. Undefined if there are no specified resource targets.
speculativeBooleanWhether the plan associated with the run is a speculative plan only
target_addrsAn array of strings representing resource addresses.The targets specified using the -target flag in the CLI or the target-addrs property in the API. Undefined if there are no specified resource targets.
variablesA string-keyed map of values.Provides the variables configured within the run. Each variable name maps to two properties: category and sensitive. The category property is a string indicating the variable type, either "input" or "environment". The sensitive property is a boolean, indicating whether the variable is a sensitive value.

Organization Namespace

The organization namespace has one property called name. The name property is a string that specifies the name of the Terraform Cloud organization for the run.

Workspace Namespace

The following table contains the properties for the workspace namespace.

Property NameTypeDescription
idStringThe ID associated with the Terraform workspace
nameStringThe name of the workspace, which can only include letters, numbers, -, and _
created_atStringThe time of the workspace's creation. The timestamp follows the standard timestamp format in RFC 3339.
descriptionStringThe description for the workspace. This value can be null.
auto_applyBooleanThe workspace's auto-apply setting
tagsArray of stringsThe list of tag names for the workspace
working_directoryStringThe configured Terraform working directory of the workspace. This value can be null.
vcs_repoA string-keyed map to objectsData associated with a VCS repository connected to the workspace. The map contains identifier (string), display_identifier (string), branch (string), and ingress_submodules (boolean). Refer to the Terraform Cloud Workspaces API documentation for details about each property. This value can be null.
Edit this page on GitHub

On this page

  1. Defining OPA Policies
  2. Defining Policies
  3. Testing Policies
  4. Terraform Run Data
  • Give Feedback
  • System status
  • Terms of use
  • Security
  • Privacy
  • Trademark Policy
  • Trade Controls

Defining Policies - Open Policy Agent - Terraform Cloud | Terraform | HashiCorp Developer