Before you begin
Constraint Framework
gcloud beta terraform vet
uses Constraint Framework
policies, which consist of constraints and constraint templates. The
difference between the two is as follows:
- A constraint template is like a function declaration; it defines a rule and optionally takes variables as inputs.
- A constraint is a file that references a constraint template and defines the values to use with it.
Create a constraint template
1. Collect sample data
In order to write a constraint template, you need to have sample data to operate
on. Terraform-based constraints operate on resource change data, which comes
from the resource_changes
key of Terraform plan JSON.
For example, your JSON might look like this:
// tfplan.json
{
"format_version": "0.2",
"terraform_version": "1.0.10",
"resource_changes": [
{
"address": "google_compute_address.internal_with_subnet_and_address",
"mode": "managed",
"type": "google_compute_address",
"name": "internal_with_subnet_and_address",
"provider_name": "registry.terraform.io/hashicorp/google",
"change": {
"actions": [
"create"
],
"before": null,
"after": {
"address": "10.0.42.42",
"address_type": "INTERNAL",
"description": null,
"name": "my-internal-address",
"network": null,
"prefix_length": null,
"region": "us-central1",
"timeouts": null
},
"after_unknown": {
"creation_timestamp": true,
"id": true,
"network_tier": true,
"project": true,
"purpose": true,
"self_link": true,
"subnetwork": true,
"users": true
},
"before_sensitive": false,
"after_sensitive": {
"users": []
}
}
}
],
// other data
}
2. Write Rego
After you have sample data, you can write the logic for your constraint template
in Rego.
Your Rego must have a violations
rule. The resource change being reviewed is
available as input.review
. Constraint parameters are available as
input.parameters
. For example, to require that google_compute_address
resources have an allowed address_type
, write:
# validator/tf_compute_address_address_type_allowlist_constraint_v1.rego
package templates.gcp.TFComputeAddressAddressTypeAllowlistConstraintV1
violation[{
"msg": message,
"details": metadata,
}] {
resource := input.review
resource.type == "google_compute_address"
allowed_address_types := input.parameters.allowed_address_types
count({resource.after.address_type} & allowed_address_types) >= 1
message := sprintf(
"Compute address %s has a disallowed address_type: %s",
[resource.address, resource.after.address_type]
)
metadata := {"resource": resource.name}
}
Name your constraint template
The previous example uses the name TFComputeAddressAddressTypeAllowlistConstraintV1
.
This is a unique identifier for each constraint template. We recommend following
these naming guidelines:
- General format:
TF{resource}{feature}Constraint{version}
. Use CamelCase. (In other words, capitalize each new word.) - For single-resource constraints, follow the Terraform provider's
conventions for product naming. For example, for
google_tags_tag
the product name istags
even theough the API name isresourcemanager
. - If a template applies to more than one type of resource, omit the resource part and only include the feature (example: "TFAddressTypeAllowlistConstraintV1").
- The version number does not follow semver form; it is just a single number. This effectively makes every version of a template an unique template.
We recommend using a name for your rego file that matches the constraint
template name, but using snake_case. In other words, convert the name to
lowercase separate words with _
. For the previous example, the recommended
filename is tf_compute_address_address_type_allowlist_constraint_v1.rego
3. Test your Rego
You can test your Rego manually with the Rego Playground. Make sure to use non-sensitive data.
We recommend writing automated tests.
Put your collected sample data in validator/test/fixtures/<constraint filename>/resource_changes/data.json
and reference it in your test file like this:
# validator/tf_compute_address_address_type_allowlist_constraint_v1_test.rego
package templates.gcp.TFComputeAddressAddressTypeAllowlistConstraintV1
import data.test.fixtures.tf_compute_address_address_type_allowlist_constraint_v1_test.resource_changes as resource_changes
test_violation_with_disallowed_address_type {
parameters := {
"allowed_address_types": "EXTERNAL"
}
violations := violation with input.review as resource_changes[_]
with input.parameters as parameters
count(violations) == 1
}
Place your rego and your test in the validator
folder in your policy library.
4. Set up a constraint template skeleton
After you have a working and tested Rego rule, you must package it as a constraint template. Constraint Framework uses Kubernetes Custom Resource Definitions as the container for the policy Rego.
The constraint template also defines what parameters are allowed as inputs from constraints, using the OpenAPI V3 schema.
Use the same name for the skeleton as you used for your rego. In particular:
- Use the same filename as for your rego. Example:
tf_compute_address_address_type_allowlist_constraint_v1.yaml
spec.crd.spec.names.kind
must contain the template namemetadata.name
must contain the template name, but lower-cased
Place the constraint template skeleton in policies/templates
.
For the example above:
# policies/templates/tf_compute_address_address_type_allowlist_constraint_v1.yaml
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
name: tfcomputeaddressaddresstypeallowlistconstraintv1
spec:
crd:
spec:
names:
kind: TFComputeAddressAddressTypeAllowlistConstraintV1
validation:
openAPIV3Schema:
properties:
allowed_address_types:
description: "A list of address_types allowed, for example: ['INTERNAL']"
type: array
items:
type: string
targets:
- target: validation.resourcechange.terraform.cloud.google.com
rego: |
#INLINE("validator/tf_compute_address_address_type_allowlist_constraint_v1.rego")
#ENDINLINE
5. Inline your rego
At this point, following the previous example, your directory layout looks like this:
| policy-library/
|- validator/
||- tf_compute_address_address_type_allowlist_constraint_v1.rego
||- tf_compute_address_address_type_allowlist_constraint_v1_test.rego
|- policies
||- templates
|||- tf_compute_address_address_type_allowlist_constraint_v1.yaml
If you cloned the Google-provided policy-library repository,
you can run make build
to automatically update your constraint templates in
policies/templates
with the rego defined in validator
.
6. Set up a constraint
Constraints contain three pieces of information that gcloud beta terraform vet
needs
to properly enforce and report violations:
severity
:low
,medium
, orhigh
match
: parameters for determining if a constraint applies to a particular resource. The following match parameters are supported:addresses
: A list of resource addresses to include using glob-style matchingexcludedAddresses
: (Optional) A list of resource addresses to exclude using glob-style matching.
parameters
: Values for the constraint template's input parameters.
Make sure that kind
contains the constraint template name. We recommend setting
metadata.name
to a descriptive slug.
For example, to only allow INTERNAL
address types using the previous example
constraint template, write:
# policies/constraints/tf_compute_address_internal_only.yaml
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: TFComputeAddressAddressTypeAllowlistConstraintV1
metadata:
name: tf_compute_address_internal_only
spec:
severity: high
match:
addresses:
- "**"
parameters:
allowed_address_types:
- "INTERNAL"
Matching examples:
Address matcher | Description |
---|---|
`module.**` | All resources in any module |
`module.my_module.**` | Everything in module `my_module` |
`**.google_compute_global_forwarding_rule.*` | All google_compute_global_forwarding_rule resources in any module |
`module.my_module.google_compute_global_forwarding_rule.*` | All google_compute_global_forwarding_rule resources in `my_module` |
If a resource address matches values in addresses
and excludedAddresses
, it
is excluded.
Limitations
Terraform plan data gives the best available representation of actual state after apply; however, in many cases the state after apply may not be known because it is calculated on the server side.
Supported resources
You can create resource change constraints for any resources from Terraform's google and google-beta providers.