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.

flowchart TD
    A["Hardcoded\nCredentials"] --> B["Data Breach"]
    C["Public\nBucket"] --> B
    D["Overly Broad\nIAM"] --> B
    E["No\nEncryption"] --> B

    style A fill:#ef4444,stroke:#dc2626,color:#fff
    style C fill:#ef4444,stroke:#dc2626,color:#fff
    style D fill:#ef4444,stroke:#dc2626,color:#fff
    style E fill:#ef4444,stroke:#dc2626,color:#fff
    style B fill:#f59e0b,stroke:#d97706,color:#fff

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

flowchart TD
    A["Mistake"] --> B["Impact"]
    B --> C["Detection"]
    C --> D["Fix"]

    style A fill:#ef4444,stroke:#dc2626,color:#fff
    style B fill:#f59e0b,stroke:#d97706,color:#fff
    style C fill:#3b82f6,stroke:#1e40af,color:#fff
    style D fill:#10b981,stroke:#059669,color:#fff

Security Scanning Automation #

# CI/CD security scanning pipeline
name: Security Scan
on: [pull_request]

jobs:
  tfsec:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: aquasecurity/[email protected]
        with:
          soft_fail: true

  checkov:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: bridgecrewio/checkov-action@v12
        with:
          directory: .
          framework: terraform

Security Review Checklist #

# Pre-deploy security checklist:

# 1. Cek tidak ada secret di code
grep -rn "password\|secret\|api_key\|token" --include="*.tf" .
# Harusnya kosong atau hanya reference ke secret manager

# 2. Cek encryption aktif
grep -rn "encrypt" --include="*.tf" .
# Pastikan encrypt = true di semua backend

# 3. Cek public access
grep -rn "0.0.0.0/0\|public" --include="*.tf" .
# Pastikan tidak ada security group terbuka ke public

# 4. Jalankan tfsec
tfsec . --soft-fail
# Review semua warning dan error

# 5. Jalankan checkov
checkov -d . --framework terraform
# Review compliance checks

Security Hardening Checklist #

# Pre-deploy security checklist:

# 1. Secret scanning
gitleaks detect --source . --verbose

# 2. Infrastructure security scanning
tfsec . --soft-fail
checkov -d . --framework terraform

# 3. Check public access
grep -rn "0.0.0.0/0" --include="*.tf" .

# 4. Check encryption
grep -rn "encrypt.*false" --include="*.tf" .

# 5. Check IAM overly permissive
grep -rn 'Action.*"\*"' --include="*.tf" .

# 6. Check deletion protection
grep -rn "prevent_destroy" --include="*.tf" .

Security Scanning Tools #

# tfsec: Terraform security scanner
brew install tfsec
tfsec . --soft-fail

# checkov: Infrastructure-as-Code scanner
pip install checkov
checkov -d . --framework terraform

# terrascan: Compliance scanner
brew install terrascan
terrascan scan -i terraform

# Integrate ke CI/CD
# GitHub Actions:
- name: Run tfsec
  uses: aquasecurity/[email protected]
  with:
    soft_fail: false

- name: Run checkov
  uses: bridgecrewio/checkov-action@v12

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