Secret Management

Secret Management #

Saat mengelola infrastruktur modern, kita menghadapi tantangan besar dalam menjaga kerahasiaan data sensitif seperti password database, API key, sertifikat SSL, dan private key. Ansible Vault memberikan solusi awal yang baik dengan mengenkripsi file di dalam repository Git. Namun, seiring dengan pertumbuhan tim dan kompleksitas infrastruktur, ketergantungan penuh pada Ansible Vault sering kali memunculkan masalah baru. Kita harus berbagi password vault yang sama kepada semua developer, melakukan commit ulang setiap kali memutar credential, dan kita tidak memiliki audit log yang mencatat siapa yang mengakses secret tersebut.

Untuk mengatasi tantangan ini, kita membutuhkan solusi yang lebih dinamis dan terpusat melalui external secret manager seperti HashiCorp Vault, AWS Secrets Manager, atau Azure Key Vault. Dengan mengintegrasikan Ansible dengan secret manager eksternal, kita bisa melakukan runtime injection (memasukkan secret langsung ke memori saat playbook berjalan) tanpa pernah menyimpannya di disk, menerapkan dynamic secret dengan Time-to-Live (TTL) pendek, serta melindungi log CI/CD dari kebocoran data sensitif.

Lifecycle Secret: Dari Create sampai Revoke #

Sebelum kita memilih dan mengonfigurasi tools yang akan kita gunakan, kita harus memahami daur hidup (lifecycle) secret yang ideal dalam pengelolaan infrastruktur yang aman. Banyak tim operasional mengelola secret secara reaktif — membuat password secara manual saat dibutuhkan, menyimpannya di tempat seadanya, dan membiarkannya aktif selamanya tanpa pernah diganti. Daur hidup secret yang matang setidaknya harus memiliki lima fase utama yang berjalan secara terus-menerus:

flowchart LR
    A["1. Create<br/>generate secret<br/>dengan entropy tinggi"] --> B["2. Store<br/>simpan di secret manager<br/>dengan enkripsi at-rest"]
    B --> C["3. Use<br/>inject saat runtime<br/>no env permanen"]
    C --> D["4. Rotate<br/>ganti berkala<br/>atau otomatis"]
    D --> E["5. Revoke<br/>hapus akses<br/>dan audit trail"]
    E --> A

    style A stroke:#4a90e2,stroke-width:2px
    style B stroke:#7b68ee,stroke-width:2px
    style C stroke:#50c878,stroke-width:2px
    style D stroke:#f5a623,stroke-width:2px
    style E stroke:#d0021b,stroke-width:2px

Fase-fase ini wajib kita terapkan pada setiap kredensial di lingkungan produksi kita. Sebagai contoh, ketika kita membuat kredensial untuk database baru: kita men-generate password acak dengan entropy tinggi (fase 1), menyimpannya ke dalam sistem penyimpanan terenkripsi yang aman (fase 2), mengambil dan menyuntikkannya langsung ke dalam memori Ansible selama deployment aplikasi (fase 3), merotasi nilai password tersebut secara berkala (fase 4), dan pada akhirnya mencabut hak akses serta menghapus data tersebut ketika aplikasi dinonaktifkan (fase 5). Jika salah satu dari fase ini diabaikan, permukaan serangan (attack surface) pada sistem kita akan meningkat secara signifikan.


Ansible Vault vs External Secret Manager #

Sebelum kita memutuskan untuk berpindah atau menggabungkan Ansible Vault dengan secret manager eksternal, kita perlu menganalisis perbedaan mendasar di antara keduanya. Tabel berikut membandingkan berbagai dimensi penting yang harus kita pertimbangkan:

Aspek Ansible Vault HashiCorp Vault AWS Secrets Manager Azure Key Vault
Metode Penyimpanan File terenkripsi AES-256 di Git Distributed storage terenkripsi (HA) Managed storage terenkripsi KMS Managed storage HSM terenkripsi
Audit Trail Tidak ada (hanya Git history) Log akses granular dan komprehensif CloudTrail + log pemanggilan API Azure Monitor + log aktivitas
Kontrol Akses All-or-nothing (akses password) Policy berbasis RBAC / ACL granular IAM Policy + Resource Policy RBAC + Access Policy Azure Active Directory
Rotasi Otomatis Manual (rekey + commit ulang) Engine rotasi otomatis bawaan Rotasi otomatis berbasis AWS Lambda Rotasi otomatis berbasis Event Grid
Dynamic Secret Tidak didukung Didukung untuk DB, AWS, SSH, dll. Workaround manual via Lambda Workaround manual via Function
Token & Lease (TTL) Tidak ada konsep umur pakai Didukung dengan lease time & renewal Tidak didukung secara native Tidak didukung secara native
Kebutuhan Infrastruktur Tidak ada Perlu di-deploy dan di-maintenance Tanpa setup server (managed service) Tanpa setup server (managed service)
Skenario Terbaik Tim kecil, proyek lokal, CI sederhana Multi-cloud, hybrid, compliance ketat AWS-native, deployment skala besar Azure-native, ekosistem Microsoft
Biaya Gratis (fitur bawaan Ansible) Open source gratis, enterprise berbayar $0.40 per secret / bulan $0.03 per 10,000 operasi transaksi

Kita perlu menyadari bahwa perbedaan utama di sini bukan pada kekuatan enkripsi. Baik Ansible Vault maupun secret manager eksternal sama-sama menggunakan standar enkripsi yang sangat kuat. Pembeda sesungguhnya terletak pada kontrol audit, manajemen akses yang terperinci (RBAC), umur pakai token (TTL), serta kemampuan untuk merotasi rahasia secara otomatis tanpa intervensi manual. Untuk sistem produksi yang tunduk pada aturan compliance yang ketat (seperti SOC2, PCI-DSS, atau HIPAA), integrasi dengan external secret manager merupakan kebutuhan mutlak.


HashiCorp Vault dengan Ansible #

HashiCorp Vault adalah salah satu pilihan terpopuler untuk menyimpan secret di lingkungan multi-cloud dan hybrid. Vault menyediakan fungsionalitas canggih seperti AppRole untuk autentikasi sistem-ke-sistem, dynamic secret, dan audit logging yang sangat detail.

Instalasi dan Setup Dependensi #

Untuk mulai menggunakan HashiCorp Vault di Ansible, kita perlu menginstal koleksi komunitas yang menyediakan lookup plugin serta pustaka Python hvac di control node kita:

# Menginstal koleksi Ansible Galaxy untuk HashiCorp Vault
ansible-galaxy collection install community.hashi_vault

# Menginstal modul Python hvac yang dibutuhkan oleh lookup plugin
pip install hvac

Alur Request Token dan Retrieval Secret #

Sangat penting bagi kita untuk memahami bagaimana Ansible berkomunikasi dengan Vault untuk mengambil secret di belakang layar. Sequence diagram berikut menggambarkan alur pertukaran token dan pengambilan data:

sequenceDiagram
    participant Play as "Ansible Playbook"
    participant LP as "Lookup Plugin<br/>community.hashi_vault"
    participant Auth as "Vault Auth Method<br/>(AppRole/Kubernetes/AWS)"
    participant Core as "Vault Core"
    participant SecEng as "Secret Engine<br/>(KV v2/Database/AWS)"

    Play->>LP: "lookup('hashi_vault', secret=..., url=..., auth_method=approle)"
    LP->>Auth: "POST /v1/auth/approle/login<br/>(role_id + secret_id)"
    Auth-->>LP: "client_token (dengan TTL)"
    LP->>Core: "SET X-Vault-Token: <token>"
    LP->>SecEng: "GET /v1/secret/data/myapp/database"
    SecEng->>SecEng: "Cek policy — apakah<br/>token ini boleh baca path ini?"
    SecEng-->>LP: "{ data: { username, password } }"
    LP-->>Play: "Return secret value"
    Note over Play,SecEng: "Secret hanya ada di memory<br/>selama task berjalan"
    Play->>Play: "Pakai secret untuk template/configure"
    Note over Play: "Variabel secret di-scope<br/>ke task block, di-clear setelah"

Dari diagram di atas, kita bisa menarik beberapa poin penting:

  1. Autentikasi Aman: Ansible menggunakan AppRole (Role ID dan Secret ID) untuk melakukan login awal. Vault kemudian mengembalikan token akses yang berumur pendek.
  2. Runtime Memory: Secret yang dikembalikan oleh Vault hanya disimpan di dalam memori eksekusi Ansible dan tidak pernah ditulis ke media penyimpanan permanen di control node maupun managed node.
  3. Pengecekan Kebijakan (Policy): Setiap akses secret akan divalidasi berdasarkan hak akses yang melekat pada token tersebut, membatasi akses ilegal bahkan jika token tersebut berhasil dicek.

Contoh Implementasi Playbook dengan AppRole #

Di bawah ini adalah contoh bagaimana kita menulis playbook untuk mengambil database credential dari KV (Key-Value) v2 engine di HashiCorp Vault secara dinamis:

- name: Deploy aplikasi dengan credential dari HashiCorp Vault
  hosts: appservers
  gather_facts: false
  vars:
    # Membaca informasi autentikasi dari environment variable di control node
    vault_role_id: "{{ lookup('env', 'VAULT_ROLE_ID') }}"
    vault_secret_id: "{{ lookup('env', 'VAULT_SECRET_ID') }}"
    vault_addr: "https://vault.company.internal:8200"

  tasks:
    - name: Ambil data secret dari Vault
      set_fact:
        db_secrets: "{{ lookup('community.hashi_vault.hashi_vault',
          'secret=secret/data/myapp/database:data',
          url=vault_addr,
          auth_method='approle',
          role_id=vault_role_id,
          secret_id=vault_secret_id) }}"
      no_log: true  # Mencegah nilai secret tertulis di stdout dan file log

    - name: Konfigurasi file database.conf
      template:
        src: templates/database.conf.j2
        dest: /etc/app/database.conf
        owner: appuser
        group: appuser
        mode: '0600'
      vars:
        db_username: "{{ db_secrets.username }}"
        db_password: "{{ db_secrets.password }}"
      no_log: true
Kita harus selalu menyertakan atribut no_log: true pada setiap task yang memproses atau menampilkan rahasia. Tanpa flag ini, jika terjadi error atau kegagalan eksekusi, Ansible akan mencetak isi objek ke log standar, sehingga rahasia produksi kita bisa terlihat oleh siapa saja yang memiliki akses ke sistem CI/CD.

Dynamic Secret: Kredensial yang Mengalami Self-Destruct #

Fitur tercanggih dari HashiCorp Vault adalah kemampuannya membuat dynamic secret. Alih-alih menggunakan password statis database yang sama untuk selamanya, Vault dapat men-generate user database unik untuk Ansible dengan hak akses terbatas dan TTL yang pendek. Setelah TTL habis, Vault secara otomatis akan menghapus user tersebut dari database.

- name: Ambil dynamic database credential dari Vault
  set_fact:
    db_dynamic_creds: "{{ lookup('community.hashi_vault.hashi_vault',
      'database/creds/myapp-writer',
      url=vault_addr,
      auth_method='approle',
      role_id=vault_role_id,
      secret_id=vault_secret_id) }}"
  no_log: true
  vars:
    # Mengajukan TTL khusus selama 30 menit untuk deployment ini
    vault_secret_ttl: "30m"

Dengan dynamic secret, kita menghilangkan risiko kebocoran credential jangka panjang secara permanen. Bahkan jika credentials tersebut tidak sengaja terekspos, masa aktifnya yang sangat pendek meminimalkan dampak eksploitasi oleh pihak luar.


AWS Secrets Manager dengan Ansible #

Bagi kita yang mengoperasikan infrastruktur di AWS secara eksklusif, AWS Secrets Manager adalah solusi yang sangat terintegrasi. Kita tidak perlu mengelola server backend sendiri dan dapat memanfaatkan integrasi native dengan AWS IAM.

Setup dan Instalasi Dependensi AWS #

Kita membutuhkan koleksi amazon.aws serta modul Python boto3 dan botocore untuk berkomunikasi dengan API AWS:

# Menginstal koleksi AWS untuk Ansible
ansible-galaxy collection install amazon.aws

# Menginstal library AWS SDK untuk Python
pip install boto3 botocore

Mengambil Secret Menggunakan IAM Role (Passwordless Control) #

Daripada kita menulis AWS access key secara hardcoded di dalam variabel, praktik terbaik yang harus kita terapkan adalah menggunakan IAM Instance Profile pada EC2 Instance yang bertindak sebagai Ansible control node. Dengan cara ini, plugin lookup amazon.aws.aws_secret akan mendeteksi credentials sementara secara otomatis.

Berikut adalah contoh playbook untuk mengambil secret RDS:

- name: Ambil kredensial RDS dari AWS Secrets Manager
  hosts: appservers
  gather_facts: false
  tasks:
    - name: Ambil secret JSON dari Secrets Manager
      set_fact:
        rds_raw_secret: "{{ lookup('amazon.aws.aws_secret',
          'prod/myapp/rds',
          region='ap-southeast-1',
          on_missing='error') | from_json }}"
      no_log: true

    - name: Render file konfigurasi database di target host
      template:
        src: templates/app_config.yml.j2
        dest: /var/www/app/config.yml
        owner: www-data
        group: www-data
        mode: '0600'
      vars:
        db_host: "{{ rds_raw_secret.host }}"
        db_user: "{{ rds_raw_secret.username }}"
        db_pass: "{{ rds_raw_secret.password }}"
      no_log: true
Kita disarankan untuk mengelompokkan secret dalam format JSON di AWS Secrets Manager agar kita bisa mengambil seluruh pasangan key-value yang dibutuhkan dalam satu kali panggilan API, menghemat biaya operasional pemanggilan API AWS Secrets Manager.

SOPS: Enkripsi File untuk GitOps #

Mozilla SOPS (Secrets OPerationS) menawarkan pendekatan yang berbeda dari secret manager terpusat. SOPS adalah editor file terenkripsi yang mendukung format YAML, JSON, ENV, dan binary. Keunggulan utama SOPS adalah ia hanya mengenkripsi value dari sebuah file YAML, sementara key struktur file tersebut tetap dalam bentuk plaintext. Hal ini sangat memudahkan kita untuk melakukan code review karena struktur file konfigurasi tetap bisa dibaca secara visual tanpa membocorkan nilai rahasianya.

Selain itu, SOPS tidak menyimpan kunci enkripsi di dalam repository. Kunci enkripsi tersebut didelegasikan ke penyedia eksternal seperti AWS KMS, GCP KMS, Azure Key Vault, HashiCorp Vault, PGP, atau age.

Ilustrasi File YAML Terenkripsi SOPS #

Di bawah ini adalah contoh perbedaan tampilan file sebelum dan sesudah kita enkripsi menggunakan SOPS:

# File: group_vars/production/secrets.sops.yml
db_host: "prod-db.internal"           # tetap terbaca (plaintext)
db_user: "app_prod"                   # tetap terbaca (plaintext)
db_password: ENC[AES256_GCM,data:3XqBf8==,tag:1Xz...,type:str] # terenkripsi

Integrasi SOPS ke dalam Playbook Ansible #

Untuk mengintegrasikannya dengan Ansible, kita bisa menggunakan plugin lookup community.sops:

- name: Membaca data sensitif dari file terenkripsi SOPS
  hosts: appservers
  gather_facts: false
  tasks:
    - name: Load variabel terenkripsi SOPS ke memori
      set_fact:
        decrypted_secrets: "{{ lookup('community.sops.sops', 'group_vars/production/secrets.sops.yml') | from_yaml }}"
      no_log: true

    - name: Menjalankan konfigurasi aplikasi dengan secret
      template:
        src: templates/app.env.j2
        dest: /opt/myapp/.env
        mode: '0600'
      vars:
        app_db_password: "{{ decrypted_secrets.db_password }}"
      no_log: true

Pola ini sangat ideal untuk workflow GitOps, di mana seluruh file konfigurasi kita simpan di repository Git, tetapi nilainya tetap terproteksi dengan aman menggunakan kunci enkripsi KMS yang dikontrol secara ketat.


Pola: Runtime Injection vs Environment Permanen #

Salah satu kesalahan arsitektur paling fatal yang sering kita temukan adalah membiarkan secret disimpan secara permanen di dalam environment variable sistem operasi target (misalnya melalui /etc/environment atau file .bashrc).

# ANTI-PATTERN: Menyimpan secret secara permanen di environment variable server
# File: /etc/environment (✗ SANGAT TIDAK DIREKOMENDASIKAN)
DATABASE_PASSWORD="PasswordSangatRahasia123!"
THIRD_PARTY_API_TOKEN="api_token_val_abc_xyz"

# Mengapa ini berbahaya?
# 1. Semua proses anak (child process) mewarisi variabel ini, termasuk editor teks dan scheduler.
# 2. Nilai env var dapat dibaca dengan mudah melalui file '/proc/<pid>/environ' oleh user non-root.
# 3. Log pengawasan sistem (monitoring agent) sering mencatat seluruh env var saat mendeteksi crash.
# 4. Sangat menyulitkan kita jika ingin melakukan rotasi kredensial dengan cepat.

Solusi yang benar adalah menerapkan Runtime Injection. Kita menyimpan secret di dalam secret manager terpusat, mengambilnya secara on-demand menggunakan Ansible lookup plugin saat playbook dijalankan, menyuntikkannya ke file konfigurasi lokal aplikasi dengan hak akses terbatas (0600), lalu membiarkan memori Ansible dibersihkan saat proses eksekusi playbook selesai.

# BENAR: Runtime injection ke file konfigurasi lokal berizin ketat
- name: Terapkan runtime injection untuk konfigurasi database
  hosts: db_clients
  gather_facts: false
  tasks:
    - name: Ambil password secara dinamis
      set_fact:
        fetched_pass: "{{ lookup('community.hashi_vault.hashi_vault', 'secret=secret/data/db:password', ...) }}"
      no_log: true

    - name: Tulis ke file konfigurasi aplikasi dengan mode aman
      template:
        src: templates/local_conf.j2
        dest: /etc/myapp/local.conf
        owner: myapp_runner
        group: myapp_runner
        mode: '0600'  # Hanya owner yang memiliki izin baca dan tulis
      vars:
        secret_db_pass: "{{ fetched_pass }}"
      no_log: true

Dengan model runtime injection, kita memastikan bahwa variabel sensitif tersebut tidak akan menetap di environment global sistem operasi yang rentan terhadap kebocoran informasi.


Pola: Automatic Rotation vs Tidak Rotate #

Menyimpan secret dengan aman di dalam secret manager saja tidak cukup jika kita membiarkan kredensial tersebut aktif selamanya tanpa pernah diganti. Banyak tim mengalami pelanggaran keamanan bukan karena algoritma enkripsi mereka dijebol, melainkan karena kredensial statis mereka bocor tanpa disadari dan tetap aktif selama bertahun-tahun.

# ANTI-PATTERN: Secret statis tanpa skema rotasi
# Kredensial database prod/db-password dibuat pada tahun 2022
# Nilai password masih sama di tahun 2026
# Tidak ada pengingat, tidak ada otomasi rotasi
# Kredensial telah tersimpan di laptop mantan engineer, log lama, dan backup tape

Kita harus mendesain otomasi untuk mengganti secret secara periodik (misalnya setiap 30 hari). Di lingkungan AWS, kita bisa mengonfigurasi AWS Secrets Manager agar memicu fungsi AWS Lambda yang akan memperbarui password di database target sekaligus memperbarui nilai secret tersebut di Secrets Manager secara atomic:

# Contoh script AWS Lambda untuk rotasi otomatis password RDS (rotation_lambda.py)
import boto3
import json
import secrets
import string

def generate_strong_password(length=32):
    alphabet = string.ascii_letters + string.digits + "!@#$%^&*()"
    return "".join(secrets.choice(alphabet) for _ in range(length))

def lambda_handler(event, context):
    secrets_client = boto3.client('secretsmanager')
    rds_client = boto3.client('rds')
    
    secret_arn = event['SecretId']
    token = event['ClientRequestToken']
    
    # 1. Generate password baru dengan entropy tinggi
    new_password = generate_strong_password()
    
    # 2. Update password pada instance database target
    rds_client.modify_db_instance(
        DBInstanceIdentifier='myapp-prod-db',
        MasterUserPassword=new_password,
        ApplyImmediately=True
    )
    
    # 3. Perbarui value secret di AWS Secrets Manager
    secrets_client.put_secret_value(
        SecretId=secret_arn,
        ClientRequestToken=token,
        SecretString=json.dumps({
            'username': 'db_admin',
            'password': new_password
        }),
        VersionStages=['AWSCURRENT']
    )
    
    return {'statusCode': 200, 'body': 'Rotation successful'}

Dengan mengintegrasikan skrip ini pada AWS Secrets Manager, kita memastikan bahwa password database kita dirotasi secara otomatis tanpa memerlukan intervensi manual dari administrator infrastruktur.


RBAC: Prinsip Least Privilege untuk Secret #

External secret manager menawarkan kontrol akses berbasis peran (Role-Based Access Control / RBAC) yang jauh lebih canggih daripada sekadar mengenkripsi file dengan satu password. Kita bisa membatasi akses secara sangat granular sehingga setiap entitas hanya memiliki izin minimum yang benar-benar mereka butuhkan untuk menjalankan fungsinya.

Sebagai contoh, kita bisa mendefinisikan kebijakan (policy) di HashiCorp Vault menggunakan format HCL seperti di bawah ini:

# File: policies/ansible-app-deploy.hcl
# Izinkan Ansible membaca secret untuk deployment aplikasi web prod
path "secret/data/myapp/production/*" {
  capabilities = ["read"]
}

# Izinkan Ansible membaca secret untuk staging secara penuh (read dan list)
path "secret/data/myapp/staging/*" {
  capabilities = ["read", "list"]
}

# Batasi agar tidak bisa melihat data sensitif root database
path "secret/data/myapp/production/db-root" {
  capabilities = [] # Explicit deny
}

Di sisi AWS, kita bisa membatasi hak akses service account Ansible menggunakan IAM Policy dengan batasan kondisi resource yang ketat:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowAnsibleAppSecretsOnly",
      "Effect": "Allow",
      "Action": "secretsmanager:GetSecretValue",
      "Resource": "arn:aws:secretsmanager:ap-southeast-1:123456789012:secret:prod/myapp/*"
    },
    {
      "Sid": "DenyRootDbAccess",
      "Effect": "Deny",
      "Action": "secretsmanager:GetSecretValue",
      "Resource": "arn:aws:secretsmanager:ap-southeast-1:123456789012:secret:prod/myapp/db-root-*"
    }
  ]
}

Menerapkan kebijakan semacam ini secara disiplin akan meminimalkan risiko keamanan jika salah satu credentials atau control node kita mengalami kompromi. Attacker hanya akan mendapatkan akses terbatas ke segmen infrastruktur tertentu, bukan ke seluruh fleet server kita.


Secret Scanning: Mendeteksi Kebocoran Dini #

Walaupun kita telah bermigrasi ke secret manager terpusat, kemungkinan tidak sengaja melakukan commit secret ke repository Git tetap ada. Oleh karena itu, kita memerlukan pertahanan berlapis (defense in depth) dengan menerapkan tool pemindaian otomatis (secret scanning) seperti Gitleaks atau Trufflehog.

Kita bisa menambahkan langkah pemindaian ini secara langsung di pipeline CI/CD kita untuk menganalisis setiap push commit:

# File: .github/workflows/secret-scanning.yml
name: Secret Scanning Guard
on: [push, pull_request]

jobs:
  gitleaks_scan:
    name: Run Gitleaks Scan
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Code
        uses: actions/checkout@v4
        with:
          fetch-depth: 0  # Mengambil seluruh commit history untuk dianalisis

      - name: Jalankan Gitleaks
        uses: gitleaks/gitleaks-action@v2
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Untuk pengerjaan di lokal, kita juga bisa memaksa pemindaian pre-commit di laptop developer sebelum commit tersebut berhasil dibuat:

# Menjalankan pemindaian manual di direktori proyek lokal kita
gitleaks detect --source . --verbose

# Menganalisis seluruh riwayat git commit untuk mendeteksi kebocoran lama
gitleaks detect --source . --log-opts="--all"

Jika terlanjur ada secret yang masuk ke dalam Git history kita, langkah pertama yang harus kita lakukan bukanlah menghapus file tersebut lalu melakukan commit baru (karena secret masih ada di history commit lama). Kita harus segera merotasi kredensial tersebut di backend target, lalu menggunakan BFG Repo-Cleaner atau perintah git filter-repo untuk membersihkan kredensial tersebut secara permanen dari seluruh sejarah Git kita.


Kapan Tetap Pakai Ansible Vault #

Meskipun sistem secret manager eksternal menawarkan keandalan tinggi, untuk beberapa kasus tertentu, penggunaan Ansible Vault secara mandiri masih dapat kita benarkan. Tabel berikut meringkas panduan untuk membantu kita menentukan pilihan terbaik:

Tetap Gunakan Ansible Vault Jika Beralih ke External Secret Manager Jika
✓ Anggota tim operasional masih sangat sedikit (< 5 orang) ✗ Tim operasional berskala menengah-besar dan memiliki pembagian tugas (RBAC) yang jelas
✓ Seluruh server dikelola di satu lokasi infrastruktur terpusat ✗ Lingkungan multi-cloud, hybrid, atau on-premise yang kompleks
✓ Kredensial bersifat statis dan tidak memerlukan rotasi berkala yang cepat ✗ Kredensial harus dirotasi secara otomatis setiap hari atau minggu
✓ Tidak ada kewajiban audit log akses individual untuk compliance eksternal ✗ Organisasi wajib tunduk pada standar audit ketat (SOC2, HIPAA, PCI-DSS)
✓ Seluruh proses deployment dapat diselesaikan secara offline tanpa internet ✗ Memerlukan dynamic secret generation (user database on-demand)

Sebuah pola hybrid juga sangat sering digunakan di dunia industri: kita menggunakan Ansible Vault untuk menyimpan secret bootstrap awal (seperti API key atau token akses yang digunakan Ansible untuk terhubung ke AWS Secrets Manager atau HashiCorp Vault), kemudian kita menggunakan external secret manager untuk mengambil seluruh rahasia runtime aplikasi secara dinamis. Pola ini membatasi risiko paparan credentials dan tetap mempermudah manajemen operasional kita.


Integrasi dengan Configuration Management #

Mari kita tinjau contoh implementasi riil yang menggabungkan seluruh best practice di atas ke dalam playbook deployment aplikasi web kita. Di sini kita menggunakan block terstruktur, menangani pengecualian (rescue), menetapkan kepemilikan file yang ketat, serta menonaktifkan pencatatan log sensitif.

# File: site-deploy.yml
- name: Deploy web application with secure secret management
  hosts: production_servers
  gather_facts: false
  vars:
    vault_addr: "https://vault.company.internal:8200"

  tasks:
    - name: Mengamankan deployment menggunakan block kontrol
      block:
        - name: Memuat role ID dan secret ID dari environment control node
          set_fact:
            role_id: "{{ lookup('env', 'VAULT_ROLE_ID') }}"
            secret_id: "{{ lookup('env', 'VAULT_SECRET_ID') }}"
          no_log: true

        - name: Mengambil secret database dan API key dari Vault
          set_fact:
            vault_data: "{{ lookup('community.hashi_vault.hashi_vault',
              'secret=secret/data/myapp/production:data',
              url=vault_addr,
              auth_method='approle',
              role_id=role_id,
              secret_id=secret_id) }}"
          no_log: true

        - name: Membuat file konfigurasi .env dengan izin akses ketat
          template:
            src: templates/env.j2
            dest: /var/www/app/.env
            owner: app_runner
            group: app_runner
            mode: '0600'  # Owner read and write only
          vars:
            app_db_password: "{{ vault_data.db_password }}"
            app_api_token: "{{ vault_data.api_token }}"
          no_log: true

        - name: Memastikan service aplikasi berjalan
          service:
            name: myapp
            state: restarted
          become: true

      rescue:
        - name: Menangani error deployment tanpa membocorkan rahasia
          debug:
            msg: "Deployment ke host {{ inventory_hostname }} gagal. Silakan periksa log aplikasi atau konektivitas ke secret manager."

Pada contoh di atas, jika proses pengambilan credentials gagal atau file konfigurasi tidak dapat dibuat, eksekusi playbook akan dialihkan ke blok rescue yang menampilkan pesan error generik tanpa memaparkan kode error internal yang berpotensi membocorkan konfigurasi teknis kita.


Ringkasan #

  • Keterbatasan Ansible Vault — Meskipun andal untuk enkripsi file Git lokal, Ansible Vault tidak mendukung dynamic secret, audit log pemanggilan, pembatasan RBAC granular, atau rotasi otomatis.
  • Daur Hidup Secret — Kita harus memastikan setiap kredensial melalui lima tahap daur hidup: Create, Store, Use, Rotate, dan Revoke secara disiplin.
  • HashiCorp Vault & AppRole — Kita disarankan menggunakan autentikasi AppRole untuk otomatisasi sistem-ke-sistem, yang menyediakan token akses dengan batasan waktu aktif (TTL) yang aman.
  • AWS Secrets Manager & IAM — Untuk deployment AWS-native, manfaatkan IAM Instance Profile pada control node agar kita bisa melakukan lookup secret tanpa menyimpan kredensial akses AWS statis.
  • File Enkripsi SOPS — Mozilla SOPS sangat disukai dalam arsitektur GitOps karena memungkinkan enkripsi hanya pada nilai variabel (value) sementara kuncinya tetap terbaca di Git.
  • Runtime Injection — Terapkan penyuntikan secret langsung ke memori saat eksekusi playbook berjalan, dan hindari menyimpan secret di environment variable permanen OS target.
  • Rotasi Otomatis — Cegah ancaman kebocoran jangka panjang dengan mengaktifkan rotasi berkala otomatis menggunakan Lambda atau cron engine secret manager.
  • Kebijakan Least Privilege — Konfigurasikan kebijakan akses minimal (RBAC) pada secret manager agar setiap playbook hanya bisa membaca data yang relevan dengan skop tugasnya saja.
  • Secret Scanning — Pasang pre-commit hook dan pipeline check menggunakan Gitleaks untuk mencegah commit kredensial plaintext ke dalam sejarah Git kita secara tidak sengaja.
  • Atribut no_log: true — Selalu sertakan flag no_log: true di setiap tugas Ansible yang membaca atau menyentuh data rahasia untuk memproteksi file log sistem CI/CD kita.

← Sebelumnya: Workflow   Berikutnya: SSH Security →

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