Terraform security measures using tfpolicy

This is Hasegawa (@rarirureluis) from the Service Reliability Group (SRG) of the Media Division.
#SRGThe Service Reliability Group primarily provides comprehensive support for the infrastructure surrounding our media services, focusing on improving existing services, launching new ones, and contributing to open-source software (OSS).
Prevent supply chain attacks with tfpolicyThere's a .terraform.lock.hcl file, isn't there?1. Terraform Policy stops the provider in HCL.Differences from Sentinel/OPA2. It can be completed locally and evaluation timing can be separated.3. Install tfpolicy3.1 Assumptions3.2 Obtaining and Placing the Binary3.3 Installation Verification4. How to use: Create separate directories for validation and testing.4.1 Directory Structure4.2 validation — Syntax verification4.3 test — Policy testing4.4 Examples of everyday commands4.5 terraform init -policies — provider Pre-download evaluationWhat will happen?Command examplePrerequisites and constraints (important)Division of roles with tfpolicy test4.6 How to Write Tests (Key Points)5. Actual output (PASS / FAIL)5.1 PASS: validate5.2 PASS: All tests passed5.3 FAIL: Policy violation (expect_failure not specified)5.4 FAIL: expect_failure, yet the policy passed.5.5 FAIL: validate (syntax error)6. Example Policy (Using Cloudflare DNS as an example)6.1 Provider Version and Official Source6.2 Operating Terms (proxied/TTL/MX)6.3 DNS SecurityAbout CIDR determination6.4 Provider Release Maturity Period (Supply Chain Attack Countermeasures)structuretest7. Enforce it on the entire team.7.1 Principle: Layer defense7.2 A+B: Make CI a required check (mandatory)7.3 C: validate / test in pre-commit7.4 D: Enforcing policies in init with terraform wrapper7.5 E: Each person's terraform init policies (toolchain assumed)7.6 F: HCP Terraform policy set (The main option after GA)7.7 Implementation ChecklistIn conclusionreference
💡
This article was generated by AI, but it has been edited and reviewed by humans.

Prevent supply chain attacks with tfpolicy


NoteThe Terraform Policy is as of the time of writing.Beta is.
tfpolicy
I will state the conclusion of this paper first.
  1. HCL remainsYou can write rules for providers/resources (without having to learn Sentinel or Rego from scratch).
  1. Then, run a mock regression test on both your local machine and your CI (Continuous Integration) machine.
  1. (After the experimental/GA release of Terraform 1.16+) Apply the same rule before the actual init download.
  1. especially "We will reject providers who have been on the market for less than 72 hours."Policies like these can serve as supply chain countermeasures.

There's a .terraform.lock.hcl file, isn't there?

.terraform.lock.hcl cannot "detect a corrupted new version during updates".
The model explicitly stated in the official documentation is Trust On First Use (TOFU).
  • During initial installation
    • The checksum of the package at that time is recorded in the lock (plus the registry-signed zh: hash is also included).
  • Subsequent versions
    • An error occurs if the downloaded binary does not match the lock hash. In other words, this prevents the following types of attacks:
scenarioCan it be prevented with a lock?
A binary of the same version that was initially trusted can be replaced later (registry breach, Man-in-the-Middle (MITM), mirror contamination).Preventable
CI / Other members use a different binaryPreventable
Install the new version using `terraform init -upgrade`.It cannot be prevented (it will simply be replaced with the hash of the new version).
The upstream is already contaminated at the time of the initial upgrade.Unpreventable

1. Terraform Policy stops the provider in HCL.


Terraform Policy is a policy-as-code provided by HashiCorp.HCLThis is how you write the code and apply conditions to the resource attributes and meta information of the Terraform provider.
tfpolicy

Differences from Sentinel/OPA

perspectiveTerraform PolicySentinelOPA
languageHCLSentinel DSLRego
Terraform integrationNative references to provider/resource/modulevia plan importParse the plan JSON
Evaluation timingBefore init/After plan/After applyplan rear centerplan rear center
Related ResourcesManual scanningManual scanning
Data sourceThis can be referenced during evaluation.Not possibleNot possible
For teams that regularly write Terraform code, the advantage is that they can code governance without having to learn a new DSL.

2. It can be completed locally and evaluation timing can be separated.


  1. core::startswith
  1. attrs.proxied
  1. tfpolicy test
  1. Multiple evaluation stages— The provider is evaluated before init, the resource after plan/apply, etc., when the necessary data is available.
  1. mandatory

3. Install tfpolicy


3.1 Assumptions

itemContent
runtimeTFC collaborationTerraformv1.16 and later(As documented).This is limited to experimental builds (§4.5)
local testThe binary can be validated/tested on its own.
Distribution formatThere is no official Homebrew formula at the time of writing.binary zipPlace
Version checkreleases.hashicorp.com/tfpolicy

3.2 Obtaining and Placing the Binary

PATH
platformExamples of archive names ()
macOS Apple Silicon
macOS Intel
Linux amd64
Linux arm64
SHA256SUMS.sig

3.3 Installation Verification

There are only three commands.
commandrole
Syntax and structure check
Execute against policy
Version display

4. How to use: Create separate directories for validation and testing.


4.1 Directory Structure

terraform/cloudflare/
  • .policy.hcl
  • policytest { targets = [...] }

4.2 validation — Syntax verification

flagexplanation
The policy file or directory. If omitted, the current directory is used.
or (When omitted)

4.3 test — Policy testing

flagexplanation
The policy path. If omitted, it defaults to the current directory.
Test pass. If omitted,Same directory
or (When omitted)
TFPOLICY_INPUT_<name>

4.4 Examples of everyday commands

From the repository root:

terraform init -policies

terraform init -policies

What will happen?

OfficialEvaluation stagesinSetupIt's a stage.
  1. terraform init -policies=<path>
  1. Terraform sets up a policy client after the backend is initialized.
  1. module_policy
  1. Provider download blocked due to policy violations
This is where supply chain policies come into play.
Policy ExamplesThings to see in setup
and
Registrymore than 72 hours ago

Command example

If you separate the configuration directory and policies,chdir and policies use relative pathsI'll hand it over.
-policies

Prerequisites and constraints (important)

itemContent
Terraform versionPolicy integration isv1.16 and later(As documented). The init built-in evaluation cannot be used with the old pin configuration.
Experimental build teeth experimental buildOnly valid in (alpha/development snapshot) versions. Not available in stable binaries.It is rejected.
local backendIt works. The HCP entitlement check is skipped if the backend cannot be supplied.
Existing cacheAlreadyThe providers listed will not be re-downloaded. When the policy is first applied, a clean init or intentionalConsider
Difference from ActionsTerraform Actions( / ) is a day-2 operation during apply.init is not a front hook.Cannot be used as a pre-download guard.

tfpolicy test

inputmockActual root module / lock / required_providers
timingAnytime (CI/On hand)During initialization / just before download
the purposePolicy regression testingStop the actual init on that machine.
Team MandatoryEveryone goes through the same gate at CI.It needs to be added to each person's CLI (§7)
-policies

4.6 How to Write Tests (Key Points)

notationmeaning
mock resource attributes
Provider/module metadata (source, version, etc.)
Policy violations occurI hope
Dependency mock. This block itself does not undergo policy evaluation.
pass

5. Actual output (PASS / FAIL)


tfpolicy

5.1 PASS: validate

5.2 PASS: All tests passed

pass

expect_failure

error_message
Test side:
output:

expect_failure

This is the case where a prediction that "should violate" was written, but it passed. It can be used for regressions where the policy is too lax or the test data is weak.
Test side:
output:

5.5 FAIL: validate (syntax error)

This is an example of validating a broken HCL.
output:

6. Example Policy (Using Cloudflare DNS as an example)


cloudflare_dns_record

6.1 Provider Version and Official Source

This requires official source code and version 5 or higher. It prevents the inclusion of forks and older major updates before initialization.
  • meta.version
  • core::semverconstraint

6.2 Operating Terms (proxied/TTL/MX)

ttl = 1
This object does not have an attribute named "proxied"

6.3 DNS Security

This is a basic form of protection: do not include private IP addresses or wildcards in your public DNS.

About CIDR determination

core::cidrcontains
plugin::network::cidr_overlaps

6.4 Provider Release Maturity Period (Supply Chain Attack Countermeasures)

Immediately after a Provider release, there's a possibility that malicious code may have been infiltrated, and the community may not have been able to detect it yet. Allowing only versions that have been released for a certain period (72 hours in this case) reduces this risk.
published_at

structure

  1. published_at
  1. core::timeadd(core::timestamp(), "-72h")
  1. core::parseint
  1. published_at

test

This policy is sent to the Registry via HTTP during evaluation. In offline environments or rate-limited CI, you need to separate the stages or isolate network-dependent policies so that they can be skipped (see also §7.3).

7. Enforce it on the entire team.


tfpolicy

7.1 Principle: Layer defense

layerWhat to stopCan it be omitted?
A. CIMain inflow of broken policy-test regressionsPR required + required check: not allowed
B. branch protectionCI remains red while mergingThis is not possible if you close the admin bypass.
C. pre-commit / local hookForgot to include validate/test before commitAvoidable (weak)
D. wrapperinit withoutAlternatively, it can be avoided with a different password (moderate).
E. (TF ≥ 1.16 exp/GA)The machine providerWithout a wrapper, it's optional (procedure-dependent).
F. HCP Terraform policy set (after GA)Remote run setup/planIf you align your workspace with TFC, it's the best.
Currently, with local backend + local apply,A+B is required.We recommend C+D, and realistically, we'll wait for the toolchain and GA for E/F.

7.2 A+B: Make CI a required check (mandatory)

Having a workflow alone only means that "you can merge even if it fails."
With GitHub branch protection (or ruleset):
  1. master
  1. Require status checks to passAdd tfpolicy job to
  1. Require a pull request before merging(direct push prohibited)
  1. If possibleDo not allow bypassing the above settings(Even admins are not allowed)
terraform init

7.3 C: validate / test in pre-commit

pre-commit install
NoteContent
This is required in PATHIf it's missing, the hook will fail. Write `install` in onboarding.
This can be avoided.The coercive force is weaker than CI. Auxiliary layer
networkThis will appear in the Registry. Offline development requires either separating stages or implementing a skip design.

policies

-policies

terraform init -policies

Conditions for making the commands in §4.5 team standards:
  1. policies
  1. policies=...
  1. Run the same init process in CI (PIN install experimental TF).
While the cost of distributing experimental builds to everyone is high,Increase the thickness of A+B+C, and make E the main line after GA.It's safer to do that.

7.6 F: HCP Terraform policy set (The main option after GA)

  1. terraform/policy/
  1. Create a Terraform policy set in HCP and connect to VCS.
  1. Attach to target workspace
  1. The provider policy during the setup phaseEquivalent to the init for remote runIt will be evaluated, and if a violation occurs, it will be stopped before the download.
This is the only form that doesn't depend on "someone else's manual procedure." During the beta phase, it will only cover connection verification; the mandatory block will be available after the general availability (GA) release.

7.7 Implementation Checklist

#work
1Set tfpolicy's CI to require status check.
2Direct push is prohibited, only pull requests are allowed.
3onboardinginstall + write everyday commands
4(Optional) Add §7.3 to pre-commit
5(Optional) §7.4 Share wrapper
6Make §4.5 the standard init after TF 1.16+ experimental build or GA.
7HCP policy set after GA

In conclusion


tfpolicy can stop the "moment of dropping a new provider" that .terraform.lock.hcl alone cannot, before the download occurs while keeping it as an HCL. The practical approach described in this article is to make validation/tests mandatory locally, the required checks in CI at the team level, and block the local init with a wrapper or an HCP policy set after GA.
Because Terraform (infrastructure) operates at a lower layer than the application, any omissions in configuration or review have a significant impact.
I've decided that when introducing new things using Terraform in the future, I'll try a workflow where I first define the policy before implementing it.

reference



If you are interested in SRG, please contact us here.