HashiCorp Vault vs AWS Secrets Manager: Perbandingan Mendalam + Contoh Implementasi di Golang
16 min read

HashiCorp Vault vs AWS Secrets Manager: Perbandingan Mendalam + Contoh Implementasi di Golang

Secret management adalah salah satu keputusan arsitektur yang paling sering diremehkan. Banyak tim masih menyimpan database credential di environment variable, API key di config file, atau token OAuth di kode sumber — dan baru menyadari risikonya setelah insiden terjadi. Dua solusi yang paling sering dibandingkan untuk mengatasi masalah ini adalah HashiCorp Vault dan AWS Secrets Manager. Keduanya memecahkan problem yang sama di permukaan, tapi filosofi dan kemampuannya berbeda secara fundamental. Artikel ini membahas perbedaan tersebut secara mendalam, lengkap dengan implementasi nyata di Golang, sehingga kamu bisa memilih dengan tepat sesuai konteks.

Mengapa Secret Management Itu Penting

Sebelum membandingkan keduanya, penting untuk memahami mengapa pendekatan tradisional gagal dan apa konsekuensinya.

Menyimpan secret di environment variable terlihat aman, tapi environment variable bisa bocor melalui log aplikasi, error message, proses dump, atau saat ditransmisikan ke monitoring tools. Masalah yang lebih serius: secret statis tidak punya masa berlaku. Jika credential database bocor hari ini, credential itu tetap valid sampai seseorang secara manual menggantinya — yang bisa memakan waktu berhari-hari atau bahkan tidak pernah dilakukan.

Secret management yang benar harus menjawab empat pertanyaan ini:

  • Penyimpanan: di mana secret disimpan dan bagaimana ia dienkripsi?
  • Akses: siapa yang boleh mengambil secret ini dan dalam kondisi apa?
  • Rotasi: seberapa sering secret diganti, dan apakah prosesnya otomatis?
  • Audit: siapa yang sudah mengakses secret ini, kapan, dan dari mana?

HashiCorp Vault dan AWS Secrets Manager menjawab keempat pertanyaan ini, tapi dengan pendekatan yang berbeda.


Filosofi Dasar

Memahami filosofi di balik sebuah tool akan membantumu memprediksi perilakunya di edge case dan mengevaluasi apakah ia cocok dengan kebutuhan jangka panjang.

HashiCorp Vault

Vault dibangun di atas satu prinsip: secrets should be short-lived, dynamic, and tightly controlled. Ini bukan sekadar tagline — ia menentukan seluruh arsitektur Vault.

Vault berfungsi sebagai security broker, bukan sekadar secret store. Ketika aplikasi membutuhkan credential database, Vault tidak mengembalikan credential yang sudah tersimpan — ia membuat credential baru secara on-demand, memberikannya ke aplikasi, lalu otomatis mencabut credential itu setelah TTL (Time To Live) habis. Credential yang punya TTL berarti window of exposure-nya terbatas: bahkan jika credential bocor, ia akan kadaluarsa dalam waktu yang sudah ditentukan.

Selain dynamic secrets, Vault juga menerapkan identity-based access yang vendor-agnostic. Vault bisa mengautentikasi workload menggunakan Kubernetes Service Account, AWS IAM Role, Google Cloud Service Account, LDAP, OIDC, dan puluhan metode lainnya. Ini membuatnya cocok untuk arsitektur multi-cloud atau hybrid.

AWS Secrets Manager

AWS Secrets Manager dibangun dengan filosofi yang berbeda: managed, simple, and tightly integrated with AWS. Fokusnya adalah kemudahan operasional, bukan fleksibilitas maksimum.

Secrets Manager adalah secure key-value store dengan kemampuan rotasi. Secret yang kamu simpan bersifat statis — ia tidak di-generate on-demand. Yang bisa dilakukan adalah mengatur rotasi periodik (misalnya setiap 30 hari) menggunakan Lambda function yang sudah disiapkan AWS untuk layanan-layanan populer seperti RDS, Redshift, dan DocumentDB.

Karena fully managed, kamu tidak perlu memikirkan high availability, backup, atau patch management. Akses dikontrol sepenuhnya melalui IAM Policy, yang berarti jika kamu sudah bekerja dengan AWS, kurva belajarnya sangat landai.


Perbandingan Fitur

Perbedaan filosofis di atas berimplikasi langsung pada fitur yang tersedia. Tabel berikut merangkum perbedaan utamanya:

AspekHashiCorp VaultAWS Secrets Manager
Model deploymentSelf-hosted atau HCP (cloud managed)Fully managed AWS
Dynamic secrets✓ Ya — credential dibuat on-demand✗ Tidak
Lease dan TTL✓ Ya — secret kadaluarsa otomatis✗ Tidak
Rotasi secretFleksibel, bisa customBuilt-in untuk layanan AWS
Policy engineVault Policy (HCL) yang granularIAM Policy
Multi-cloud / on-prem✓ Ya — vendor agnostic✗ AWS only
Auth methods20+ metode (K8s, OIDC, LDAP, cloud IAM)AWS IAM saja
Encryption as a Service✓ Ya — Transit Secrets Engine✗ Tidak
Overhead operasionalTinggi (butuh tim untuk mengelola)Rendah (fully managed)
BiayaOpen source gratis; HCP berbayar$0.40/secret/bulan + $0.05/10K API call
Integrasi AWS nativeButuh konfigurasi tambahanSangat mulus

Satu hal yang perlu digarisbawahi: kolom “Dynamic secrets” adalah pembeda paling signifikan antara keduanya. Ini bukan fitur kecil — ini perbedaan paradigma. Jika keamanan adalah prioritas utama dan kamu ingin meminimalisir dampak credential yang bocor, dynamic secrets adalah fitur yang tidak bisa ditawar.


Arsitektur dan Alur Kerja

Cara kedua tool ini bekerja secara internal mempengaruhi bagaimana kamu mendesain sistem.

Alur Kerja HashiCorp Vault

sequenceDiagram
    participant App as Aplikasi
    participant Vault as HashiCorp Vault
    participant DB as Database

    App->>Vault: Autentikasi (K8s SA / IAM / Token)
    Vault-->>App: Vault Token (dengan TTL)
    App->>Vault: Request credential (database/creds/my-role)
    Vault->>DB: CREATE USER vault_xyz WITH PASSWORD '...'
    DB-->>Vault: User berhasil dibuat
    Vault-->>App: username=vault_xyz, password=..., lease_duration=1h
    App->>DB: Koneksi dengan credential sementara
    Note over Vault,DB: Setelah TTL habis...
    Vault->>DB: DROP USER vault_xyz

Perhatikan bahwa credential tidak pernah “disimpan” di mana pun — ia dibuat saat dibutuhkan dan dihapus setelah kadaluarsa. Ini adalah zero-trust approach yang sesungguhnya.

Alur Kerja AWS Secrets Manager

sequenceDiagram
    participant App as Aplikasi
    participant SM as Secrets Manager
    participant DB as Database
    participant Lambda as Rotation Lambda

    App->>SM: GetSecretValue("prod/myapp/db")
    SM-->>App: {"username": "dbuser", "password": "..."}
    App->>DB: Koneksi dengan credential statis
    Note over SM,Lambda: Setiap 30 hari (rotasi)...
    Lambda->>DB: ALTER USER dbuser PASSWORD '...'
    Lambda->>SM: PutSecretValue (password baru)

Model Secrets Manager lebih sederhana: ada satu credential yang hidup lama, dan secara periodik diperbarui nilainya. Konsekuensinya: jika credential bocor, ia valid sampai rotasi berikutnya terjadi.


Implementasi di Golang: HashiCorp Vault

Mari lihat implementasi nyata mengambil secret dari Vault menggunakan Golang. Kita akan bahas dua skenario: mengambil static secret (KV store) dan mengambil dynamic database credential.

Instalasi

go get github.com/hashicorp/vault/[email protected]

Mengambil Static Secret dari KV Store

Ini adalah use case paling dasar — menyimpan dan mengambil key-value secret dari Vault KV Secrets Engine.

package vault

import (
    "context"
    "fmt"
    "log"

    vault "github.com/hashicorp/vault/api"
    auth "github.com/hashicorp/vault/api/auth/kubernetes"
)

type VaultClient struct {
    client *vault.Client
}

// NewVaultClient membuat Vault client dengan autentikasi Kubernetes.
// Ini adalah pendekatan yang direkomendasikan untuk workload di K8s.
func NewVaultClient(vaultAddr, role string) (*VaultClient, error) {
    config := vault.DefaultConfig()
    config.Address = vaultAddr

    client, err := vault.NewClient(config)
    if err != nil {
        return nil, fmt.Errorf("gagal membuat vault client: %w", err)
    }

    // BENAR: gunakan Kubernetes auth, bukan hardcode token
    k8sAuth, err := auth.NewKubernetesAuth(role)
    if err != nil {
        return nil, fmt.Errorf("gagal inisialisasi k8s auth: %w", err)
    }

    authInfo, err := client.Auth().Login(context.Background(), k8sAuth)
    if err != nil {
        return nil, fmt.Errorf("gagal login ke vault: %w", err)
    }
    if authInfo == nil {
        return nil, fmt.Errorf("login berhasil tapi tidak ada auth info")
    }

    return &VaultClient{client: client}, nil
}

type DBSecret struct {
    Username string
    Password string
}

// GetDBSecret mengambil database credential dari KV Secrets Engine.
// Path format untuk KV v2: secret/data/<path>
func (v *VaultClient) GetDBSecret(ctx context.Context, path string) (*DBSecret, error) {
    secret, err := v.client.KVv2("secret").Get(ctx, path)
    if err != nil {
        return nil, fmt.Errorf("gagal membaca secret di path %s: %w", path, err)
    }

    if secret == nil || secret.Data == nil {
        return nil, fmt.Errorf("secret tidak ditemukan di path: %s", path)
    }

    username, ok := secret.Data["username"].(string)
    if !ok {
        return nil, fmt.Errorf("field 'username' tidak ditemukan atau bukan string")
    }

    password, ok := secret.Data["password"].(string)
    if !ok {
        return nil, fmt.Errorf("field 'password' tidak ditemukan atau bukan string")
    }

    return &DBSecret{Username: username, Password: password}, nil
}

Mengambil Dynamic Database Credential

Ini adalah fitur yang tidak dimiliki AWS Secrets Manager. Vault men-generate credential database baru setiap kali diminta, dengan TTL yang sudah dikonfigurasi.

// GetDynamicDBCredential meminta Vault untuk membuat database credential baru.
// Setiap pemanggilan menghasilkan credential unik dengan TTL terbatas.
func (v *VaultClient) GetDynamicDBCredential(ctx context.Context, role string) (*DBSecret, error) {
    // Path ini adalah Database Secrets Engine, bukan KV
    // Format: database/creds/<role-name>
    path := fmt.Sprintf("database/creds/%s", role)

    secret, err := v.client.Logical().ReadWithContext(ctx, path)
    if err != nil {
        return nil, fmt.Errorf("gagal generate dynamic credential untuk role %s: %w", role, err)
    }

    if secret == nil {
        return nil, fmt.Errorf("tidak ada credential yang dikembalikan untuk role: %s", role)
    }

    username, ok := secret.Data["username"].(string)
    if !ok {
        return nil, fmt.Errorf("field 'username' tidak ada dalam response")
    }

    password, ok := secret.Data["password"].(string)
    if !ok {
        return nil, fmt.Errorf("field 'password' tidak ada dalam response")
    }

    // Catat lease ID untuk keperluan renewal atau revocation manual
    log.Printf("Dynamic credential berhasil dibuat. Lease ID: %s, TTL: %s",
        secret.LeaseID, secret.LeaseDuration)

    return &DBSecret{Username: username, Password: password}, nil
}

// RenewLease memperpanjang masa berlaku sebuah lease sebelum kadaluarsa.
// Panggil ini jika aplikasi masih butuh credential tapi TTL-nya hampir habis.
func (v *VaultClient) RenewLease(ctx context.Context, leaseID string, increment int) error {
    _, err := v.client.Sys().RenewWithContext(ctx, leaseID, increment)
    if err != nil {
        return fmt.Errorf("gagal memperpanjang lease %s: %w", leaseID, err)
    }
    return nil
}
Saat menggunakan dynamic database credential, jangan cache credential tanpa memperhatikan TTL-nya. Jika kamu menyimpan credential di memory dan TTL habis, semua koneksi database yang menggunakan credential itu akan ditolak. Implementasikan mekanisme renewal atau buat credential baru sebelum yang lama kadaluarsa.

Implementasi di Golang: AWS Secrets Manager

AWS Secrets Manager menggunakan AWS SDK v2. Model penggunaannya lebih sederhana karena tidak ada konsep lease atau renewal.

Instalasi

go get github.com/aws/aws-sdk-go-v2/config
go get github.com/aws/aws-sdk-go-v2/service/secretsmanager

Mengambil Secret

package secrets

import (
    "context"
    "encoding/json"
    "fmt"

    "github.com/aws/aws-sdk-go-v2/aws"
    "github.com/aws/aws-sdk-go-v2/config"
    "github.com/aws/aws-sdk-go-v2/service/secretsmanager"
)

type SecretsManagerClient struct {
    client *secretsmanager.Client
}

// NewSecretsManagerClient membuat client dengan konfigurasi dari environment.
// Autentikasi ditangani secara otomatis melalui IAM Role yang di-attach ke instance/pod.
func NewSecretsManagerClient(ctx context.Context, region string) (*SecretsManagerClient, error) {
    cfg, err := config.LoadDefaultConfig(ctx,
        config.WithRegion(region),
    )
    if err != nil {
        return nil, fmt.Errorf("gagal memuat AWS config: %w", err)
    }

    return &SecretsManagerClient{
        client: secretsmanager.NewFromConfig(cfg),
    }, nil
}

type DBSecret struct {
    Username string `json:"username"`
    Password string `json:"password"`
    Host     string `json:"host"`
    Port     int    `json:"port"`
    DBName   string `json:"dbname"`
}

// GetDBSecret mengambil dan memparsing database secret dari Secrets Manager.
// secretID bisa berupa nama secret atau ARN-nya.
func (s *SecretsManagerClient) GetDBSecret(ctx context.Context, secretID string) (*DBSecret, error) {
    input := &secretsmanager.GetSecretValueInput{
        SecretId: aws.String(secretID),
    }

    result, err := s.client.GetSecretValue(ctx, input)
    if err != nil {
        return nil, fmt.Errorf("gagal mengambil secret %s: %w", secretID, err)
    }

    if result.SecretString == nil {
        return nil, fmt.Errorf("secret %s tidak mengandung string value", secretID)
    }

    var secret DBSecret
    if err := json.Unmarshal([]byte(*result.SecretString), &secret); err != nil {
        return nil, fmt.Errorf("gagal parse secret JSON: %w", err)
    }

    return &secret, nil
}

// GetSecretVersion mengambil versi spesifik dari sebuah secret.
// Berguna saat rotasi sedang berjalan dan kamu butuh versi sebelumnya (AWSPREVIOUS).
func (s *SecretsManagerClient) GetSecretVersion(ctx context.Context, secretID, versionStage string) (*DBSecret, error) {
    input := &secretsmanager.GetSecretValueInput{
        SecretId:     aws.String(secretID),
        VersionStage: aws.String(versionStage), // "AWSCURRENT" atau "AWSPREVIOUS"
    }

    result, err := s.client.GetSecretValue(ctx, input)
    if err != nil {
        return nil, fmt.Errorf("gagal mengambil secret %s (stage: %s): %w", secretID, versionStage, err)
    }

    var secret DBSecret
    if err := json.Unmarshal([]byte(*result.SecretString), &secret); err != nil {
        return nil, fmt.Errorf("gagal parse secret JSON: %w", err)
    }

    return &secret, nil
}

Caching dengan Refresh Otomatis

Memanggil Secrets Manager setiap request adalah anti-pattern yang umum terjadi — selain menambah latency, ini juga meningkatkan biaya API call. Solusinya adalah caching dengan TTL.

package secrets

import (
    "context"
    "sync"
    "time"
)

type CachedSecret struct {
    value     *DBSecret
    fetchedAt time.Time
    ttl       time.Duration
}

func (c *CachedSecret) isExpired() bool {
    return time.Since(c.fetchedAt) > c.ttl
}

type CachingSecretsClient struct {
    client *SecretsManagerClient
    cache  map[string]*CachedSecret
    mu     sync.RWMutex
    ttl    time.Duration
}

func NewCachingSecretsClient(client *SecretsManagerClient, cacheTTL time.Duration) *CachingSecretsClient {
    return &CachingSecretsClient{
        client: client,
        cache:  make(map[string]*CachedSecret),
        ttl:    cacheTTL,
    }
}

// GetDBSecret mengembalikan secret dari cache jika masih valid,
// atau fetch ulang dari Secrets Manager jika sudah kadaluarsa.
func (c *CachingSecretsClient) GetDBSecret(ctx context.Context, secretID string) (*DBSecret, error) {
    c.mu.RLock()
    cached, exists := c.cache[secretID]
    c.mu.RUnlock()

    if exists && !cached.isExpired() {
        return cached.value, nil
    }

    // Cache miss atau sudah expired — fetch dari Secrets Manager
    secret, err := c.client.GetDBSecret(ctx, secretID)
    if err != nil {
        // Jika fetch gagal tapi masih ada cache (meskipun expired), kembalikan yang lama
        // daripada return error dan membuat aplikasi down
        if exists {
            return cached.value, nil
        }
        return nil, err
    }

    c.mu.Lock()
    c.cache[secretID] = &CachedSecret{
        value:     secret,
        fetchedAt: time.Now(),
        ttl:       c.ttl,
    }
    c.mu.Unlock()

    return secret, nil
}
AWS juga menyediakan library resmi aws/aws-secretsmanager-caching-go yang mengimplementasikan caching dengan refresh otomatis. Library ini lebih battle-tested untuk production dan menangani edge case seperti rotasi yang sedang berjalan. Pertimbangkan menggunakannya daripada implementasi caching custom.

Perbandingan Kode: Fitur yang Sama

Untuk perbandingan apple-to-apple, berikut implementasi fungsi yang identik menggunakan kedua tool: membuat database connection pool dengan secret yang diambil secara aman.

// ANTI-PATTERN: hardcode atau baca dari env var langsung
// ✗ Tidak ada enkripsi, tidak ada audit trail, tidak ada rotasi
func connectDBUnsafe() (*sql.DB, error) {
    dsn := fmt.Sprintf("postgres://%s:%s@localhost/mydb",
        os.Getenv("DB_USER"),   // ✗ env var bisa bocor ke log
        os.Getenv("DB_PASS"),   // ✗ tidak punya TTL, valid selamanya
    )
    return sql.Open("postgres", dsn)
}

// BENAR dengan Vault: dynamic credential, auto-expire
// ✓ Credential baru setiap koneksi, expired otomatis
func connectDBWithVault(vaultClient *VaultClient) (*sql.DB, error) {
    cred, err := vaultClient.GetDynamicDBCredential(context.Background(), "my-app-role")
    if err != nil {
        return nil, fmt.Errorf("gagal mendapat credential dari vault: %w", err)
    }

    dsn := fmt.Sprintf("postgres://%s:%s@localhost/mydb", cred.Username, cred.Password)
    return sql.Open("postgres", dsn)
}

// BENAR dengan Secrets Manager: static credential dengan caching
// ✓ Terenkripsi, ada audit trail, bisa di-rotate periodik
func connectDBWithSecretsManager(smClient *CachingSecretsClient) (*sql.DB, error) {
    secret, err := smClient.GetDBSecret(context.Background(), "prod/myapp/db")
    if err != nil {
        return nil, fmt.Errorf("gagal mendapat credential dari secrets manager: %w", err)
    }

    dsn := fmt.Sprintf("postgres://%s:%s@%s:%d/%s",
        secret.Username, secret.Password,
        secret.Host, secret.Port, secret.DBName,
    )
    return sql.Open("postgres", dsn)
}

Decision Tree — Pilih yang Tepat

Gunakan diagram berikut sebagai panduan awal. Setelah menemukan titik akhirnya, baca penjelasan skenario di bagian selanjutnya untuk konteks lebih lengkap.

flowchart TD
    A{Infrastruktur kamu\n100% di AWS?} -- Ya --> B{Butuh dynamic\nsecrets atau\nshort-lived credential?}
    A -- Tidak / Hybrid --> C{Punya tim ops\nyang bisa kelola\ninfrastruktur tambahan?}

    B -- Ya --> D[HashiCorp Vault\natau HCP Vault]
    B -- Tidak --> E{Butuh secret\ndi luar ekosistem\nAWS?}

    E -- Tidak --> F[AWS Secrets Manager]
    E -- Ya --> D

    C -- Ya --> G{Prioritas utama\nkeamanan atau\nkemudahan?}
    C -- Tidak --> H[AWS Secrets Manager\n+ AWS services lainnya]

    G -- Keamanan / Kontrol --> D
    G -- Kemudahan / Kecepatan --> H

    style D fill:#e8f5e9,stroke:#43a047
    style F fill:#e3f2fd,stroke:#1e88e5
    style H fill:#e3f2fd,stroke:#1e88e5

Faktor-faktor Keputusan

Tidak ada pilihan yang selalu benar. Berikut faktor-faktor yang harus dipertimbangkan secara konkret.

1. Overhead Operasional

Vault membutuhkan komitmen infrastruktur yang serius. Kamu perlu mengelola high availability (minimal 3 node untuk production), storage backend (Integrated Storage atau Consul), backup, upgrade, dan monitoring Vault itu sendiri. Jika tim kamu belum punya pengalaman dengan ini, perkirakan beberapa minggu untuk setup yang proper.

HashiCorp Cloud Platform (HCP) Vault menawarkan Vault sebagai managed service, yang menghilangkan overhead operasional ini — tapi dengan biaya yang signifikan. Untuk sebagian besar startup, HCP Vault di level Standard bisa lebih mahal dari seluruh biaya AWS Secrets Manager.

AWS Secrets Manager, di sisi lain, zero operational overhead. Kamu cukup panggil API-nya, AWS yang mengurus selebihnya.

2. Model Keamanan

Vault menerapkan zero-trust secara lebih ketat. Dynamic secrets berarti credential yang bocor menjadi tidak berguna setelah TTL-nya habis — window of exposure sangat terbatas. Vault juga mendukung response wrapping (token untuk mengambil secret hanya bisa digunakan sekali) dan cubbyhole (penyimpanan private per token).

Secrets Manager menggunakan perimeter security model — akses dikontrol di level IAM. Selama IAM Role-nya dikonfigurasi dengan benar, aksesnya aman. Tapi jika credential yang tersimpan bocor (misalnya karena aplikasi di-compromise), credential itu tetap valid sampai rotasi berikutnya.

3. Fleksibilitas Auth dan Multi-Cloud

Ini adalah salah satu keunggulan terbesar Vault. Vault mendukung lebih dari 20 auth method: Kubernetes Service Account, AWS IAM, GCP Service Account, Azure Managed Identity, GitHub, LDAP, OIDC, dan banyak lagi. Ini membuatnya cocok untuk organisasi yang punya workload di beberapa cloud atau on-premise.

Secrets Manager hanya mendukung AWS IAM. Jika kamu punya workload di GCP yang perlu mengakses secret yang sama, kamu butuh solusi tambahan.

4. Kemampuan Beyond Secret Storage

Vault adalah platform keamanan yang jauh lebih luas dari sekadar secret storage:

  • Transit Secrets Engine: enkripsi/dekripsi data tanpa menyimpan data di Vault (Encryption as a Service)
  • PKI Secrets Engine: certificate authority internal, issue dan revoke certificate secara programatik
  • SSH Secrets Engine: signed SSH certificate dengan TTL pendek, menggantikan static SSH key
  • TOTP Secrets Engine: generate TOTP token untuk MFA

Secrets Manager fokus pada satu hal: menyimpan dan merotasi secret dengan aman. Jika kamu butuh kemampuan di atas, Secrets Manager tidak bisa membantu.


Rekomendasi Berdasarkan Skenario

Startup AWS-native, Tim Kecil

Pilihan: AWS Secrets Manager

Tim kecil tidak punya bandwidth untuk mengelola infrastruktur Vault. Secrets Manager menyediakan keamanan yang lebih dari cukup untuk sebagian besar startup: enkripsi at-rest dan in-transit, audit trail via CloudTrail, rotasi otomatis untuk RDS, dan integrasi mulus dengan ECS dan Lambda. Mulai di sini dan evaluasi ulang ketika kebutuhan bertambah kompleks.

Platform Engineering di Kubernetes, Multi-Cloud

Pilihan: HashiCorp Vault (Self-hosted atau HCP)

Kubernetes dan Vault adalah kombinasi yang sangat natural. Vault Agent Injector atau Vault Secrets Operator bisa secara otomatis inject secret ke pod sebagai file atau environment variable, dengan renewal otomatis. Jika kamu punya workload di beberapa cloud, Vault menyediakan satu control plane untuk semua secret — tidak perlu mengelola secret di setiap cloud secara terpisah.

Enterprise dengan Regulasi Ketat (HIPAA, PCI-DSS, SOC2)

Pilihan: HashiCorp Vault

Regulasi seperti HIPAA dan PCI-DSS membutuhkan kontrol granular, audit trail yang detail, dan kemampuan untuk membuktikan bahwa credential dirotasi sesuai kebijakan. Vault menyediakan semua ini plus kemampuan untuk mengatur policy yang sangat spesifik: siapa yang boleh mengakses secret apa, dari IP mana, pada jam berapa, dan berapa kali dalam sehari.

Aplikasi Serverless (Lambda-heavy)

Pilihan: AWS Secrets Manager

Lambda terintegrasi secara native dengan Secrets Manager. Kamu bisa menggunakan aws_secretsmanager_secret sebagai environment variable yang di-inject saat deployment, atau memanggil API-nya langsung. Latency API call Secrets Manager juga rendah karena berada dalam jaringan AWS. Menjalankan Vault agent di Lambda adalah overkill yang tidak praktis.

Migrasi dari On-Premise ke Cloud (Hybrid)

Pilihan: HashiCorp Vault

Selama masa transisi, kamu punya workload di on-premise dan di cloud. Vault bisa berjalan di keduanya dan menyediakan satu interface yang konsisten. Begitu migrasi selesai, kamu bisa mempertimbangkan transisi ke Secrets Manager jika memang lebih cocok — tapi Vault juga bisa tetap digunakan di AWS tanpa masalah.


Anti-Pattern yang Harus Dihindari

Banyak tim membuat kesalahan yang sama saat mengadopsi secret management tool. Kenali dan hindari pola-pola ini.

// ✗ Anti-pattern 1: Vault hanya sebagai KV store statis
// Ini membuang-buang potensi Vault dan sama saja dengan Secrets Manager
// tapi dengan overhead operasional yang lebih besar
vaultClient.KVv2("secret").Put(ctx, "myapp/db", map[string]interface{}{
    "password": "hardcoded-password-that-never-changes",
})
// ✓ Gunakan Database Secrets Engine untuk dynamic credential, atau
// setidaknya implementasi rotasi otomatis via Vault Agent

// ✗ Anti-pattern 2: Panggil Secrets Manager di setiap request
func handleRequest(w http.ResponseWriter, r *http.Request) {
    secret, _ := smClient.GetDBSecret(r.Context(), "prod/myapp/db") // ✗ setiap request!
    db, _ := sql.Open("postgres", buildDSN(secret))
    // ...
}
// ✓ Buat database connection pool satu kali saat startup,
// atau gunakan caching dengan TTL yang masuk akal (misal 5 menit)

// ✗ Anti-pattern 3: Tidak ada error handling saat secret fetch gagal
secret, _ := vaultClient.GetDBSecret(ctx, "myapp/db") // ✗ ignore error
db.Username = secret.Username // panic jika secret nil
// ✓ Selalu handle error, dan pertimbangkan fallback strategy
// (misalnya gunakan cached value yang expired daripada crash)

// ✗ Anti-pattern 4: Log nilai secret untuk debugging
log.Printf("Berhasil mengambil secret: username=%s password=%s",
    secret.Username, secret.Password) // ✗ credential masuk ke log!
// ✓ Hanya log metadata, bukan nilai secret-nya
log.Printf("Berhasil mengambil secret untuk path: %s", secretPath)
Jangan pernah log nilai secret, bahkan di level DEBUG. Log biasanya dikirim ke sistem terpusat (Datadog, Splunk, CloudWatch Logs) yang aksesnya lebih luas dari aplikasi itu sendiri. Satu credential yang masuk ke log bisa bocor ke puluhan orang yang seharusnya tidak punya akses.

Ringkasan

  • Vault dan Secrets Manager bukan kompetitor langsung — Vault adalah security platform, Secrets Manager adalah managed secret store. Keduanya menjawab problem yang berbeda.
  • Dynamic secrets adalah pembeda utama — Vault bisa generate credential on-demand dengan TTL. Secrets Manager menyimpan credential statis dengan rotasi periodik. Jika window of exposure sangat kritikal, Vault adalah jawabannya.
  • Overhead operasional Vault itu nyata — self-hosting Vault butuh tim yang kompeten dan waktu setup yang tidak sedikit. Pertimbangkan HCP Vault jika butuh managed Vault, atau mulai dengan Secrets Manager jika tim masih kecil.
  • Secrets Manager cocok untuk AWS-native stack — integrasi dengan RDS, Lambda, ECS, dan IAM-nya sangat mulus. Jika seluruh infrastruktur kamu di AWS dan tidak butuh dynamic secrets, Secrets Manager adalah pilihan yang pragmatis.
  • Selalu implementasikan caching — jangan panggil Secrets Manager atau Vault di setiap request. Cache dengan TTL yang masuk akal untuk mengurangi latency dan biaya.
  • Jangan gunakan Vault sekadar sebagai KV store statis — jika kamu menggunakan Vault tapi tidak memanfaatkan dynamic secrets, lease, atau auth method yang granular, kamu menanggung overhead tanpa mendapat manfaat penuhnya.
  • Jangan log nilai secret — audit trail ada untuk mendeteksi akses yang tidak sah, bukan untuk mencatat nilai credential di log yang bisa diakses siapa saja.
  • Pertimbangkan hybrid — organisasi besar sering menggunakan keduanya: Vault untuk workload multi-cloud dan kebutuhan dynamic secrets, Secrets Manager untuk aplikasi serverless dan integrasi AWS native.

Portofolio