Fatskills
Practice. Master. Repeat.
Study Guide: TECH **Terraform Sensitive Data Handling: Zero-Fluff Study Guide**
Source: https://www.fatskills.com/cloud-application-developer/chapter/tech-terraform-sensitive-data-handling-zero-fluff-study-guide

TECH **Terraform Sensitive Data Handling: Zero-Fluff Study Guide**

By Fatskills Exam Guides Team — the exam nerds behind 28,500+ quizzes and 2.1M practice questions across 500+ global exams.

⏱️ ~8 min read

Terraform Sensitive Data Handling: Zero-Fluff Study Guide

(For Engineers Who Need to Ship Secure IaC—Fast)


1. What This Is & Why It Matters

You’re deploying a Terraform stack that needs a database password, an API key, or a TLS certificate. If you hardcode these in plaintext, you just turned your Git repo into a treasure map for attackers. Even if you delete the secret later, Git history keeps it forever.

Why this matters in production:
- Compliance violations: PCI-DSS, HIPAA, SOC2, and GDPR all require secrets to be encrypted at rest and in transit.
- Security breaches: A leaked AWS secret key can cost $100K+ in stolen resources (or ransomware).
- Operational chaos: Rotating a leaked secret means downtime, manual updates, and firefighting.

Real-world scenario:
You inherit a Terraform repo where terraform.tfvars contains:


db_password = "SuperSecret123!"

This file is committed to Git. Your first task: Secure this immediately—without breaking the deployment.


2. Core Concepts & Components


1. sensitive = true (Terraform 0.14+)

  • Definition: Marks a variable or output as sensitive, preventing it from being logged in CLI output or state files.
  • Production insight: Always use this for passwords, API keys, and certificates. But: It doesn’t encrypt the value—just hides it from logs.

2. terraform.tfvars vs. variables.tf

  • variables.tf: Declares variable types and descriptions.
  • terraform.tfvars: Assigns values (often committed to Git—dangerous for secrets).
  • Production insight: Never commit terraform.tfvars with secrets. Use environment variables or a secrets manager instead.

3. Environment Variables (TF_VAR_ prefix)

  • Definition: Terraform automatically picks up env vars prefixed with TF_VAR_ (e.g., TF_VAR_db_password).
  • Production insight: Useful for CI/CD pipelines (e.g., GitHub Actions secrets). But: Still visible in process listings (ps aux).

4. AWS Secrets Manager / Parameter Store

  • Definition: AWS services for storing and retrieving secrets at runtime.
  • Production insight: Use aws_secretsmanager_secret_version or aws_ssm_parameter data sources to fetch secrets during terraform apply. Cost: Secrets Manager is pricier than Parameter Store.

5. HashiCorp Vault

  • Definition: Dedicated secrets management tool with dynamic secrets, leases, and fine-grained access control.
  • Production insight: Best for multi-cloud or hybrid setups. Complexity: Requires running a Vault cluster (or using HCP Vault).

6. sensitive Outputs

  • Definition: Outputs marked sensitive = true are redacted in CLI output but still stored in plaintext in the state file.
  • Production insight: Always encrypt your state file (e.g., S3 backend with SSE-KMS).

7. remote-exec and local-exec Provisioners

  • Definition: Used to inject secrets into VMs or containers at runtime.
  • Production insight: Avoid passing secrets as command-line arguments (visible in ps aux). Use files or environment variables instead.

8. terraform state Encryption

  • Definition: Terraform state files contain all secrets in plaintext unless encrypted.
  • Production insight: Use an S3 backend with SSE-KMS or a Vault-backed backend.


3. Step-by-Step: Secure a Database Password with AWS Secrets Manager


Prerequisites

  • AWS account with IAM permissions for Secrets Manager and RDS.
  • Terraform installed (>= 0.14).
  • AWS CLI configured (aws configure).

Goal

Deploy an RDS MySQL instance with a password fetched from AWS Secrets Manager (not hardcoded).


Step 1: Store the Secret in AWS Secrets Manager

# Create a secret (replace with your password)
aws secretsmanager create-secret \
  --name "prod/db/password" \
  --secret-string "SuperSecret123!" \
  --description "RDS MySQL root password"

Verify:


aws secretsmanager get-secret-value --secret-id "prod/db/password"


Step 2: Write Terraform Code

main.tf

provider "aws" {
  region = "us-east-1"
}

# Fetch the secret from AWS Secrets Manager
data "aws_secretsmanager_secret_version" "db_password" {
  secret_id = "prod/db/password"
}

# Create RDS instance with the secret
resource "aws_db_instance" "mysql" {
  allocated_storage    = 20
  engine               = "mysql"
  engine_version       = "8.0"
  instance_class       = "db.t3.micro"
  db_name              = "mydb"
  username             = "admin"
  password             = data.aws_secretsmanager_secret_version.db_password.secret_string
  parameter_group_name = "default.mysql8.0"
  skip_final_snapshot  = true
  publicly_accessible  = false  # ⚠️ Critical for security!
}

# Output the RDS endpoint (mark as sensitive)
output "rds_endpoint" {
  value     = aws_db_instance.mysql.endpoint
  sensitive = true
}

variables.tf (Optional)

variable "region" {
  description = "AWS region"
  default     = "us-east-1"
}


Step 3: Deploy

terraform init
terraform plan  # Verify the secret is fetched (but not shown in logs)
terraform apply -auto-approve

Expected output:


Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
Outputs: rds_endpoint = <sensitive>


Step 4: Verify the Secret is Secure

  1. Check state file:
    bash
    terraform show -json | jq '.values.root_module.resources[] | select(.address == "aws_db_instance.mysql")'
  2. ⚠️ The password is still in plaintext in the state file! (We’ll fix this in Best Practices.)

  3. Check AWS Console:

  4. Go to RDS > Databases and verify the instance is running.
  5. Go to Secrets Manager and confirm the secret exists.

4. ? Production-Ready Best Practices


Security

  • Encrypt state files: Use an S3 backend with SSE-KMS: hcl terraform {
    backend "s3" {
    bucket = "my-terraform-state"
    key = "prod/terraform.tfstate"
    region = "us-east-1"
    encrypt = true
    kms_key_id = "alias/terraform-state-key"
    } }
  • Least privilege IAM: Restrict Terraform’s IAM role to only access the secrets it needs: json {
    "Version": "2012-10-17",
    "Statement": [
    {
    "Effect": "Allow",
    "Action": [
    "secretsmanager:GetSecretValue"
    ],
    "Resource": "arn:aws:secretsmanager:us-east-1:123456789012:secret:prod/db/*"
    }
    ] }
  • Rotate secrets: Use AWS Secrets Manager’s automatic rotation or Vault’s dynamic secrets.

Cost Optimization

  • Use Parameter Store for static secrets: Cheaper than Secrets Manager (free for standard tier).
  • Avoid local-exec for secrets: It’s slow and hard to audit.

Reliability & Maintainability

  • Tag secrets: Always tag secrets in AWS/Vault for cost tracking and ownership: hcl resource "aws_secretsmanager_secret" "db_password" {
    name = "prod/db/password"
    description = "RDS MySQL root password"
    tags = {
    Environment = "prod"
    Owner = "devops-team"
    } }
  • Use sensitive outputs sparingly: Only mark outputs as sensitive if they contain secrets. Overuse makes debugging harder.

Observability

  • Monitor secret access: Enable AWS CloudTrail for Secrets Manager: bash aws cloudtrail create-trail --name SecretsManagerTrail --s3-bucket-name my-cloudtrail-logs
  • Alert on unauthorized access: Set up CloudWatch alarms for GetSecretValue events.


5. ⚠️ Common Mistakes & Traps

Mistake Symptom Fix/Prevention
Committing terraform.tfvars to Git Secrets exposed in Git history forever. Add *.tfvars to .gitignore. Use environment variables or a secrets manager.
Not marking outputs as sensitive Secrets leaked in CLI output or logs. Always mark sensitive outputs with sensitive = true.
Storing secrets in plaintext state State file becomes a security risk. Encrypt state files (S3 + KMS) and restrict access.
Hardcoding secrets in user_data Secrets visible in EC2 instance metadata. Use aws_secretsmanager_secret_version or aws_ssm_parameter data sources.
Using local-exec with secrets Secrets visible in ps aux or logs. Pass secrets via files or environment variables instead of command-line args.


6. ? Exam/Certification Focus


Typical Question Patterns

  1. Scenario: "You need to deploy an RDS instance with a password. How do you ensure the password isn’t logged in Terraform output?"
  2. Answer: Mark the output as sensitive = true and use a secrets manager (e.g., AWS Secrets Manager).

  3. Trick Question: "Is marking a variable as sensitive = true enough to secure it?"

  4. Answer: No. It only hides the value from logs—it’s still in plaintext in the state file.

  5. Scenario: "Your Terraform state file contains secrets. How do you secure it?"

  6. Answer: Use an S3 backend with SSE-KMS encryption and restrict IAM access.

Key Distinctions

Concept What It Does What It Doesn’t Do
sensitive = true Hides values from logs. Doesn’t encrypt the value.
AWS Secrets Manager Stores and retrieves secrets securely. Doesn’t automatically rotate secrets.
Vault Dynamic Secrets Generates short-lived credentials. Requires Vault cluster setup.


7. ? Hands-On Challenge


Challenge

Deploy an EC2 instance with an SSH key fetched from AWS Secrets Manager (instead of a hardcoded key pair).

Requirements:
1. Store the private key in AWS Secrets Manager.
2. Fetch the key during terraform apply.
3. Inject the key into the EC2 instance via user_data.

Solution:


data "aws_secretsmanager_secret_version" "ssh_key" {
  secret_id = "prod/ec2/ssh_key"
}

resource "aws_instance" "web" {
  ami           = "ami-0c55b159cbfafe1f0"  # Amazon Linux 2
  instance_type = "t3.micro"
  user_data = <<-EOF
#!/bin/bash
echo "${data.aws_secretsmanager_secret_version.ssh_key.secret_string}" > /home/ec2-user/.ssh/id_rsa
chmod 600 /home/ec2-user/.ssh/id_rsa
EOF }

Why it works:
- The key is fetched at runtime (not hardcoded).
- user_data writes the key to the instance (but ⚠️ this is still visible in instance metadata—use SSM Parameter Store for better security).


8. ? Rapid-Reference Crib Sheet

Command/Concept Usage Exam Trap
sensitive = true output "db_password" { value = var.password, sensitive = true } Doesn’t encrypt—just hides from logs.
TF_VAR_ prefix export TF_VAR_db_password="SuperSecret123!" Visible in ps aux.
AWS Secrets Manager aws secretsmanager get-secret-value --secret-id "prod/db/password" Costs $0.40/secret/month.
AWS Parameter Store aws ssm get-parameter --name "/prod/db/password" --with-decryption Free for standard tier.
Vault CLI vault kv get -mount=secret prod/db/password Requires Vault cluster.
S3 Backend + KMS terraform { backend "s3" { encrypt = true, kms_key_id = "..." } } Default S3 encryption is SSE-S3 (not KMS).
.gitignore for secrets Add *.tfvars and *.auto.tfvars to .gitignore. Git history still contains old secrets.
local-exec with secrets ❌ Avoid. Use files or env vars instead. Secrets visible in ps aux.
remote-exec with SSH ✅ Better. Use connection { private_key = file("~/.ssh/id_rsa") }. Still needs secure key storage.


9. ? Where to Go Next

  1. Terraform Sensitive Variables Docs
  2. AWS Secrets Manager Best Practices
  3. HashiCorp Vault + Terraform Tutorial
  4. Terraform State Encryption Guide

Final Pro Tip:


"Treat your Terraform state file like a nuclear launch code. Encrypt it, restrict access, and never commit it to Git. A single leaked state file can compromise your entire infrastructure."




ADVERTISEMENT