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 →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact