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.
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 addressmodule
: resource moduletype
: resource typename
: resource nameaction
: actions to be taken by Terraform on the resourcecontext
: resource values contributing to the violation
The metadata.rule
object fields are defined as:
id
: unique identifier for the ruletitle
: rule titledescription
: rule detailsrule_link
: link to the rule in the Styra Docsseverity
: rule severity, either low, medium, high, or criticalimpact
: explanation of rule violation impactremediation
: instructions to remediate rule violationsresource_category
: categorization of the Terraform resource typecontrol_category
: categorization of the rulecompliance_pack
: rule association with compliance packs, if anyplatform
: returnsterraform
for all Terraform rulesprovider
: associated Terraform provider for the rulerule_targets
: the Terraform objects evaluated by the ruleparameters
: user-specified rule configuration parameters, if any
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
withrules.rego
located in the policy >> aws >> ec2 sub-folder.package policy.azure.vms
withrules.rego
located in the policy >> azure >> vms sub-folder.package policy.gcp.compute
withrules.rego
located in the policy >> gcp >> compute sub-folder.package policy.kubernetes.cluster
withrules.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).
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.
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>
: thecustom.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 rulecomment
: (optional) can be used to provide details about the exemption for the associated resourceexpires
: (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"
}
}
}
}
}
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.