PS Product SecurityKnowledge Base

Terraform Snippet Pack

Intro: Terraform security lives in both the HCL and the surrounding workflow. This page focuses on reusable snippet patterns for state, provider pinning, role assumption, module discipline, and plan/apply separation.

What this page includes

  • remote state and locking examples
  • provider version and dependency lock patterns
  • IAM-assume-role provider snippets and CI pipeline usage
  • policy checks and review-oriented commands

Working assumptions

  • infrastructure code should be reproducible, reviewable, and environment-specific without copy-paste sprawl

1. Remote state in S3 with explicit locking options

terraform {
  required_version = ">= 1.8.0"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.80"
    }
  }

  backend "s3" {
    bucket       = "org-terraform-state-prod"
    key          = "product-api/prod/terraform.tfstate"
    region       = "us-east-1"
    encrypt      = true
    use_lockfile = true
    # Older setups may still use DynamoDB locking, but HashiCorp documents it as deprecated.
    dynamodb_table = "terraform-locks"
  }
}

2. Provider configuration with assume role

provider "aws" {
  region = var.aws_region

  assume_role {
    role_arn     = "arn:aws:iam::123456789012:role/terraform-prod-apply"
    session_name = "terraform-${terraform.workspace}"
  }

  default_tags {
    tags = {
      managed_by = "terraform"
      owner      = "platform"
      repo       = "product-api-infra"
    }
  }
}

3. Commit the dependency lock file

Example .terraform.lock.hcl excerpt:

provider "registry.terraform.io/hashicorp/aws" {
  version     = "5.80.0"
  constraints = "~> 5.80"
  hashes = [
    "h1:examplehash",
    "zh:anotherexamplehash"
  ]
}

Treat lock file changes like dependency changes in application code: review them and commit them intentionally.

4. Separate plan and apply in GitLab

terraform_plan:
  stage: build
  image: hashicorp/terraform:1.9
  script:
    - terraform init -input=false
    - terraform validate
    - terraform plan -out=tfplan
  artifacts:
    paths: [tfplan, .terraform.lock.hcl]

terraform_apply:
  stage: deploy_staging
  image: hashicorp/terraform:1.9
  needs: [terraform_plan]
  when: manual
  script:
    - terraform init -input=false
    - terraform apply -input=false tfplan

Keep apply on protected refs or protected environments only.

5. Module source pinning

module "vpc" {
  source = "git::https://gitlab.example.com/platform/tf-modules.git//aws/vpc?ref=v2.4.1"

  name            = "product-api-prod"
  cidr_block      = "10.40.0.0/16"
  private_subnets = ["10.40.1.0/24", "10.40.2.0/24"]
}

Pin module versions. Avoid floating branches for production infrastructure.

6. Sensitive outputs and state awareness

output "db_password" {
  value     = aws_secretsmanager_secret_version.db_password.secret_string
  sensitive = true
}

Marking an output as sensitive does not mean the value disappears from state. Protect the backend, the access path, and the pipeline role.

7. Workspace guardrail

#!/usr/bin/env bash
set -euo pipefail

expected_workspace="prod"
current_workspace=$(terraform workspace show)

if [ "$current_workspace" != "$expected_workspace" ]; then
  echo "Refusing to apply: expected workspace '$expected_workspace', got '$current_workspace'"
  exit 1
fi

8. Static checks in CI

terraform_security_checks:
  stage: sast
  image: aquasec/tfsec:latest
  script:
    - tfsec . --format json --out tfsec.json
  artifacts:
    when: always
    paths: [tfsec.json]

And with Checkov:

checkov:
  stage: sast
  image: bridgecrew/checkov:latest
  script:
    - checkov -d . -o json > checkov.json
  artifacts:
    when: always
    paths: [checkov.json]

9. Provider mirror and plugin policy idea

provider_installation {
  filesystem_mirror {
    path    = "/terraform-providers"
    include = ["registry.terraform.io/hashicorp/*"]
  }
  direct {
    exclude = ["registry.terraform.io/hashicorp/*"]
  }
}

Use when you need tighter control over how providers are sourced in regulated or offline-adjacent environments.

10. Review checklist

  • remote state is encrypted and access-controlled;
  • plan and apply are separated;
  • provider versions are constrained;
  • .terraform.lock.hcl is reviewed and committed;
  • module sources are pinned;
  • CI role is narrower than human admin role;
  • state access is treated as secret access.

Author attribution: Ivan Piskunov, 2026 - Educational and defensive-engineering use.