Terraform Cloud Hardening Guide
IaC platform security for workspace variables, team access, and run triggers
Overview
Terraform Cloud state files containing plaintext secrets, cloud provider credentials, and workspace configurations make IaC platforms high-value targets. Vault-backed dynamic credentials via OIDC federation represent best practice for eliminating stored secrets. State file exposure reveals database passwords and API keys; malicious provider backdoors infrastructure.
Intended Audience
- Security engineers managing IaC platforms
- Platform engineers configuring Terraform
- GRC professionals assessing infrastructure compliance
- DevOps teams implementing secure IaC
Table of Contents
- Authentication & Access Controls
- Workspace Security
- State File Security
- Secrets Management
- Monitoring & Detection
1. Authentication & Access Controls
1.1 Enforce SSO with MFA
Profile Level: L1 (Baseline) NIST 800-53: IA-2(1)
ClickOps Implementation
Step 1: Configure SSO (Business)
- Navigate to: Organization → Settings → SSO
- Configure SAML with your IdP
- Enforce SSO for all users
Step 2: Configure Team Tokens
- Create team tokens with minimum permissions
- Set expiration
- Rotate quarterly
1.2 Team-Based Access Control
Profile Level: L1 (Baseline) NIST 800-53: AC-3, AC-6
ClickOps Implementation
Step 1: Define Teams
| Team | Permissions |
|---|---|
| owners | Full organization access |
| platform | Manage workspaces |
| developers | Plan only (no apply) |
| read-only | View only |
Step 2: Assign Workspace Permissions
- Navigate to: Workspace → Team Access
- Grant minimum permissions per team
2. Workspace Security
2.1 Configure Workspace Restrictions
Profile Level: L1 (Baseline) NIST 800-53: CM-3
ClickOps Implementation
Step 1: Execution Mode
- Navigate to: Workspace → Settings → General
- Configure: Execution Mode: Remote
- Enable: Auto-apply: Disabled for production
Step 2: VCS Integration Security
- Configure branch protection
- Require PR review before apply
- Enable speculative plans
2.2 Sentinel Policy Enforcement
Profile Level: L2 (Hardened) NIST 800-53: CM-7
Implementation
# Sentinel policy - Require encryption
policy "require-s3-encryption" {
enforcement_level = "hard-mandatory"
}
# policy.sentinel
import "tfplan/v2" as tfplan
s3_buckets = filter tfplan.resource_changes as _, rc {
rc.type is "aws_s3_bucket" and
(rc.change.actions contains "create" or rc.change.actions contains "update")
}
encryption_enabled = rule {
all s3_buckets as _, bucket {
bucket.change.after.server_side_encryption_configuration is not null
}
}
main = rule {
encryption_enabled
}
3. State File Security
3.1 State File Protection
Profile Level: L1 (Baseline) NIST 800-53: SC-28
Rationale
Why This Matters:
- State files contain plaintext secrets
- Database passwords, API keys exposed
- State file = infrastructure blueprint
Attack Scenario: State file exposure reveals database passwords and API keys; malicious provider backdoors infrastructure.
ClickOps Implementation
Step 1: Enable State Encryption
- Terraform Cloud encrypts state at rest by default
- Verify encryption settings
Step 2: Restrict State Access
- Navigate to: Workspace → Settings → General
- Configure: Terraform State: API access restricted
- Limit who can view/download state
3.2 Sensitive Variable Handling
Profile Level: L1 (Baseline) NIST 800-53: SC-28
Implementation
# Mark variables as sensitive
variable "db_password" {
type = string
sensitive = true
}
# Output marking
output "connection_string" {
value = local.connection_string
sensitive = true
}
4. Secrets Management
4.1 Dynamic Credentials (OIDC)
Profile Level: L2 (Hardened) NIST 800-53: IA-5
Description
Use OIDC workload identity instead of static credentials.
AWS Configuration
# Configure OIDC provider in AWS
resource "aws_iam_openid_connect_provider" "tfc" {
url = "https://app.terraform.io"
client_id_list = ["aws.workload.identity"]
thumbprint_list = ["9e99a48a9960b14926bb7f3b02e22da2b0ab7280"]
}
# Trust policy for TFC
data "aws_iam_policy_document" "tfc_trust" {
statement {
actions = ["sts:AssumeRoleWithWebIdentity"]
principals {
type = "Federated"
identifiers = [aws_iam_openid_connect_provider.tfc.arn]
}
condition {
test = "StringEquals"
variable = "app.terraform.io:aud"
values = ["aws.workload.identity"]
}
condition {
test = "StringLike"
variable = "app.terraform.io:sub"
values = ["organization:myorg:project:*:workspace:*:run_phase:*"]
}
}
}
Terraform Cloud Workspace
# workspace variables
TFC_AWS_PROVIDER_AUTH = true
TFC_AWS_RUN_ROLE_ARN = arn:aws:iam::123456789:role/tfc-role
4.2 Vault Integration
Profile Level: L2 (Hardened)
Implementation
# Use Vault provider for secrets
provider "vault" {
address = "https://vault.company.com"
}
data "vault_generic_secret" "db" {
path = "secret/production/database"
}
resource "aws_db_instance" "main" {
password = data.vault_generic_secret.db.data["password"]
}
5. Monitoring & Detection
5.1 Audit Logging
Profile Level: L1 (Baseline) NIST 800-53: AU-2, AU-3
Detection Focus
-- Detect state access
SELECT user, workspace, action
FROM tfc_audit_log
WHERE action = 'state_version.read'
AND timestamp > NOW() - INTERVAL '24 hours';
-- Detect variable changes
SELECT user, workspace, variable_name
FROM tfc_audit_log
WHERE action LIKE '%variable%'
AND timestamp > NOW() - INTERVAL '7 days';
Appendix A: Edition Compatibility
| Control | Free | Team | Business | Enterprise |
|---|---|---|---|---|
| SSO | ❌ | ❌ | ✅ | ✅ |
| Sentinel | ❌ | ❌ | ✅ | ✅ |
| Audit Logs | ❌ | ❌ | ✅ | ✅ |
| OIDC | ✅ | ✅ | ✅ | ✅ |
Changelog
| Date | Version | Maturity | Changes | Author |
|---|---|---|---|---|
| 2025-12-14 | 0.1.0 | draft | Initial Terraform Cloud hardening guide | Claude Code (Opus 4.5) |