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.
tfpolicyI will state the conclusion of this paper first.
- HCL remainsYou can write rules for providers/resources (without having to learn Sentinel or Rego from scratch).
- Then, run a mock regression test on both your local machine and your CI (Continuous Integration) machine.
- (After the experimental/GA release of Terraform 1.16+) Apply the same rule before the actual init download.
- 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:
| scenario | Can 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 binary | Preventable |
| 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.
tfpolicyDifferences from Sentinel/OPA
| perspective | Terraform Policy | Sentinel | OPA |
|---|---|---|---|
| language | HCL | Sentinel DSL | Rego |
| Terraform integration | Native references to provider/resource/module | via plan import | Parse the plan JSON |
| Evaluation timing | Before init/After plan/After apply | plan rear center | plan rear center |
| Related Resources | Manual scanning | Manual scanning | |
| Data source | This can be referenced during evaluation. | Not possible | Not 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.
core::startswith
attrs.proxied
tfpolicy test
- Multiple evaluation stages— The provider is evaluated before init, the resource after plan/apply, etc., when the necessary data is available.
mandatory
3. Install tfpolicy
Official:Install the Terraform policy CLI
3.1 Assumptions
| item | Content |
|---|---|
| runtime | TFC collaborationTerraformv1.16 and later(As documented).This is limited to experimental builds (§4.5) |
| local test | The binary can be validated/tested on its own. |
| Distribution format | There is no official Homebrew formula at the time of writing.binary zipPlace |
| Version check | releases.hashicorp.com/tfpolicy |
3.2 Obtaining and Placing the Binary
PATH| platform | Examples of archive names () |
|---|---|
| macOS Apple Silicon | |
| macOS Intel | |
| Linux amd64 | |
| Linux arm64 |
SHA256SUMS.sig3.3 Installation Verification
There are only three commands.
| command | role |
|---|---|
| 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
| flag | explanation |
|---|---|
| The policy file or directory. If omitted, the current directory is used. | |
| or (When omitted)) |
4.3 test — Policy testing
| flag | explanation |
|---|---|
| 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 -policiesWhat will happen?
terraform init -policies=<path>
- Terraform sets up a policy client after the backend is initialized.
module_policy
Provider download blocked due to policy violations
This is where supply chain policies come into play.
| Policy Examples | Things 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.
-policiesPrerequisites and constraints (important)
| item | Content |
|---|---|
| Terraform version | Policy 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 backend | It works. The HCP entitlement check is skipped if the backend cannot be supplied. |
| Existing cache | AlreadyThe providers listed will not be re-downloaded. When the policy is first applied, a clean init or intentionalConsider |
| Difference from Actions | Terraform Actions( / ) is a day-2 operation during apply.init is not a front hook.Cannot be used as a pre-download guard. |
tfpolicy test
| input | mock | Actual root module / lock / required_providers |
| timing | Anytime (CI/On hand) | During initialization / just before download |
| the purpose | Policy regression testing | Stop the actual init on that machine. |
| Team Mandatory | Everyone goes through the same gate at CI. | It needs to be added to each person's CLI (§7) |
-policies4.6 How to Write Tests (Key Points)
| notation | meaning |
|---|---|
| mock resource attributes | |
| Provider/module metadata (source, version, etc.) | |
| Policy violations occurI hope | |
| Dependency mock. This block itself does not undergo policy evaluation. |
pass5. Actual output (PASS / FAIL)
tfpolicy5.1 PASS: validate
5.2 PASS: All tests passed
passexpect_failure
error_messageTest 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_record6.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 = 1This 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::cidrcontainsplugin::network::cidr_overlaps6.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_atstructure
published_at
core::timeadd(core::timestamp(), "-72h")
core::parseint
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.
tfpolicy7.1 Principle: Layer defense
| layer | What to stop | Can it be omitted? |
|---|---|---|
| A. CI | Main inflow of broken policy-test regressions | PR required + required check: not allowed |
| B. branch protection | CI remains red while merging | This is not possible if you close the admin bypass. |
| C. pre-commit / local hook | Forgot to include validate/test before commit | Avoidable (weak) |
| D. wrapper | init without | Alternatively, it can be avoided with a different password (moderate). |
| E. (TF ≥ 1.16 exp/GA) | The machine provider | Without a wrapper, it's optional (procedure-dependent). |
| F. HCP Terraform policy set (after GA) | Remote run setup/plan | If 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):
master
- Require status checks to passAdd tfpolicy job to
- Require a pull request before merging(direct push prohibited)
- If possibleDo not allow bypassing the above settings(Even admins are not allowed)
terraform init7.3 C: validate / test in pre-commit
pre-commit install| Note | Content |
|---|---|
| This is required in PATH | If it's missing, the hook will fail. Write `install` in onboarding. |
| This can be avoided. | The coercive force is weaker than CI. Auxiliary layer |
| network | This will appear in the Registry. Offline development requires either separating stages or implementing a skip design. |
policies
-policiesterraform init -policies
Conditions for making the commands in §4.5 team standards:
policies
policies=...
- 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)
terraform/policy/
- Create a Terraform policy set in HCP and connect to VCS.
- Attach to target workspace
- 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 |
|---|---|
| 1 | Set tfpolicy's CI to require status check. |
| 2 | Direct push is prohibited, only pull requests are allowed. |
| 3 | onboardinginstall + write everyday commands |
| 4 | (Optional) Add §7.3 to pre-commit |
| 5 | (Optional) §7.4 Share wrapper |
| 6 | Make §4.5 the standard init after TF 1.16+ experimental build or GA. |
| 7 | HCP 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.
