Kenapa Harus Pakai Secrets Manager, Bukan Hardcode Password di Kode?

Satu pertanyaan yang hampir selalu muncul di code review: 'repo-nya private kok, kenapa tidak boleh hardcode password DB-nya langsung di kode?' Pertanyaan ini masuk akal di permukaan, tapi model ancaman yang mendasarinya salah — dan Secrets Manager hadir bukan hanya untuk menyembunyikan string, melainkan untuk menyelesaikan masalah operasional yang jauh lebih dalam dari sekadar visibilitas kode.

TL;DR — Secrets Manager vs Hardcoding

Aspek Hardcoding di Kode AWS Secrets Manager
Rotasi password Manual, rawan downtime Otomatis via Lambda rotation function
Audit akses Tidak ada jejak siapa membaca secret Setiap GetSecretValue tercatat di CloudTrail
Scope paparan Semua yang bisa baca kode, bisa baca secret Dikontrol IAM per resource, per principal
Revoke akses Harus deploy ulang seluruh aplikasi Rotasi secret, akses lama langsung invalid
Risiko git history Secret tersimpan permanen di commit history Tidak ada credential di repository
Multi-environment Rawan salah env, sulit diaudit Secret berbeda per environment, path terpisah

Bagaimana AWS Secrets Manager Bekerja

Sebelum masuk ke alasan operasional, penting untuk memahami mekanisme dasarnya. Secrets Manager menyimpan secret sebagai pasangan key-value terenkripsi menggunakan AWS KMS. Aplikasi tidak pernah memegang credential secara statis — setiap kali butuh password, aplikasi memanggil API GetSecretValue dan mendapat nilai terkini.

sequenceDiagram participant App as Aplikasi (ECS/Lambda) participant SM as Secrets Manager participant KMS as AWS KMS participant CT as CloudTrail App->>SM: GetSecretValue(secret-id) SM->>SM: Verifikasi IAM permission SM->>KMS: Decrypt(encrypted-secret) KMS-->>SM: Plaintext secret SM-->>App: SecretString (nilai terkini) SM->>CT: Log event GetSecretValue
  1. Aplikasi memanggil GetSecretValue — request dikirim ke Secrets Manager endpoint regional.
  2. IAM authorization — Secrets Manager memverifikasi apakah IAM role aplikasi memiliki izin secretsmanager:GetSecretValue pada ARN secret tersebut.
  3. KMS decrypt — Secret yang tersimpan dalam bentuk terenkripsi didekripsi menggunakan KMS key yang dikonfigurasi.
  4. Nilai dikembalikan — Aplikasi menerima nilai secret saat ini, bukan nilai yang di-cache dari waktu deploy.
  5. CloudTrail mencatat — Setiap pemanggilan GetSecretValue menghasilkan event audit di CloudTrail secara otomatis.

Kenapa Repo Private Bukan Jaminan Keamanan

Asumsi 'repo private = aman' mengabaikan beberapa vektor serangan nyata yang sering terjadi di lingkungan produksi.

Pertama, git history tidak bisa dihapus dengan mudah. Kalau password pernah di-commit, bahkan satu kali, nilai itu tersimpan di seluruh clone lokal yang pernah ada. Force-push atau git filter-branch tidak menjamin semua salinan lokal developer sudah bersih.

Kedua, scope akses repo tidak sama dengan scope akses yang seharusnya. Developer yang butuh baca kode frontend tidak seharusnya otomatis bisa baca password database produksi. Tapi kalau keduanya ada di repo yang sama, itulah yang terjadi. Dengan Secrets Manager, akses ke secret dikontrol IAM secara terpisah dari akses ke kode.

Ketiga, CI/CD pipeline sering menjadi titik bocor. Build log, artifact, environment variable yang di-print saat debugging — semua ini bisa mengekspos credential yang hardcoded tanpa disadari.

Analogi yang tepat: menyimpan kunci rumah di bawah keset depan pintu. Semua orang yang 'tahu caranya' bisa masuk. Secrets Manager lebih seperti brankas dengan log siapa yang membukanya dan kapan.

Rotasi Password Otomatis — Ini yang Sering Diabaikan

Fitur rotasi otomatis Secrets Manager adalah alasan operasional terkuat untuk migrasi dari hardcoding, dan ini yang paling sering diremehkan tim engineering.

sequenceDiagram participant Sched as Rotation Schedule participant SM as Secrets Manager participant Lambda as Rotation Lambda participant DB as Database (RDS) participant App as Aplikasi Sched->>SM: Trigger rotasi (setiap N hari) SM->>Lambda: Invoke rotation function Lambda->>DB: Buat password baru DB-->>Lambda: Password baru aktif Lambda->>SM: UpdateSecretVersionStage
(simpan nilai baru) Lambda->>DB: Verifikasi koneksi dengan password baru DB-->>Lambda: Koneksi berhasil Lambda-->>SM: Rotasi selesai App->>SM: GetSecretValue (request berikutnya) SM-->>App: Password baru (otomatis)
  1. Rotation schedule terpicu — Secrets Manager memanggil Lambda rotation function sesuai jadwal yang dikonfigurasi (misalnya setiap 30 hari).
  2. Lambda membuat password baru — Rotation function membuat credential baru di database target (RDS, Redshift, dll.) menggunakan API database yang sesuai.
  3. Secret diperbarui — Nilai baru disimpan di Secrets Manager. Versi lama tetap tersedia sementara dengan label AWSPREVIOUS.
  4. Verifikasi — Lambda memverifikasi credential baru berfungsi sebelum menandai rotasi sebagai selesai.
  5. Aplikasi otomatis pakai nilai baru — Karena aplikasi selalu memanggil GetSecretValue (bukan membaca nilai statis), credential baru langsung digunakan tanpa deploy ulang.

Dengan hardcoding, rotasi password berarti: update kode → commit → deploy → restart aplikasi. Setiap langkah adalah peluang downtime dan kesalahan manusia. Dengan Secrets Manager, aplikasi yang memanggil GetSecretValue setiap koneksi (atau dengan TTL cache yang pendek) akan otomatis mengambil credential baru.

Implementasi Praktis — Migrasi dari Hardcode ke Secrets Manager

Berikut langkah konkret untuk menyimpan dan mengambil secret database menggunakan AWS CLI dan kode aplikasi.

Langkah 1: Simpan Secret ke Secrets Manager

Buat secret baru yang menyimpan credential database. Format JSON direkomendasikan untuk secret yang berisi beberapa field.

aws secretsmanager create-secret \
  --name 'prod/myapp/db-credentials' \
  --description 'Kredensial database produksi untuk MyApp' \
  --secret-string '{"username":"dbadmin","password":"P@ssw0rd-contoh","host":"mydb.cluster-xyz.us-east-1.rds.amazonaws.com","port":"5432","dbname":"myappdb"}' \
  --region us-east-1

Langkah 2: Buat IAM Policy untuk Aplikasi

Aplikasi hanya perlu izin membaca secret spesifik — bukan semua secret di akun. Prinsip least privilege diterapkan di level ARN secret.

🔽 Klik untuk lihat IAM Policy JSON
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowReadAppSecret",
      "Effect": "Allow",
      "Action": [
        "secretsmanager:GetSecretValue",
        "secretsmanager:DescribeSecret"
      ],
      "Resource": "arn:aws:secretsmanager:us-east-1:123456789012:secret:prod/myapp/db-credentials-*"
    },
    {
      "Sid": "AllowKMSDecrypt",
      "Effect": "Allow",
      "Action": [
        "kms:Decrypt"
      ],
      "Resource": "arn:aws:kms:us-east-1:123456789012:key/your-kms-key-id"
    }
  ]
}

Perhatikan wildcard -* di akhir ARN secret — Secrets Manager menambahkan suffix acak 6 karakter saat secret dibuat, sehingga ARN eksak perlu dicek dengan describe-secret atau wildcard digunakan untuk policy.

Langkah 3: Verifikasi Akses dari CLI

Sebelum deploy ke aplikasi, verifikasi bahwa role yang benar bisa membaca secret.

aws secretsmanager get-secret-value \
  --secret-id 'prod/myapp/db-credentials' \
  --region us-east-1 \
  --query 'SecretString' \
  --output text

Langkah 4: Ambil Secret di Kode Aplikasi (Python)

Contoh ini menggunakan boto3. Pola yang direkomendasikan adalah mengambil secret saat startup koneksi, bukan di-cache selamanya — ini memungkinkan rotasi bekerja tanpa restart.

🔽 Klik untuk lihat contoh kode Python
import boto3
import json
from botocore.exceptions import ClientError

def get_db_credentials(secret_name: str, region_name: str) -> dict:
    client = boto3.client('secretsmanager', region_name=region_name)
    try:
        response = client.get_secret_value(SecretId=secret_name)
    except ClientError as e:
        # Tangani error spesifik sesuai kebutuhan aplikasi
        raise e

    secret = json.loads(response['SecretString'])
    return secret

# Penggunaan
creds = get_db_credentials(
    secret_name='prod/myapp/db-credentials',
    region_name='us-east-1'
)

connection = connect_to_db(
    host=creds['host'],
    port=int(creds['port']),
    user=creds['username'],
    password=creds['password'],
    database=creds['dbname']
)

Langkah 5: Aktifkan Rotasi Otomatis

Untuk RDS, Secrets Manager menyediakan managed rotation function yang bisa diaktifkan langsung. Pastikan Lambda rotation function sudah ada di akun (bisa di-deploy via AWS console atau SAR).

aws secretsmanager rotate-secret \
  --secret-id 'prod/myapp/db-credentials' \
  --rotation-rules '{"AutomaticallyAfterDays": 30}' \
  --rotation-lambda-arn 'arn:aws:lambda:us-east-1:123456789012:function:SecretsManagerRDSPostgreSQLRotationSingleUser' \
  --region us-east-1

Pola Kesalahan yang Sering Terjadi — Diagnosis Lapangan

Ini skenario nyata yang sering muncul saat tim pertama kali migrasi ke Secrets Manager.

Gejala: Aplikasi berjalan normal di lokal tapi gagal di ECS dengan error AccessDeniedException saat memanggil GetSecretValue.

Diagnosis awal yang salah: Tim langsung curiga ke VPC endpoint atau network connectivity, karena aplikasi 'tidak bisa reach' Secrets Manager.

Penyebab sebenarnya: ECS task role tidak memiliki izin secretsmanager:GetSecretValue pada ARN secret yang spesifik. Di lokal, developer menggunakan AWS credentials pribadi yang memiliki akses lebih luas. Dua identity yang berbeda, dua set izin yang berbeda.

Fix: Verifikasi identity yang digunakan task ECS, lalu pastikan policy terpasang di task role yang benar — bukan execution role.

# Cek task role yang digunakan ECS task
aws ecs describe-tasks \
  --cluster nama-cluster-anda \
  --tasks arn:aws:ecs:us-east-1:123456789012:task/nama-cluster-anda/task-id-anda \
  --region us-east-1 \
  --query 'tasks[0].taskDefinitionArn'

# Cek task definition untuk melihat taskRoleArn
aws ecs describe-task-definition \
  --task-definition nama-task-definition:versi \
  --region us-east-1 \
  --query 'taskDefinition.taskRoleArn'

Perbedaan antara taskRoleArn (identity yang digunakan kode aplikasi) dan executionRoleArn (identity yang digunakan ECS agent untuk pull image dan push log) adalah sumber kebingungan yang hampir universal di tim yang baru mulai pakai ECS.

Audit Trail — Siapa Mengakses Secret dan Kapan

Setiap panggilan GetSecretValue menghasilkan event di CloudTrail. Ini bukan fitur opsional — ini terjadi secara otomatis selama CloudTrail aktif di region tersebut.

aws cloudtrail lookup-events \
  --lookup-attributes AttributeKey=EventName,AttributeValue=GetSecretValue \
  --region us-east-1 \
  --query 'Events[*].{Time:EventTime,User:Username,Source:EventSource}' \
  --output table

Dengan hardcoding, tidak ada cara untuk tahu siapa yang 'menggunakan' password tersebut setelah kode di-deploy. Dengan Secrets Manager, setiap akses tercatat dengan identity, timestamp, dan source IP — informasi yang krusial saat investigasi insiden.

Wrap-Up dan Langkah Selanjutnya

Menyimpan credential di kode bukan hanya masalah keamanan — ini masalah operasional. Rotasi manual, audit yang tidak ada, dan scope akses yang terlalu luas adalah utang teknis yang akan jatuh tempo saat paling tidak nyaman: di tengah insiden produksi.

Langkah konkret berikutnya:

Glosarium

Istilah Penjelasan
Secret Rotation Proses otomatis mengganti nilai credential (password, API key) secara berkala tanpa intervensi manual.
GetSecretValue API action Secrets Manager untuk mengambil nilai secret saat ini. Setiap panggilan dicatat di CloudTrail.
KMS CMK Customer Managed Key di AWS Key Management Service — kunci enkripsi yang digunakan Secrets Manager untuk mengenkripsi secret.
Task Role (ECS) IAM role yang diasosiasikan dengan ECS task, digunakan oleh kode aplikasi untuk memanggil AWS API. Berbeda dari execution role.
AWSPREVIOUS Label versi secret di Secrets Manager yang menandai credential sebelum rotasi terakhir — tersedia sementara untuk backward compatibility.

Related Posts

Komentar

Postingan populer dari blog ini

EC2 Tidak Bisa Akses Internet di Custom VPC: Cara Pasang Internet Gateway dan Update Route Table