Common Security Mistake #
Banyak insiden keamanan infrastruktur bukan disebabkan oleh serangan canggih, tapi oleh kesalahan sederhana yang konsisten dilakukan — credentials yang di-commit karena terburu-buru, S3 bucket yang tanpa sadar dibuat public, atau IAM role yang punya akses lebih besar dari yang dibutuhkan karena “lebih mudah begitu”. Mengenali kesalahan-kesalahan ini sebelum melakukannya jauh lebih murah dari memperbaikinya setelah terjadi.
Kesalahan 1 — Hardcode Credentials di Konfigurasi #
# ANTI-PATTERN: Credentials langsung di konfigurasi
provider "aws" {
access_key = "AKIAIOSFODNN7EXAMPLE"
secret_key = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
}
# Atau di variable tanpa sensitive flag
variable "db_password" {
default = "MyP4ssword123" # ✗ Default value ter-commit ke Git
}
# BENAR: Tidak ada credentials eksplisit di konfigurasi
provider "aws" {
region = "ap-southeast-1"
# Credentials dari environment, instance profile, atau OIDC
}
variable "db_password" {
type = string
sensitive = true
# Tidak ada default — nilai wajib dioper dari luar
}
Kesalahan 2 — State File di Git #
# ANTI-PATTERN: terraform.tfstate ter-commit ke Git
git add .
git commit -m "update infra"
# → terraform.tfstate masuk bersama commit lain
# → State berisi password, private key, semua atribut resource — plaintext
# Tanda bahaya:
git log --all -- "*.tfstate" # Cek apakah tfstate pernah ter-commit
# Jika ada output: sudah terlambat, perlu rotasi semua credentials
# BENAR: .gitignore yang tepat
# *.tfstate dalam .gitignore SEBELUM commit pertama
# Remote backend untuk menyimpan state (S3, Terraform Cloud, GCS)
Kesalahan 3 — Resource Publik yang Tidak Disengaja #
# ANTI-PATTERN: S3 bucket tanpa blok public access
resource "aws_s3_bucket" "data" {
bucket = "my-company-data"
# Tidak ada konfigurasi access control
# Default bisa bervariasi tergantung account setting
}
# ANTI-PATTERN: Security group terlalu permisif
resource "aws_security_group" "web" {
ingress {
from_port = 0
to_port = 65535
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"] # ✗ Semua port terbuka ke publik
}
}
# BENAR: Eksplisit blokir public access untuk S3
resource "aws_s3_bucket_public_access_block" "data" {
bucket = aws_s3_bucket.data.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
# BENAR: Security group dengan port minimum yang diperlukan
resource "aws_security_group" "web" {
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"] # HTTPS — memang perlu public
}
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["10.0.0.0/8"] # SSH hanya dari internal network
}
# Tidak ada ingress untuk port lain
}
Kesalahan 4 — Enkripsi yang Terlewat #
# ANTI-PATTERN: RDS tanpa enkripsi
resource "aws_db_instance" "main" {
identifier = "production-db"
engine = "postgres"
instance_class = "db.t3.medium"
storage_type = "gp2"
allocated_storage = 20
# storage_encrypted tidak diset → default false
}
# ANTI-PATTERN: S3 tanpa enkripsi server-side
resource "aws_s3_bucket" "logs" {
bucket = "production-logs"
# Tidak ada server_side_encryption_configuration
}
# ANTI-PATTERN: EBS volume tanpa enkripsi
resource "aws_ebs_volume" "data" {
availability_zone = "ap-southeast-1a"
size = 100
# encrypted tidak diset → default false
}
# BENAR: Enkripsi untuk semua storage
resource "aws_db_instance" "main" {
identifier = "production-db"
engine = "postgres"
instance_class = "db.t3.medium"
storage_encrypted = true # ← Wajib
kms_key_id = aws_kms_key.rds.arn # KMS key custom (bukan default)
}
resource "aws_s3_bucket_server_side_encryption_configuration" "logs" {
bucket = aws_s3_bucket.logs.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "aws:kms"
kms_master_key_id = aws_kms_key.s3.arn
}
}
}
resource "aws_ebs_volume" "data" {
availability_zone = "ap-southeast-1a"
size = 100
encrypted = true # ← Wajib
kms_key_id = aws_kms_key.ebs.arn
}
Kesalahan 5 — Logging dan Monitoring yang Hilang #
# ANTI-PATTERN: RDS tanpa logging
resource "aws_db_instance" "main" {
identifier = "production-db"
# Tidak ada enabled_cloudwatch_logs_exports
# Tidak ada parameter group untuk audit logging
}
# ANTI-PATTERN: S3 tanpa server access logging
resource "aws_s3_bucket" "important_data" {
bucket = "important-data"
# Tidak ada logging block
}
# BENAR: Logging untuk semua resource penting
resource "aws_db_instance" "main" {
identifier = "production-db"
enabled_cloudwatch_logs_exports = [
"postgresql", # Query logs
"upgrade"
]
# Parameter group untuk audit logging
parameter_group_name = aws_db_parameter_group.postgres_audit.name
}
resource "aws_s3_bucket_logging" "important_data" {
bucket = aws_s3_bucket.important_data.id
target_bucket = aws_s3_bucket.access_logs.id # Log ditulis ke bucket terpisah
target_prefix = "important-data-access/"
}
# CloudTrail untuk audit semua API call ke infrastruktur
resource "aws_cloudtrail" "main" {
name = "production-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 # ← Verifikasi log tidak dimodifikasi
}
Kesalahan 6 — Provider Versi Tidak Terkunci #
# ANTI-PATTERN: Provider versi tidak terkunci
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
}
# Tanpa version constraint → selalu download versi terbaru
# Breaking change di versi baru bisa merusak konfigurasi tanpa peringatan
}
}
# ANTI-PATTERN: Constraint terlalu longgar
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 3.0" # Terlalu longgar — bisa naik ke v5 yang breaking
}
}
}
# BENAR: Constraint yang spesifik dengan lock file
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0" # Caret — izinkan patch update tapi tidak major
}
}
required_version = ">= 1.6"
}
# .terraform.lock.hcl — commit ini ke Git!
# Menjamin versi provider yang persis sama di semua environment
Checklist Keamanan Sebelum Apply ke Production #
CHECKLIST KEAMANAN TERRAFORM:
CREDENTIALS:
□ Tidak ada access key / secret key di file .tf atau .tfvars
□ .gitignore sudah include *.tfstate dan *.tfvars berisi secret
□ CI/CD menggunakan OIDC, bukan static credentials
□ Role plan dan apply terpisah dengan permission berbeda
STATE:
□ Remote backend digunakan (bukan local state)
□ State backend dienkripsi (S3 dengan encrypt=true dan KMS key)
□ Akses ke state backend dibatasi dengan IAM policy ketat
□ State locking diaktifkan
RESOURCE CONFIGURATION:
□ Tidak ada security group dengan 0.0.0.0/0 pada port sensitif (22, 3306, 5432)
□ S3 bucket yang menyimpan data punya public access block
□ Semua storage (RDS, EBS, S3) dienkripsi
□ Logging diaktifkan untuk semua resource penting
POLICY & COMPLIANCE:
□ Checkov atau tfsec dijalankan di CI pipeline
□ Semua resource punya tag yang diperlukan (Environment, Owner)
□ Provider versi terkunci di required_providers
□ .terraform.lock.hcl di-commit ke repository
Ringkasan #
- Hardcode credentials di konfigurasi adalah kesalahan paling berbahaya — masuk ke Git history secara permanen dan harus dianggap dikompromikan.
- State file di Git adalah insiden keamanan — state berisi semua atribut resource sebagai plaintext, termasuk password dan API key.
- Resource publik yang tidak disengaja (S3 tanpa public access block, security group terlalu permisif) sering terjadi karena default yang tidak aman — selalu eksplisit.
- Enkripsi untuk semua storage — RDS, S3, EBS semua harus punya enkripsi at rest. Gunakan KMS key custom bukan default untuk audit yang lebih baik.
- Logging dan monitoring bukan opsional di production — CloudTrail, RDS query log, dan S3 access log adalah minimum yang harus ada.
- Gunakan checklist keamanan sebelum setiap apply ke production — lebih mudah mencegah dari awal dibanding memperbaiki setelah ada insiden.
← Sebelumnya: Least Privilege Berikutnya: Monorepo vs Multirepo →