Terraform Security
Pendahuluan
Terraform adalah Infrastructure as Code (IaC) tool paling populer yang dikembangkan oleh HashiCorp. Dengan Terraform, infrastruktur cloud - server, database, jaringan, load balancer - didefinisikan sebagai kode dan dikelola melalui siklus hidup yang terotomatisasi.
Namun, kekuatan Terraform juga membawa risiko keamanan yang signifikan. State file Terraform berisi semua informasi infrastruktur termasuk nilai sensitif. Provider dan module dari sumber tidak terpercaya dapat menyuntikkan kode berbahaya. Kebijakan keamanan yang longgar dapat menyebabkan konfigurasi infrastruktur yang tidak aman.
Artikel ini membahas aspek keamanan kritis Terraform: keamanan state file, penanganan sensitive values, keamanan provider dan module, serta policy-as-code dengan Sentinel.
State File Security
State file (terraform.tfstate) adalah file JSON yang
berisi representasi infrastruktur yang dikelola Terraform. File ini
sangat sensitif karena berisi:
- Resource identifiers: ARN, ID resource, IP addresses.
- Sensitive values: Database passwords,
API keys, private keys (meskipun ditandai
sensitive). - Network topology: VPC IDs, subnet CIDRs, security group rules.
Remote State dengan S3 Backend
Menyimpan state file secara lokal sangat berbahaya. Praktik terbaik adalah menggunakan remote backend dengan enkripsi.
Contoh konfigurasi S3 backend:
terraform {
backend "s3" {
bucket = "company-terraform-state"
key = "production/network/terraform.tfstate"
region = "ap-southeast-1"
encrypt = true
dynamodb_table = "terraform-state-lock"
kms_key_id = "arn:aws:kms:ap-southeast-1:123456789012:key/abc123"
}
}
S3 Bucket untuk State File
Bucket S3 yang menyimpan state file harus dikonfigurasi dengan keamanan maksimal:
resource "aws_s3_bucket" "terraform_state" {
bucket = "company-terraform-state-123456789012"
}
resource "aws_s3_bucket_versioning" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_s3_bucket_server_side_encryption_configuration" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
rule {
apply_server_side_encryption_by_default {
kms_master_key_id = aws_kms_key.terraform_state.arn
sse_algorithm = "aws:kms"
}
}
}
resource "aws_s3_bucket_public_access_block" "terraform_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_policy" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Principal = {
AWS = "arn:aws:iam::123456789012:role/terraform-backend-role"
}
Action = [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject"
]
Resource = "${aws_s3_bucket.terraform_state.arn}/*"
Condition = {
Bool = {
"aws:SecureTransport": "true"
}
}
},
{
Effect = "Deny"
Principal = "*"
Action = "s3:*"
Resource = [
aws_s3_bucket.terraform_state.arn,
"${aws_s3_bucket.terraform_state.arn}/*"
]
Condition = {
Bool = {
"aws:SecureTransport": "false"
}
}
}
]
})
}
DynamoDB State Locking
DynamoDB digunakan untuk state locking - mencegah dua orang atau proses mengubah state secara bersamaan. Konfigurasi ini wajib untuk lingkungan tim.
resource "aws_dynamodb_table" "terraform_state_lock" {
name = "terraform-state-lock"
billing_mode = "PAY_PER_REQUEST"
hash_key = "LockID"
attribute {
name = "LockID"
type = "S"
}
server_side_encryption {
enabled = true
}
point_in_time_recovery {
enabled = true
}
}
Dengan konfigurasi di atas:
- State file dienkripsi dengan KMS (AWS KMS).
- Akses S3 dibatasi hanya untuk role tertentu.
- Semua akses melalui HTTPS (SecureTransport).
- State locking menggunakan DynamoDB.
- Versioning diaktifkan untuk pemulihan.
Alternatif Backend: Terraform Cloud / HCP Terraform
HashiCorp Cloud Platform (HCP) Terraform menyediakan backend terkelola dengan fitur keamanan tambahan:
terraform {
cloud {
organization = "example-org"
workspaces {
name = "production-infrastructure"
}
}
}
Keuntungan HCP Terraform:
- Enkripsi state at-rest dan in-transit.
- Audit logging.
- Role-based access control (RBAC).
- Run tasks untuk policy enforcement (Sentinel).
- Private registry untuk module.
Sensitive Values
Terraform memiliki mekanisme untuk melindungi nilai sensitif seperti password dan API keys.
Parameter sensitive = true
resource "aws_db_instance" "database" {
username = "admin"
password = random_password.db_password.result
# password tidak akan muncul di output terraform plan/apply
}
resource "random_password" "db_password" {
length = 24
special = true
override_special = "!#$%&*()-_=+[]:."
}
output "db_password" {
value = random_password.db_password.result
sensitive = true # tidak ditampilkan di output
}
output "db_endpoint" {
value = aws_db_instance.database.endpoint
# tidak sensitive - endpoint boleh ditampilkan
}
Dengan sensitive = true, nilai tersebut:
- Tidak akan muncul di output
terraform planatauterraform apply. - Tidak akan disimpan dalam log.
- Akan tetap tersimpan di state file (terenkripsi jika backend mendukung).
Menulis Output Aman dengan terraform output
# Output nilai non-sensitive
terraform output db_endpoint
# Output nilai sensitive - harus dengan flag -json
terraform output -json db_password
# Simpan ke file dengan permission terbatas
terraform output -json > /tmp/output.json && chmod 600 /tmp/output.json
Input Variables dengan Sensitive Flag
variable "db_password" {
description = "Database administrator password"
type = string
sensitive = true
}
variable "api_keys" {
description = "Map of API keys per service"
type = map(string)
sensitive = true
}
Menggunakan Vault untuk Secrets Management
Untuk manajemen secrets yang lebih baik, integrasikan Terraform dengan Vault:
# Gunakan Vault provider
provider "vault" {
address = "https://vault.example.com:8200"
token = var.vault_token
}
# Ambil secret dari Vault
data "vault_generic_secret" "db_credentials" {
path = "secret/data/database/production"
}
resource "aws_db_instance" "database" {
username = data.vault_generic_secret.db_credentials.data["username"]
password = data.vault_generic_secret.db_credentials.data["password"]
# Secret tidak disimpan di state file Terraform - hanya referensi
}
Provider Security
Provider Terraform adalah plugin yang berkomunikasi dengan API penyedia infrastruktur. Keamanan provider sangat penting karena provider memiliki akses luas ke infrastruktur.
Provider Version Pinning
Selalu pin versi provider untuk menghindari perubahan yang tidak terduga:
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0" # kompatibel dengan 5.x
}
random = {
source = "hashicorp/random"
version = "3.6.0"
}
}
}
Verification Provider Signature
Provider dari HashiCorp Registry telah ditandatangani. Terraform secara otomatis memverifikasi signature provider saat inisialisasi:
# Verifikasi dilakukan otomatis oleh terraform init
# HashiCorp Registry menggunakan GnuPG signature
terraform init
Untuk self-signed provider atau provider dari registry khusus, atur verifikasi:
terraform {
required_providers {
custom = {
source = "example.com/org/custom"
version = "1.0.0"
}
}
}
Provider Credentials
Jangan pernah menyimpan kredensial provider di kode. Gunakan environment variables, Vault, atau OIDC:
# BURUK - hardcoded credentials
provider "aws" {
region = "ap-southeast-1"
access_key = "AKIAIOSFODNN7EXAMPLE"
secret_key = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
}
# BAIK - gunakan environment variables atau credential chain
provider "aws" {
region = "ap-southeast-1"
# Credential diambil dari AWS_DEFAULT_REGION, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY
# Atau dari ~/.aws/credentials
# Atau dari instance profile (EC2)
}
# BAIK - OIDC untuk CI/CD
provider "aws" {
region = "ap-southeast-1"
assume_role {
role_arn = "arn:aws:iam::123456789012:role/terraform-role"
session_name = "terraform-ci"
}
}
Module Sourcing
Module Terraform memungkinkan penggunaan ulang kode infrastruktur. Namun, modul dari sumber yang tidak terpercaya dapat mengandung kode berbahaya.
Pinned Module Versions
# BURUK - versi tidak ditentukan
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
}
# BAIK - versi di-pin
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "5.1.2"
}
# LEBIH BAIK - pin ke commit spesifik
module "vpc" {
source = "github.com/terraform-aws-modules/terraform-aws-vpc?ref=e21d5f8e8c5c4b3a2a1f0d9e8c7b6a5f4e3d2c1"
}
Module Registry Verification
Gunakan modul hanya dari Terraform Registry yang terverifikasi:
# Terverifikasi - ditandai dengan "Verified" badge di registry
module "ec2" {
source = "terraform-aws-modules/ec2-instance/aws"
version = "5.5.0"
name = "web-server"
}
Untuk module internal, gunakan private registry:
# Module dari private registry HCP Terraform
module "internal-base" {
source = "app.terraform.io/example/internal-base/aws"
version = "1.2.0"
}
Audit Module Content
Sebelum menggunakan module baru:
- Audit source code module secara manual.
- Periksa apakah module membutuhkan akses yang tidak semestinya.
- Verifikasi bahwa module tidak mengirim data ke pihak ketiga.
- Gunakan
terraform planuntuk melihat perubahan yang akan dilakukan module.
Sentinel Policies (HashiCorp Cloud Platform)
Sentinel adalah policy-as-code framework dari HashiCorp yang
memungkinkan Anda mendefinisikan kebijakan keamanan yang dijalankan
sebelum terraform apply.
Contoh Sentinel Policy
Kebijakan: Larang security group dengan port 22 (SSH) terbuka ke 0.0.0.0/0:
# deny-public-ssh.sentinel
import "tfplan"
# Cari semua resource aws_security_group_rule
ssh_rules = filter tfplan.resource_changes as _, rc in [
rc.mode is "managed" and
rc.type is "aws_security_group_rule"
]
# Periksa apakah ada aturan SSH yang terbuka ke publik
violations = []
for rule in ssh_rules {
if rule.change.after.type is "ingress" and
rule.change.after.from_port <= 22 and
rule.change.after.to_port >= 22 and
rule.change.after.cidr_blocks contains "0.0.0.0/0" {
violations = append(violations, rule.change.after)
}
}
# Jika ada pelanggaran, reject
if length(violations) > 0 {
print("Found", length(violations), "SSH rules open to 0.0.0.0/0:")
for violation in violations {
print(" -", violation.cidr_blocks, "port 22")
}
}
else {
main = true
}
Kebijakan: Wajib Enkripsi S3 Bucket
# enforce-s3-encryption.sentinel
import "tfplan"
# Filter resource S3 bucket
s3_buckets = filter tfplan.resource_changes as _, rc in [
rc.mode is "managed" and
rc.type is "aws_s3_bucket"
]
# Periksa enkripsi
violations = []
for bucket in s3_buckets {
encryption = bucket.change.after.server_side_encryption_configuration
if encryption is undefined or encryption.rule is undefined {
violations = append(violations, bucket.change.after.bucket)
}
}
# Enforce
main = length(violations) is 0
Integrasi Sentinel di Pipeline
Sentinel dijalankan sebagai run task di HCP Terraform atau Terraform Enterprise:
# GitHub Actions - integrasi dengan HCP Terraform
jobs:
terraform:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Terraform
uses: hashicorp/setup-terraform@v2
with:
cli_config_credentials_token: ${{ secrets.TF_API_TOKEN }}
- name: Terraform Plan
run: terraform plan
- name: Terraform Apply
# Policy check otomatis dijalankan oleh HCP Terraform
run: terraform apply -auto-approve
Sentinel policies dapat dikonfigurasi dengan tiga mode:
- Advisory: Policy dilanggar tetapi tidak memblokir apply.
- Soft mandatory: Policy dilanggar, perlu persetujuan admin.
- Hard mandatory: Policy memblokir apply secara absolut.
Contoh: S3 Backend Lengkap dengan Keamanan
Berikut adalah konfigurasi lengkap backend Terraform yang aman:
# backend-config.hcl - konfigurasi untuk init
bucket = "company-tf-state-prod"
key = "terraform.tfstate"
region = "ap-southeast-1"
encrypt = true
kms_key_id = "arn:aws:kms:ap-southeast-1:123456789012:key/abc123"
dynamodb_table = "tf-state-locks"
profile = "terraform-admin"
Inisialisasi dengan konfigurasi backend:
terraform init -backend-config=backend-config.hcl
Konfigurasi Terraform untuk production:
# main.tf
terraform {
required_version = ">= 1.6.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.40"
}
random = {
source = "hashicorp/random"
version = "~> 3.6"
}
}
backend "s3" {
# Nilai diisi saat init via backend-config.hcl
}
}
provider "aws" {
region = var.aws_region
default_tags {
tags = {
Environment = var.environment
Terraform = "true"
ManagedBy = "devsecops-team"
}
}
}
# Variable definitions
variable "environment" {
description = "Environment name (production, staging, development)"
type = string
}
variable "aws_region" {
description = "AWS region"
type = string
default = "ap-southeast-1"
}
variable "vpc_cidr" {
description = "VPC CIDR block"
type = string
sensitive = false
}
# Remote state dari infrastruktur bersama
data "terraform_remote_state" "networking" {
backend = "s3"
config = {
bucket = "company-tf-state-shared"
key = "networking/terraform.tfstate"
region = "ap-southeast-1"
encrypt = true
}
}
# Module dari registry terverifikasi
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "5.8.1"
name = "${var.environment}-vpc"
cidr = var.vpc_cidr
azs = ["${var.aws_region}a", "${var.aws_region}b"]
private_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
public_subnets = ["10.0.101.0/24", "10.0.102.0/24"]
enable_nat_gateway = true
enable_vpn_gateway = false
tags = {
Environment = var.environment
}
}
# Resource dengan sensitive values
resource "random_password" "db_master" {
length = 24
special = false
}
resource "aws_db_instance" "database" {
identifier = "${var.environment}-database"
engine = "postgres"
engine_version = "16.3"
instance_class = "db.t3.medium"
db_name = "appdb"
username = "appadmin"
password = random_password.db_master.result
vpc_security_group_ids = [aws_security_group.database.id]
db_subnet_group_name = aws_db_subnet_group.database.name
storage_encrypted = true
storage_type = "gp3"
allocated_storage = 100
backup_retention_period = 35
backup_window = "03:00-04:00"
maintenance_window = "sun:04:00-sun:05:00"
deletion_protection = true
skip_final_snapshot = false
final_snapshot_identifier = "${var.environment}-database-final"
enabled_cloudwatch_logs_exports = ["postgresql"]
}
# Output aman
output "db_endpoint" {
value = aws_db_instance.database.endpoint
}
output "db_port" {
value = aws_db_instance.database.port
}
output "db_name" {
value = aws_db_instance.database.db_name
}
output "db_username" {
value = aws_db_instance.database.username
}
output "db_password" {
value = random_password.db_master.result
sensitive = true
}
Kesimpulan
Keamanan Terraform membutuhkan perhatian di setiap lapisan: state
file harus diamankan dengan remote backend terenkripsi dan state
locking; sensitive values harus dilindungi dengan flag
sensitive dan integrasi Vault; provider dan module
harus diverifikasi dan versinya di-pin; dan kebijakan keamanan dapat
ditegakkan dengan Sentinel policies. Dengan menerapkan
praktik-praktik ini, Anda dapat menggunakan Terraform secara aman
untuk mengelola infrastruktur skala besar.