Terraform on Ubuntu 26.04 LTS - sudo-rs, APT Rollback, and Hardened Base Images
Install and run Terraform on Ubuntu 26.04 LTS Resolute Raccoon. Covers sudo-rs as default, APT 3.2 rollback, Kernel 7.0, Wayland-only, ROCm, and building...
DevOps
Secure your Terraform workflows. Never hardcode secrets, encrypt state files, use least-privilege IAM, scan with tfsec/checkov
# NEVER DO THIS
provider "aws" {
access_key = "AKIAIOSFODNN7EXAMPLE"
secret_key = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
}
resource "aws_db_instance" "main" {
password = "SuperSecret123!" # Stored in state AND version control
}This puts credentials in Git history forever.
export AWS_ACCESS_KEY_ID="AKIAIOSFODNN7EXAMPLE"
export AWS_SECRET_ACCESS_KEY="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"# No credentials in code — provider reads from environment
provider "aws" {
region = "us-east-1"
}variable "db_password" {
description = "Database master password"
type = string
sensitive = true # Won't show in plan/apply output
}
resource "aws_db_instance" "main" {
password = var.db_password
}Pass via environment variable:
export TF_VAR_db_password="SuperSecret123!"
terraform applydata "vault_generic_secret" "db" {
path = "secret/database/prod"
}
resource "aws_db_instance" "main" {
password = data.vault_generic_secret.db.data["password"]
}data "aws_secretsmanager_secret_version" "db" {
secret_id = "prod/database/password"
}
resource "aws_db_instance" "main" {
password = jsondecode(data.aws_secretsmanager_secret_version.db.secret_string)["password"]
}resource "random_password" "db" {
length = 32
special = true
}
resource "aws_db_instance" "main" {
password = random_password.db.result
}
# Store in Secrets Manager for application access
resource "aws_secretsmanager_secret_version" "db" {
secret_id = aws_secretsmanager_secret.db.id
secret_string = random_password.db.result
}Terraform state contains every attribute of every resource — including passwords, private keys, and API tokens. In plaintext.
S3 Backend with encryption:
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "prod/terraform.tfstate"
region = "us-east-1"
encrypt = true # SSE-S3 encryption
kms_key_id = "arn:aws:kms:..." # Optional: SSE-KMS
dynamodb_table = "terraform-locks" # State locking
}
}# S3 bucket policy — only CI/CD role can access
resource "aws_s3_bucket_policy" "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 = {
StringNotEquals = {
"aws:PrincipalArn" = var.terraform_role_arn
}
}
}]
})
}resource "aws_s3_bucket_public_access_block" "state" {
bucket = aws_s3_bucket.terraform_state.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}resource "aws_s3_bucket_versioning" "state" {
bucket = aws_s3_bucket.terraform_state.id
versioning_configuration {
status = "Enabled"
}
}# .gitignore
*.tfstate
*.tfstate.*
*.tfplan
.terraform/# Bad — admin access
resource "aws_iam_policy" "terraform" {
policy = jsonencode({
Statement = [{
Effect = "Allow"
Action = "*"
Resource = "*"
}]
})
}
# Good — specific permissions
resource "aws_iam_policy" "terraform" {
policy = jsonencode({
Statement = [
{
Effect = "Allow"
Action = [
"ec2:*",
"s3:*",
"rds:*"
]
Resource = "*"
},
{
Effect = "Allow"
Action = [
"iam:CreateRole",
"iam:AttachRolePolicy"
]
Resource = "arn:aws:iam::*:role/app-*" # Only app- prefixed roles
}
]
})
}provider "aws" {
region = "us-east-1"
assume_role {
role_arn = "arn:aws:iam::123456789012:role/TerraformDeployRole"
session_name = "terraform-ci"
}
}GitLab CI with OIDC:
plan:
id_tokens:
GITLAB_OIDC_TOKEN:
aud: https://gitlab.com
script:
- |
export $(aws sts assume-role-with-web-identity \
--role-arn $ROLE_ARN \
--role-session-name "gitlab-${CI_PIPELINE_ID}" \
--web-identity-token $GITLAB_OIDC_TOKEN \
--query 'Credentials.[AccessKeyId,SecretAccessKey,SessionToken]' \
--output text | xargs -n3 printf 'AWS_ACCESS_KEY_ID=%s AWS_SECRET_ACCESS_KEY=%s AWS_SESSION_TOKEN=%s\n')
- terraform plan# Install
brew install tfsec
# Scan
tfsec .Result: CRITICAL - Security group rule allows ingress from public internet
Resource: aws_security_group_rule.allow_all
Location: main.tf:15
Result: HIGH - S3 bucket does not have encryption enabled
Resource: aws_s3_bucket.data
Location: storage.tf:1pip install checkov
checkov -d .security-scan:
stage: validate
script:
- tfsec . --format json > tfsec-report.json
- checkov -d . -o json > checkov-report.json
artifacts:
reports:
sast: tfsec-report.jsonEvery Terraform project needs this:
# State files
*.tfstate
*.tfstate.*
# Plan files
*.tfplan
# Provider plugins
.terraform/
# Sensitive variable files
*.auto.tfvars
secret.tfvars
# Override files
override.tf
override.tf.json
# CLI config
.terraformrc
terraform.rc
# Crash logs
crash.log
crash.*.log| Check | Status |
|---|---|
No hardcoded credentials in .tf files | ☐ |
sensitive = true on secret variables | ☐ |
| State encrypted at rest (S3 SSE / GCS encryption) | ☐ |
| State bucket has no public access | ☐ |
| State bucket has versioning enabled | ☐ |
| DynamoDB/GCS state locking enabled | ☐ |
.terraform/ and *.tfstate in .gitignore | ☐ |
| CI/CD uses OIDC or assume-role (not long-lived keys) | ☐ |
| CI/CD variables marked as protected + masked | ☐ |
| tfsec/checkov runs in CI pipeline | ☐ |
| Provider versions pinned | ☐ |
.terraform.lock.hcl committed | ☐ |
Learn by doing with interactive courses on CopyPasteLearn:
Never hardcode secrets — use environment variables, Vault, or Secrets Manager. Encrypt state with S3 SSE-KMS, block public access, enable versioning and locking. Use least-privilege IAM with assume-role and OIDC (no long-lived keys). Run tfsec and checkov in CI. These practices prevent the most common Terraform security mistakes.
Install and run Terraform on Ubuntu 26.04 LTS Resolute Raccoon. Covers sudo-rs as default, APT 3.2 rollback, Kernel 7.0, Wayland-only, ROCm, and building...
Learn Terraform workspaces to manage dev, staging, and production with the same code. Covers workspace commands, terraform.workspace variable
Write reusable Terraform modules with proper structure, inputs, outputs, and versioning. Covers local modules, registry modules, module composition
Configure Terraform remote state with AWS S3 and DynamoDB locking. Complete setup with encryption, versioning, IAM permissions, and team access patterns.