
Infrastructure as Code (IaC) has become central to modern cloud operations, with Terraform and OpenTofu leading the charge in defining and provisioning cloud infrastructure. However, as IaC defines your infrastructure, securing that code becomes paramount. Recent security research indicates that 63% of cloud security incidents stem from misconfigurations rather than sophisticated attacks, making the choice of security tooling and practices a critical decision.
This comprehensive pillar article synthesizes best practices, threat models, tools, and implementation strategies for securing Terraform and OpenTofu deployments. Whether managing AWS, Azure, Google Cloud, or multi-cloud environments, this guide provides the knowledge needed to embed security throughout your IaC lifecycle.
Modern IaC security operates on the principle of "shift-left"—catching issues as early as possible in the development lifecycle rather than discovering them in production. This requires a multi-layered approach that addresses security at multiple stages:
Development Time: Developers identify and prevent security issues in their IDEs before code commits.
Pre-Deployment: Automated scanning in CI/CD pipelines catches misconfigurations before infrastructure changes are applied.
Runtime: Continuous monitoring and compliance validation ensure deployed infrastructure remains secure and drift-free.
Operational: Policy enforcement and compliance reporting maintain security standards across environments.
Two primary methods exist for IaC security analysis:
Static Analysis (SAST) examines your HCL code before deployment:
Dynamic Analysis & Runtime Validation (DAST/CSPM) assesses infrastructure after deployment:
Best Practice: These methods are complementary, not mutually exclusive. SAST acts as preventative control, while runtime validation ensures ongoing security and detects unauthorized changes.
Understanding the threats your IaC faces is essential for designing effective controls:
Misconfiguration Exposure
Secrets Exposure
State File Compromise
Supply Chain Attacks
Access Control Weaknesses
Drift and Compliance Violations
Terraform state files represent the source of truth for your infrastructure. Compromising a state file compromises your entire infrastructure, making state security non-negotiable.
1. Enable Encryption at Rest
# Using AWS S3 for state backend with encryption
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "prod/terraform.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "terraform-locks"
}
}2. Enable Encryption in Transit
# Force HTTPS for all communication
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "prod/terraform.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "terraform-locks"
skip_credentials_validation = false
skip_metadata_api_check = false
skip_requesting_account_id = false
}
}3. Implement State Locking
# Use DynamoDB for distributed state locking
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "prod/terraform.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "terraform-locks"
}
}State locking prevents concurrent operations from corrupting state and is essential in team environments.
4. Restrict Access to State Storage
# AWS S3 bucket policy for state file access
resource "aws_s3_bucket_policy" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Deny"
Principal = "*"
Action = "s3:*"
Resource = [
aws_s3_bucket.terraform_state.arn,
"${aws_s3_bucket.terraform_state.arn}/*"
]
Condition = {
Bool = {
"aws:SecureTransport" = "false"
}
}
}
]
})
}5. Enable Versioning and MFA Delete
# Enable S3 versioning and MFA delete protection
resource "aws_s3_bucket_versioning" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
versioning_configuration {
status = "Enabled"
mfa_delete = "Enabled"
}
}6. Never Store State in Git
Always use remote backends (S3, Azure Storage, Terraform Cloud) rather than committing state files to version control. Add to .gitignore:
terraform.tfstate
terraform.tfstate.*
.terraform/
7. Audit State Access
# Enable CloudTrail logging for state access
resource "aws_s3_bucket_logging" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
target_bucket = aws_s3_bucket.log_bucket.id
target_prefix = "terraform-state-logs/"
}Terraform must handle sensitive information—database passwords, API keys, OAuth tokens—during infrastructure provisioning. Traditionally, this created a security dilemma: sensitive values either needed to be stored in state files or managed through insecure workarounds.
Introduced in Terraform v1.10 (November 2024), ephemeral resources solve this critical challenge by ensuring sensitive information exists only during execution and is never persisted in state or plan files.
Core Principles of Ephemerality:
ephemeral "<resource_type>" "<resource_name>" {
<attributes>
<meta-arguments>
}Ephemeral resources participate in Terraform's dependency graph but with unique lifecycle:
Terraform v1.11 introduced write-only arguments that allow ephemeral values to be used in regular resource blocks while maintaining security:
resource "aws_db_instance" "example" {
instance_class = "db.t3.micro"
allocated_storage = "5"
engine = "postgres"
username = "example"
skip_final_snapshot = true
# Write-only password argument (never persisted in state)
password_wo = ephemeral.random_password.db_password.result
password_wo_version = 1
}Write-only arguments follow a naming convention with the _wo suffix and include a versioning mechanism for controlled updates.
# Generate ephemeral random password
ephemeral "random_password" "db_password" {
length = 16
override_special = "!#$%&*()-_=+[]{}<>:?"
}
# Store the password in AWS Secrets Manager
resource "aws_secretsmanager_secret" "db_password" {
name = "db_password"
}
# Set the secret value using write-only argument
resource "aws_secretsmanager_secret_version" "db_password" {
secret_id = aws_secretsmanager_secret.db_password.id
secret_string_wo = ephemeral.random_password.db_password.result
secret_string_wo_version = 1
}
# Retrieve the password from Secrets Manager (ephemeral)
ephemeral "aws_secretsmanager_secret_version" "db_password" {
secret_id = aws_secretsmanager_secret_version.db_password.secret_id
}
# Use the password to configure the database
resource "aws_db_instance" "example" {
instance_class = "db.t3.micro"
allocated_storage = "5"
engine = "postgres"
username = "example"
skip_final_snapshot = true
# Use the ephemeral password (write-only argument)
password_wo = ephemeral.aws_secretsmanager_secret_version.db_password.secret_string
password_wo_version = 1
}The security landscape increasingly favors short-lived, dynamically generated credentials over static API keys and secrets. OpenID Connect (OIDC) enables workload identities to obtain temporary credentials without managing long-lived secrets.
AWS OIDC Integration:
# Configure AWS provider with OIDC
provider "aws" {
assume_role_with_web_identity {
role_arn = aws_iam_role.terraform_role.arn
web_identity_token = var.oidc_token
duration_seconds = 3600
}
}
resource "aws_iam_role" "terraform_role" {
name = "terraform-oidc-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Principal = {
Federated = "arn:aws:iam::ACCOUNT_ID:oidc-provider/token.actions.githubusercontent.com"
}
Action = "sts:AssumeRoleWithWebIdentity"
Condition = {
StringEquals = {
"token.actions.githubusercontent.com:aud" = "sts.amazonaws.com"
}
}
}
]
})
}Azure OIDC Integration:
provider "azurerm" {
features {}
use_oidc = true
}Google Cloud OIDC Integration:
provider "google" {
project = var.gcp_project
# OIDC configuration handled automatically
# when running in a workload identity context
}Scalr provides built-in support for OIDC-based cloud authentication, eliminating the need to manage long-lived credentials:
# Scalr automatically handles OIDC token exchange
# within its Terraform runs, providing ephemeral
# credentials to cloud providers without requiring
# static API keys or secretsScalr's Provider Configurations feature enables secure centralized management of cloud credentials:
# Provider configuration stored securely in Scalr
# and automatically injected into Terraform runs
resource "scalr_provider_configuration" "aws_prod" {
account_id = var.account_id
name = "aws-production"
provider_name = "aws"
# Credentials managed securely by Scalr
# with OIDC-based authentication
}
Checkov is one of the most widely adopted static analysis tools for Terraform and OpenTofu, offering graph-based scanning for higher accuracy and fewer false positives. For setup walkthroughs, see our guide on using Checkov with Terraform and the broader landscape of Terraform vulnerability scanning.
Key Capabilities:
Integration in CI/CD:
# GitHub Actions example
name: IaC Security Scan
on: [pull_request]
jobs:
checkov:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run Checkov
uses: bridgecrewio/checkov-action@master
with:
directory: .
framework: terraform
quiet: false
soft_fail: false
compact: falsetfsec is a Terraform-specific scanner written in Go, optimized for speed while maintaining comprehensive coverage.
Key Capabilities:
Configuration Example:
# .tfsec/config.json
{
"checks": {
"aws-s3-enable-bucket-encryption": "error",
"aws-s3-block-public-access": "error",
"aws-iam-require-mfa": "error"
}
}KICS (Keeping Infrastructure as Code Secure) offers support for multiple IaC frameworks and cloud platforms.
Supported Formats:

Snyk provides a developer-first approach to IaC security with strong IDE integration and actionable remediation guidance.
Key Features:
When selecting IaC security tools, consider:
DevSecOps integrates security throughout the software development lifecycle. Key principles include:
Shift Left (and Everywhere): Address security concerns as early as possible while maintaining vigilance throughout the entire lifecycle.
Holistic Automation: Automate security checks, tests, policy enforcement, and remediation to match modern development velocity.
Continuous Security Testing: Employ multiple testing techniques (SAST, DAST, SCA, infrastructure scanning) continuously.
Collaborative Culture: Break down silos between development, security, and operations teams.
Security as Code: Define security policies and controls as code for consistency and auditability.
Infrastructure as Code occupies a critical position in DevSecOps, forming the foundation upon which applications and security controls run.
SAST for Application Code scans application source code for vulnerabilities.
SCA (Software Composition Analysis) identifies vulnerabilities and license issues in open-source dependencies.
IaC Security Scanning identifies misconfigurations in Terraform, CloudFormation, and other infrastructure definitions—a layer often overlooked.
DAST tests running applications for vulnerabilities.
CSPM (Cloud Security Posture Management) continuously monitors deployed resources for drift and non-compliance.
A robust DevSecOps strategy leverages diverse tooling across multiple categories:
| Tool Category | Examples | Primary Function |
|---|---|---|
| SAST | SonarQube, Checkmarx, Snyk Code | Analyze source code for vulnerabilities |
| DAST | OWASP ZAP, Veracode | Test running applications |
| SCA | Snyk, GitHub Dependabot, Sonatype | Identify vulnerable dependencies |
| IaC Security | Checkov, tfsec, KICS | Scan infrastructure code |
| CNAPP | Wiz, Prisma Cloud | Unified cloud security platform |
| Secrets Management | HashiCorp Vault, AWS Secrets Manager | Securely manage sensitive data |
| IaC Automation/Governance | Scalr, Terraform Cloud | Manage IaC at scale with policies |
AWS secures the infrastructure (physical security, hypervisor, host OS, network infrastructure), while customers are responsible for securing what they deploy (service configuration, customer data, identity & access management, network controls, operating system, application security).
Misconfiguration remains the most common vulnerability, accounting for 63% of AWS security incidents.
IAM Issues represent the second most critical risk area, contributing to 47% of successful breaches. See our practical guide on streamlining AWS IAM role creation with Terraform for least-privilege patterns.
Data Exfiltration from improperly configured S3 buckets remains a persistent threat.
Supply Chain Attacks through compromised CI/CD pipelines increased 73% year-over-year.
1. Identity and Access Management
# Implement least-privilege IAM policies
resource "aws_iam_policy" "limited_s3_access" {
name = "s3-read-only"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"s3:GetObject",
"s3:ListBucket"
]
Resource = [
aws_s3_bucket.app_data.arn,
"${aws_s3_bucket.app_data.arn}/*"
]
}
]
})
}
# Use temporary credentials instead of long-term access keys
resource "aws_iam_role" "app_role" {
name = "app-execution-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Principal = {
Service = "ec2.amazonaws.com"
}
Action = "sts:AssumeRole"
}
]
})
}2. Data Protection
# Encrypt S3 buckets with KMS
resource "aws_kms_key" "s3_encryption_key" {
description = "KMS key for S3 bucket encryption"
deletion_window_in_days = 10
enable_key_rotation = true
}
resource "aws_s3_bucket_server_side_encryption_configuration" "secure_bucket" {
bucket = aws_s3_bucket.app_data.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "aws:kms"
kms_master_key_id = aws_kms_key.s3_encryption_key.arn
}
}
}
# Block public access
resource "aws_s3_bucket_public_access_block" "app_data" {
bucket = aws_s3_bucket.app_data.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}3. Network Security
# Implement VPC Flow Logs
resource "aws_flow_log" "main" {
log_destination = aws_s3_bucket.flow_logs.arn
log_destination_type = "s3"
traffic_type = "ALL"
vpc_id = aws_vpc.main.id
log_format = "${version} ${account-id} ${interface-id} ${srcaddr} ${dstaddr} ${srcport} ${dstport} ${protocol}"
}
# Create security groups with minimal access
resource "aws_security_group" "web" {
name = "web-sg"
description = "Allow TLS inbound traffic"
vpc_id = aws_vpc.main.id
ingress {
description = "TLS from anywhere"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
# Use VPC endpoints for service connections
resource "aws_vpc_endpoint" "s3" {
vpc_id = aws_vpc.main.id
service_name = "com.amazonaws.us-east-1.s3"
vpc_endpoint_type = "Gateway"
}4. Continuous Monitoring and Response
# Configure CloudTrail for audit logging
resource "aws_cloudtrail" "main" {
name = "main-trail"
s3_bucket_name = aws_s3_bucket.cloudtrail_logs.id
include_global_service_events = true
is_multi_region_trail = true
enable_log_file_validation = true
kms_key_id = aws_kms_key.cloudtrail.arn
event_selector {
read_write_type = "All"
include_management_events = true
data_resource {
type = "AWS::S3::Object"
values = ["arn:aws:s3:::"]
}
}
}
# Enable GuardDuty for threat detection
resource "aws_guardduty_detector" "main" {
enable = true
finding_publishing_frequency = "FIFTEEN_MINUTES"
}Modern CI/CD pipelines should include automated security scanning at multiple stages:
Pre-Commit Hooks: Prevent secrets and misconfigurations from entering the repository.
Build Stage: Run comprehensive static analysis on all IaC code.
Plan Stage: Analyze terraform/tofu plan output for security issues.
Policy Evaluation: Enforce organizational security policies before deployment.
Post-Deployment Validation: Verify deployed resources match intended configuration.
# GitHub Actions workflow
name: Terraform Security Pipeline
on:
pull_request:
paths:
- 'terraform/**'
push:
branches:
- main
jobs:
terraform_validation:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Terraform Format Check
run: terraform fmt -check -recursive terraform/
- name: Terraform Validate
run: terraform -chdir=terraform/prod validate
- name: Run Checkov
uses: bridgecrewio/checkov-action@master
with:
directory: terraform/
framework: terraform
quiet: false
- name: Run tfsec
uses: aquasecurity/[email protected]
with:
working_directory: terraform/
- name: Terraform Plan
run: terraform -chdir=terraform/prod plan -json -out=tfplan.json
- name: Analyze Plan with Snyk
run: |
npm install -g snyk
snyk iac test tfplan.json --json-file-output=snyk-results.json
- name: Upload SARIF Results
uses: github/codeql-action/upload-sarif@v2
if: always()
with:
sarif_file: snyk-results.sarif
terraform_apply:
needs: terraform_validation
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Terraform Apply
run: |
cd terraform/prod
terraform init
terraform apply tfplan.json
Scalr extends Terraform and OpenTofu with enterprise governance capabilities, enabling secure, compliant infrastructure operations at scale. For audit and compliance evidence, see Terraform audit logs; for runtime scanning integrations, see automating Terraform security in Scalr deployments with Regula and using Scalr hooks with Bridgecrew Yor.
SAML 2.0 & SCIM Integration:
# Scalr integrates with corporate identity providers
# for unified authentication and provisioningOIDC for API Access:
# Authenticate programmatically with OIDC tokens
# instead of static API keysOpen Policy Agent (OPA) Integration:
# Scalr enforces OPA policies before infrastructure changes
package terraform.policies
deny[reason] {
resource := input.resource_changes[_]
resource.type == "aws_s3_bucket"
not resource.change.after.server_side_encryption_configuration
reason := sprintf("S3 bucket '%s' must have encryption enabled", [resource.name])
}Checkov Integration:
# Scalr automatically scans Terraform/OpenTofu
# configurations with Checkov before executionProvider Configurations:
Scalr centrally manages and securely stores credentials for cloud providers and other services, then automatically injects them into Terraform runs.
OIDC for Dynamic Cloud Credentials:
# Scalr supports OIDC for obtaining temporary
# credentials from AWS, Azure, and GCP
# eliminating long-lived static credentialsVCS Agents for Internal Repositories:
For organizations with self-hosted version control systems, Scalr's VCS Agents establish secure connections without exposing internal repositories to the public internet.
Self-Hosted Agents:
# Run Terraform/OpenTofu operations within
# your own network infrastructure for complete controlState Encryption:
OPA Policy Violation Reporting:
Tracks which workspaces violate policies and provides remediation guidance.
Resource and Configuration Tracking:
Aggregates all resources across state files for visibility and audit purposes.
Drift Detection Reports:
Identifies discrepancies between intended and actual infrastructure configuration.
Version Management Reports:
Tracks Terraform/OpenTofu, module, and provider versions to identify outdated components with known vulnerabilities.
Stale Workspace Reports:
Identifies workspaces with active resources that haven't been recently updated, which may not reflect current security policies.
API Token Management:
Tracks token rotation and usage patterns to enforce security best practices.
Audit Logs:
Comprehensive activity logs with integration to AWS EventBridge or Datadog for centralized security analysis.
Treat all networks as potentially hostile and enforce strict least-privilege principles:
Manual security reviews no longer suffice for infrastructure operating at cloud scale:
Static permissions are inherently risky:
Catch issues as early as possible:
Rather than patching and modifying:
Never trust hardcoded values:
Define and enforce security standards programmatically:
Security requires accountability:
Prevent unauthorized or unintended changes:
Tooling alone is insufficient:
Securing Infrastructure as Code is a continuous journey requiring multiple layers of controls, tools, and practices. The security landscape continues to evolve, with new threats emerging and new solutions addressing longstanding challenges like ephemeral resource handling and dynamic credentials.
Success in 2026 requires:
By implementing the practices and tools outlined in this guide, organizations can maintain secure, compliant, and efficiently managed infrastructure even as cloud complexity and threat sophistication continue to increase. Security is not a destination but an ongoing practice—one that becomes progressively more effective with proper tooling, automation, and organizational commitment.
