Skip to main content

Terraform Policy Authoring

Policy authoring with Terraform systems allows you to write Rego rules which are evaluated against the contents of the Terraform plan JSON file. This page assumes you are familiar with the OPA concepts of packages and the rules they contain, as well as the Styra DAS Policy Lifecycle, the Terraform Resource Provisioning Workflow, and the Terraform JSON Output Format.

Terraform plans enable you to see the changes Terraform will make to cloud resources to bring them to desired state declared in configuration files. The Rego policies you define in Styra DAS determine whether the changes in the plan are safe to make and align with your organization's requirements. The basic rules you write are partial-set rules that either warn on or reject a request. The Terraform system type supports the following entry points for policy evaluation: enforce and deny to reject a Terraform resource change, and monitor and warn to provide a warning about a Terraform resource change.

Each of the partial sets contain a decision object which at minimum includes the message field. Styra-built Terraform Policy Library rules also return an additional metadata field in the decision object, which is detailed below. The decision object can also include custom user-defined fields by editing the Rego code directly. Styra DAS will always add "allowed": false to the returned decision object if not explicitly defined.

note

Terraform policy rules which return evaluation decisions must be defined in policy packages which follow the naming format package policy.<provider>.<resource>. See the Policy Structure section below for additional details.

For more information on writing custom Rego policies, see the Styra Academy or the Open Policy Agent policy language documentation.

Rule Formats

The following rule formats are supported when defining policies for a Terraform system.

Enforce - Results in a policy failure.

enforce[decision] {
...
decision := {
"allowed": false,
"message": "Message explaining why there is a problem",
"metadata": {}
}
}

Monitor - Results in a policy warning.

monitor[decision] {
...
decision := {
"allowed": false,
"message": "Message explaining why there is a problem",
"metadata": {}
}
}

The optional metadata object above is intended to provide additional context for each Terraform rule violation, including the context of the rule itself and the context of the resource resulting in the violation. The Styra DAS pre-built Terraform Policy Library Rules include this metadata object with populated resource and rule objects (KICS library rules do not currently include the full populated contents of the metadata object). Below is an example of the metadata object for the AWS: EC2: Restrict instances with unapproved AMIs rule.

{
"resource": {
"module": "root_module",
"type": "aws_instance",
"address": "aws_instance.example_instance",
"name": "example_instance",
"action": "create",
"context": {
"ami": "ami-0022c769"
}
},
"rule": {
"id": "aws.ec2.whitelist_ami",
"title": "AWS: EC2: Restrict instances with unapproved AMIs",
"severity": "high",
"resource_category": "",
"control_category": "",
"rule_link": "https://docs.styra.com/systems/terraform/snippets",
"compliance_pack": null,
"description": "Require EC2 instances to use an AMI from a pre-approved list",
"impact": "Running an unapproved AMI may result in using vulnerable images",
"remediation": "Launch instances using only AMI images defined in the pre-approved list",
"platform": "terraform",
"provider": "aws",
"rule_targets": [
{
"scope": "resource",
"service": "ec2",
"name": "instance",
"identifier": "aws_instance",
"argument": "ami"
},
{
"scope": "resource",
"service": "ec2",
"name": "launch_template",
"identifier": "aws_launch_template",
"argument": "image_id"
},
{
"scope": "resource",
"service": "autoscaling_group",
"name": "launch_configuration",
"identifier": "aws_launch_configuration",
"argument": "image_id"
}
],
"parameters": {
"allowed_ami_ids": ["ami-830c94e3"]
}
}
}

The metadata.resource object fields are defined as:

  • address: the full resource address
  • module: resource module
  • type: resource type
  • name: resource name
  • action: actions to be taken by Terraform on the resource
  • context: resource values contributing to the violation

The metadata.rule object fields are defined as:

  • id: unique identifier for the rule
  • title: rule title
  • description: rule details
  • rule_link: link to the rule in the Styra Docs
  • severity: rule severity, either low, medium, high, or critical
  • impact: explanation of rule violation impact
  • remediation: instructions to remediate rule violations
  • resource_category: categorization of the Terraform resource type
  • control_category: categorization of the rule
  • compliance_pack: rule association with compliance packs, if any
  • platform: returns terraform for all Terraform rules
  • provider: associated Terraform provider for the rule
  • rule_targets: the Terraform objects evaluated by the rule
  • parameters: user-specified rule configuration parameters, if any
note

The metadata object in the returned decision is only available in the Styra DAS Terraform system type v2. The resource_category, control_category, compliance_pack, impact, remediation, and rule_targets fields may be null or blank as rules in the Styra DAS policy library get updated with this additional data in upcoming updates.

Additional Supported Formats

For compatibility reasons, the following rule formats are also supported, though we recommend you define rules using the formats in the Rule Formats section above.

Deny - Results in a policy failure.

deny[decision] {
...
decision := {
"message": "Message explaining why there is a problem"
}
}

Warn - Results in a policy warning.

warn[decision] {
...
decision := {
"message": "Message explaining why there is a problem"
}
}

Policy Package and Module Structure

Styra DAS Terraform system policies are organized based on the provider and the resources/services within that provider (i.e., package policy.<provider>.<resource>) to mirror the provider >> resource structure of Terraform. This structure also helps organize your policy rules according to the resources/services they impact. For example, policy rules relating to AWS S3 buckets should reside within modules in the policy.aws.s3 package. You can create multiple policy modules within each package for further organization. For example, the policy.aws.s3 package could include encryption.rego, versioning.rego, and replication.rego.

While policy rules may be placed at any level within the policy hierarchy (e.g., a policy.utils package with helpers), only the enforce/deny and monitor/warn rules in modules following the package policy.<provider>.<resource> hierarchy will be applied to Terraform plans.

For example, the enforce rule in the first example policy below will be applied to Terraform plans while the following two monitor and enforce rules will not as they reside in packages which do not follow the policy.<provider>.<resource> hierarchy.

package policy.myprovider.myresouce

# This enforce rule will be applied to Terraform plan evaluation
enforce[decision] {
...
}
package policy.myprovider

# This monitor rule will not be applied to Terraform plan evaluation
monitor[decision] {
...
}
package policy.myprovider.myresouce.utils

# This enforce rule will not be applied to Terraform plan evaluation
enforce[decision] {
...
}

The structure of policies within Styra DAS Stacks is the same as the structure of policies within Styra DAS Systems. Rules you add to packages following the stacks.<stackID>.policy.<provider>.<resource> hierarchy at the stack level are applied to Terraform plans of associated Terraform systems.

The names of provider and resource in the package path may be any values useful to you to organize your rules (e.g., policy.foo.bar), however, the aws, azure, gcp, and kubernetes provider names are meaningful in the Styra DAS UI as they enable displaying rules associated with those providers from Styra's Terraform Policy Library within the policy builder UI (accessible via the Add rule button in the policy editor).

Default Terraform Policy Packages and Modules

Upon creation of a new Styra DAS Terraform system, three policy packages and modules are automatically added to your system to get you started and to provide a policy organization guide. These default packages and modules are:

  • package policy.aws.ec2 with rules.rego located in the policy >> aws >> ec2 sub-folder.
  • package policy.azure.vms with rules.rego located in the policy >> azure >> vms sub-folder.
  • package policy.gcp.compute with rules.rego located in the policy >> gcp >> compute sub-folder.
  • package policy.kubernetes.cluster with rules.rego located in the policy >> kubernetes >> cluster sub-folder.

When editing these policy modules in Styra DAS, usage of Styra's Terraform Policy library is enabled. You can add rules from the policy library by clicking the Add rule button when editing policies.

Add additional modules to the default policy packages by clicking on the options menu on the policy.<provider> folder and selecting Add Policy. Define a new or existing package path (e.g., kms if adding KMS policies to the policy.aws package) and define a module name.

If not needed for your use case, delete any default packages and modules. If needed in the future, manually add them using Add Policy.

Custom Terraform Policy Packages and Modules

Create custom policy packages and modules to meet your specific Terraform use cases, whether for Terraform providers other than AWS, Azure, Google Cloud, or Kubernetes, or for policies relating to Terraform Cloud or Terraform Enterprise runs.

For example, for Terraform systems integrated with Terraform Cloud/Enterprise, create a policy which prevents Friday deploys via Terraform Cloud/Enterprise by defining rules in a policy.tfc.runs package:

package policy.tfc.runs

enforce[decision] {
message := "No deployments allowed on Fridays"

time.weekday(time.now_ns()) == "Friday"

decision := {
"allowed": false,
"message": message
}
}

Terraform Policy Tests and Data Sources

Test policies can be placed anywhere within a Styra DAS Terraform system, however, Styra typically recommends you put them in separate packages so that they are not downloaded by the OPA agent as part of the policy bundle, helping to reduce the bundle size.

Data sources can also be added to your Terraform system. Data sources provide a dedicated location for injecting external data which can be used as part of policy evaluation (e.g., an LDAP data source with user roles and permissions).

note

Data sources cannot be placed at a path which is a prefix of a policy package path or another data source path.

Styra's Terraform Policy Library

Styra's growing Terraform Policy Library includes pre-built rules for the AWS, Azure, Google Cloud, and Kubernetes Terraform providers to help organizations follow IaC best practices. The Terraform Policy Library includes rules built, tested, and managed by Styra as well as rules from the open-source KICS project. Rules from the Terraform Policy Library can be added via the Styra DAS policy builder UI by clicking the Add rule button when editing a policy module with a package prefix of policy.<provider>, where <provider> is aws, azure, gcp, or kubernetes. Library rules are added to your policy in the monitor state, allowing you to review, preview, and validate the rule against past decisions before enforcing it.

As an example, a pre-built rule which does not require any configuration options, such as requiring S3 buckets to be encrypted, would be added to your policy file as a snippet like so:

monitor[decision] {
data.global.systemtypes["terraform:2.0"].library.provider.aws.s3.unencrypted.v1.unencrypted_s3_bucket[violation]
decision := {
"allowed": false,
"message": violation.message,
"metadata": violation.metadata
}
}

The list of current pre-built Terraform rules can be found on the Terraform Policy Library Rules page.

Rule Snippet Filtering

In the Styra DAS policy builder UI, you can further customize pre-built rules from the Terraform Policy Library by using the actions filter on a rule snippet to specify which Terraform resource actions the rule applies to. This actions filter supports the following resource actions:

  • create: applies to new resources which will be created when the Terraform plan is applied
  • update: applies to existing resources with configuration changes
  • replace: applies to existing resources with configuration changes which require the resource to be re-created (i.e., delete and create or create and delete)
  • delete: applies to existing resources being removed
  • no-op: applies to existing resources which have no planned changes
  • read: applies to data sources

To add or remove the actions filter on a rule snippet, click the Filter icon (three-line inverted triangle) located at the top-right of the rule snippet UI card and then select the applicable resource actions for the rule. This filter also support negation.

note

Styra DAS Terraform Policy Library rules from the open-source KICS project do not currently support the actions filter, as these rules do not return the full resource object with the resource actions.

Terraform Rule Exemptions

The Styra DAS Terraform system type supports defining Terraform resources to be exempted from returning policy violations for specific rules across Terraform code, plan, and state policy evaluation. Rule exemptions do not contribute to the Allowed or Denied decision for a code or plan input nor to state Compliance violations. In decisions with exemptions, the individual exempted rule violations are reported in the decision's result.outcome.decisions.exemption parameter for traceability.

To define exemptions, create a Styra DAS system data source of type S3 data import, GCS data import, Git data import, HTTPS, or JSON with the path exemptions and name exemptions.json. In most cases, exemptions are best managed in git and imported into a system using a Git for data import data source to allow teams to use a PR and code review process to manage resource exemptions from rules.

The data source contents must be valid JSON following the format:

{
"rules": {
"<rule.id>": {
"targets": {
"<resource.address>": {
"comment": "Exemption justification",
"expires": "2023-12-01T12:00:00Z"
}
}
}
}
}

Exemption parameters include:

  • <rule.id>: the custom.id metadata parameter value defined on all Styra DAS Terraform Policy Library rules, which is also returned by library rules as part of the rule decision metadata
  • <resource.address>: the full address of a resource to be exempted from the rule
  • comment: (optional) can be used to provide details about the exemption for the associated resource
  • expires: (optional) accepts a timestamp string in RFC3339 format (e.g., "2023-12-01T12:00:00Z" for 2023/12/01 at 12pm UTC) representing when the exemption expires and Styra DAS will no longer exempt the resource from the rule

Example exemptions.json data source contents:

{
"rules": {
"aws.s3.logging_enabled": {
"targets": {
"aws_s3_bucket.example_bucket_1": {
"comment": "This bucket is a test bucket and doesn't require logging until December 2023.",
"expires": "2023-12-01T12:00:00Z"
},
"module.app_object_storage.aws_s3_bucket.example_bucket_2": {
"comment": ""
}
}
},
"aws.s3.versioning_enabled": {
"targets": {
"aws_s3_bucket.example_bucket_1": {
"comment": "This bucket is a test bucket and doesn't require versioning until January 2024.",
"expires": "2024-01-01T12:00:00-05:00"
}
}
}
}
}
note

Styra DAS Terraform Policy Library rules from the open-source KICS project do not currently support the rule exemptions, as these rules do not return the full resource object with the full resource address.