Workflow

Workflow #

Vault dan enkripsi melindungi data sensitif. Tapi keamanan otomasi tidak hanya soal enkripsi — ia juga soal siapa yang bisa menjalankan apa, dari mana, dan kapan. Workflow yang tidak aman bisa membuat enkripsi yang paling kuat sekalipun menjadi tidak berarti: jika semua orang bisa menjalankan playbook ke production kapan saja tanpa review, nilai keamanan yang diberikan oleh Vault menjadi minimal. Artikel ini membahas workflow yang memastikan otomasi Ansible berjalan dengan kontrol yang tepat, dari siapa yang mengeksekusi playbook, bagaimana pipeline deployment dibangun, sampai bagaimana setiap perubahan ke production bisa ditelusuri.

Prinsip Least Privilege untuk Service User #

User yang digunakan Ansible untuk terhubung ke managed node harus punya hak minimum yang diperlukan — tidak lebih. Prinsip ini sering disebut least privilege dan merupakan fondasi dari seluruh workflow keamanan Ansible. Tanpa least privilege, semua kontrol lain di atasnya menjadi rapuh: SSH key yang kuat tidak ada artinya jika user-nya bisa melakukan apa saja setelah berhasil login, dan Vault yang terenkripsi dengan baik tidak melindungi apapun jika vault password bisa diakses oleh user dengan privilege berlebihan.

# roles/common/tasks/ansible-user.yml
# Buat user khusus Ansible dengan hak terbatas

- name: Buat user ansible-deploy
  user:
    name: ansible-deploy
    shell: /bin/bash
    system: false
    state: present

- name: Tambahkan SSH key untuk user ansible-deploy
  authorized_key:
    user: ansible-deploy
    key: "{{ lookup('file', 'files/ansible_deploy.pub') }}"
    exclusive: true     # Hanya key ini yang diizinkan, hapus yang lain

- name: Konfigurasi sudoers dengan hak minimum
  template:
    src: ansible-sudoers.j2
    dest: /etc/sudoers.d/ansible-deploy
    mode: '0440'
    validate: 'visudo -cf %s'
{# templates/ansible-sudoers.j2 #}
# Ansible deploy user — hak minimum untuk operasi deployment
ansible-deploy ALL=(ALL) NOPASSWD: /bin/systemctl restart myapp
ansible-deploy ALL=(ALL) NOPASSWD: /bin/systemctl start myapp
ansible-deploy ALL=(ALL) NOPASSWD: /bin/systemctl stop myapp
ansible-deploy ALL=(ALL) NOPASSWD: /usr/bin/rsync
ansible-deploy ALL=(ALL) NOPASSWD: /usr/bin/pip3 install *
# Larang akses ke operasi yang tidak diperlukan
ansible-deploy ALL=(ALL) !NOPASSWD: /bin/su
ansible-deploy ALL=(ALL) !NOPASSWD: /bin/bash

validate: 'visudo -cf %s' sangat penting — Ansible menolak untuk menyimpan file sudoers yang sintaksisnya salah, sehingga tidak ada risiko kita terkunci keluar dari server karena template yang cacat.


Memilih Model Privilege Escalation #

Ansible menyediakan beberapa cara untuk eskalasi privilege, dan pilihan model yang tepat mempengaruhi postur keamanan seluruh infrastruktur kita. Tabel berikut membandingkan tiga model yang umum dipertimbangkan:

Aspek sudo doas become_user
Asal Standar historis di dunia Unix, hampir selalu tersedia Alternatif modern dari OpenBSD, fokus pada kesederhanaan Fitur bawaan Ansible, bukan fitur OS
Konfigurasi /etc/sudoers dan /etc/sudoers.d/*, sintaksis kompleks /etc/doas.conf, sintaksis jauh lebih sederhana Tidak perlu konfigurasi OS — Ansible langsung menjalankan task sebagai user target
Granularitas Sangat granular per perintah, per user, per host Granular per perintah dan per user Granular per task di playbook
Logging Log ke syslog, detail dan terstandar Log ke syslog, lebih ringkas Tidak ada log OS — Ansible log saja yang menangkap
Default di distro Hampir semua distro Linux OpenBSD, FreeBSD, beberapa Linux (Arch, Alpine) n/a (bukan fitur OS)
Keamanan default NOPASSWD adalah risiko, banyak config lawas yang terlalu permisif persist yang aman, nolog untuk tracking, default deny lebih jelas Aman secara default — tanpa become: true, task berjalan sebagai user biasa

Untuk Ansible di Linux modern, sudo dengan konfigurasi spesifik per perintah adalah kombinasi yang paling umum — Ansible sudah mengemas logika sudo dengan baik lewat modul become, dan konfigurasi sudoers memberikan kita kontrol OS-level yang lebih ketat. doas menarik untuk deployment baru yang ingin konfigurasi ringkas dan keamanan default yang lebih baik. become_user sangat berguna untuk task yang harus berjalan sebagai user aplikasi tertentu (misalnya deployer, postgres) bukan sebagai root.

flowchart TD
    A["Butuh eskalasi privilege?"] -- "Tidak" --> B["Jalankan task tanpa become"]
    A -- "Ya" --> C{"Escalate ke siapa?"}
    C -- "root" --> D["sudo dengan perintah spesifik"]
    C -- "service user" --> E["become_user: deployer/postgres/..."]
    C -- "BSD/OpenBSD" --> F["doas dengan permit rule"]
    D --> G{"Log ke syslog?"}
    G -- "Ya" --> H["sudo memberikan audit trail OS"]
    F --> I["doas dengan persist 5 menit"]
    E --> J["Ansible log + become_user jelas"]

Decision tree di atas bukan urutan langkah yang harus diikuti, melainkan alat untuk memikirkan model mana yang paling cocok untuk konteks kita. Banyak tim produksi menggunakan kombinasi — sudo untuk task instalasi paket dan konfigurasi service, become_user untuk deploy aplikasi, dan tanpa eskalasi untuk task read-only.


Pemisahan Akses Berdasarkan Lingkungan #

Satu tim seharusnya tidak bisa mengakses semua lingkungan dengan credential yang sama. Pemisahan akses per lingkungan adalah kontrol fundamental: jika credential staging bocor melalui phising, commit yang tidak disengaja, atau laptop yang hilang, dampaknya terbatas hanya pada staging — production tetap aman.

Model Akses yang Direkomendasikan:

Developer:
  ✓ Bisa jalankan playbook ke development
  ✓ Bisa jalankan playbook ke staging (dengan approval)
  ✗ Tidak bisa langsung jalankan ke production

SRE/Ops:
  ✓ Bisa jalankan playbook ke semua lingkungan
  ✓ Punya vault password production
  ✓ Bisa approve deployment production

CI/CD Pipeline:
  ✓ Otomatis deploy ke staging setelah PR merge
  ✓ Deploy ke production hanya setelah manual approval
  ✗ Pipeline tidak punya akses ke vault password production secara langsung

Implementasinya menggunakan SSH key yang berbeda per lingkungan — saat ini berlaku sebagai kontrol defense in depth yang sederhana tapi efektif. Bocornya SSH key staging tidak memberi akses langsung ke production karena key-nya berbeda.

# inventory/production/group_vars/all.yml
ansible_ssh_private_key_file: ~/.ssh/ansible_production
ansible_user: ansible-prod-deploy

# inventory/staging/group_vars/all.yml
ansible_ssh_private_key_file: ~/.ssh/ansible_staging
ansible_user: ansible-staging-deploy

Untuk produksi yang lebih matang, pisahkan juga vault password per lingkungan. Kita bisa mempelajari lebih lanjut tentang pengelolaan vault password di artikel Secret Management.

# Vault ID berbeda per lingkungan
ansible-playbook site.yml \
  --vault-id production@~/.vault_pass_prod \
  --vault-id staging@~/.vault_pass_staging
Jangan pernah gunakan SSH key yang sama untuk staging dan production. Jika sebuah CI runner menggunakan key yang salah (misalnya lupa ganti inventory), konsekuensinya bisa menjadi fatal. Key terpisah membuat kesalahan ini menjadi fail loud — Ansible akan gagal dengan Permission denied alih-alih diam-diam mengubah server production.

Pipeline Deployment dengan Security Gate #

Workflow Ansible yang aman bukan hanya tentang kontrol akses, tapi juga tentang alur perubahan yang terstruktur. Setiap perubahan kode harus melewati beberapa gate sebelum sampai ke production, dan setiap gate memiliki kriteria yang jelas. Diagram berikut menggambarkan pipeline yang direkomendasikan:

flowchart LR
    A["Developer"] -->|"push branch"| B["CI: Lint & Syntax"]
    B --> C["CI: ansible-lint"]
    C --> D["CI: --check --diff ke staging"]
    D --> E["Merge Request"]
    E --> F{"Peer Review"}
    F -- "Ditolak" --> A
    F -- "Disetujui" --> G["Merge ke main"]
    G --> H["Auto deploy staging"]
    H --> I["Integration test"]
    I --> J{"Health check"}
    J -- "Gagal" --> K["Rollback + alert"]
    J -- "Lulus" --> L["Manual approval"]
    L --> M["Deploy production"]
    M --> N["Smoke test prod"]
    N -- "Gagal" --> O["Auto rollback"]
    N -- "Lulus" --> P["Audit log + notifikasi"]

Setidaknya ada empat security gate yang terlihat pada diagram: peer review sebelum merge, dry-run di CI, integration test setelah deploy staging, dan manual approval sebelum deploy production. Masing-masing gate menangkap kelas masalah yang berbeda — lint menangkap masalah sintaksis, review menangkap masalah logika dan keamanan, dry-run menangkap dampak perubahan, dan integration test menangkap regresi runtime.

Konfigurasi Pipeline #

# .gitlab-ci.yml — dry run otomatis pada setiap MR
ansible-check:
  stage: validate
  script:
    - ansible-lint
    - ansible-playbook -i inventory/staging/ site.yml --check --diff
  only:
    - merge_requests

ansible-deploy-staging:
  stage: deploy-staging
  script:
    - ansible-playbook -i inventory/staging/ site.yml
  only:
    - main

ansible-deploy-production:
  stage: deploy-production
  script:
    - ansible-playbook -i inventory/production/ site.yml
  when: manual        # Harus di-trigger manual, tidak otomatis
  only:
    - main
  environment:
    name: production
    url: https://app.example.com

when: manual adalah kontrol kritis — pipeline tidak boleh deploy ke production secara otomatis, bahkan setelah merge ke main. Manusia harus secara eksplisit mengklik tombol deploy di UI pipeline. Ini memberi kesempatan untuk review terakhir, mengecek notifikasi monitoring, dan memastikan tidak ada hal mencurigakan sebelum perubahan sampai ke production.

Kita juga bisa mengintegrasikan pipeline ini dengan sistem change management eksternal seperti ServiceNow, Jira Service Management, atau tool approval internal. Untuk diskusi lebih dalam tentang integrasi CI/CD, lihat artikel CICD Integration.


Dry Run sebagai Gate Wajib #

Wajibkan --check --diff sebelum setiap deployment ke production. Dry run memberitahukan kita apa yang akan berubah tanpa benar-benar mengubah apapun — sangat penting untuk deployment Ansible karena banyak task Ansible bersifat idempotent, dan diff dari dry run sering kali lebih informatif daripada log task yang sebenarnya.

# Langkah 1: Selalu jalankan dry run dulu
ansible-playbook -i inventory/production/ site.yml --check --diff

# Tinjau output — pastikan hanya perubahan yang diharapkan
# Baru kemudian jalankan sungguhan

# Langkah 2: Jalankan sungguhan setelah dry run ditinjau
ansible-playbook -i inventory/production/ site.yml

Mode check di Ansible bekerja dengan melaporkan apa yang akan dilakukan task tanpa benar-benar melakukannya. Untuk modul yang sepenuhnya idempotent (seperti apt, copy, template, service), ini sangat akurat. Untuk modul command atau shell, mode check tidak bisa memprediksi efek dari command arbitrer — untuk modul ini, kita perlu creates/removes argument atau changed_when yang eksplisit.

# Task dengan command yang tidak bisa diprediksi dry run
- name: Restart aplikasi setelah deploy
  command: /opt/app/bin/restart.sh
  # ANTI-PATTERN: dry run melaporkan "changed" tapi tidak jelas apa yang akan terjadi

# BENAR: gunakan service module atau changed_when eksplisit
- name: Restart aplikasi setelah deploy
  service:
    name: myapp
    state: restarted
  # Dry run melaporkan "changed" dengan jelas — service akan di-restart
Integrasikan --check --diff ke pipeline CI/CD kita. Setiap merge request harus menjalankan dry run terhadap staging sebelum diizinkan merge. Ini memberikan reviewer baseline tentang apa yang akan berubah di production — reviewer bisa langsung membandingkan diff dengan ekspektasi mereka, bukan harus menjalankan playbook di lokal untuk melihat dampaknya.

Kita bisa melihat pola changed_when lebih dalam di artikel Drift Handling yang membahas idempotency dan cara mendeteksi perubahan yang tidak terduga.


Code Review sebagai Kontrol Keamanan #

Code review sering dianggap sebagai kontrol kualitas kode, tapi dalam konteks Ansible, ia juga merupakan kontrol keamanan yang penting. Reviewer yang teliti akan menangkap hal-hal yang lolos dari automated tool: permission direktori yang terlalu longgar, secret yang tidak disengaja masuk ke variable, task yang berjalan di host yang salah, dan become: true di tempat yang tidak perlu.

ANTI-PATTERN: Skip Review untuk “Quick Fix” #

# ANTI-PATTERN: merge ke main tanpa review karena "perubahan kecil"
# .github/workflows/ansible-deploy.yml
on:
  push:
    branches: [main]    # Langsung deploy saat push, tidak ada review

“Quick fix” adalah dalih paling umum untuk melewati review, dan secara statistik adalah salah satu sumber utama insiden keamanan. Perubahan yang kelihatannya kecil (mengubah satu baris sudoers, menambahkan satu environment variable) bisa punya dampak yang tidak kecil.

BENAR: Mandatory Review dengan Checklist #

# BENAR: require review dari minimal satu peer
# .github/workflows/ansible-deploy.yml
on:
  pull_request:
    branches: [main]
    required_reviewers: 1
    required_status_checks:
      - ansible-lint
      - dry-run-staging

Tambahkan checklist review yang spesifik untuk keamanan:

CHECKLIST REVIEW PLAYBOOK:

Akses dan privilege:
  □ Apakah user yang digunakan untuk task ini punya hak minimum yang diperlukan?
  □ Apakah ada `become: true` yang bisa diganti `become_user` atau dihilangkan?
  □ Apakah sudoers yang ditambahkan spesifik per perintah (bukan ALL)?

Secret dan data sensitif:
  □ Apakah ada nilai sensitif yang tidak melalui Vault?
  □ Apakah task yang menangani secret punya `no_log: true`?
  □ Apakah file yang di-deploy punya permission yang benar (mode 0600/0640)?

Idempotency:
  □ Apakah task akan menghasilkan diff yang sama setiap kali dijalankan?
  □ Apakah ada command yang tidak bisa diprediksi dry run?

Target dan blast radius:
  □ Apakah pattern host di header play sudah benar?
  □ Apakah playbook ini diuji di staging dulu?

Checklist ini bisa disimpan sebagai PULL_REQUEST_TEMPLATE.md di repository agar otomatis muncul saat developer membuka merge request.


Sequence Diagram: Approval Flow Production #

Proses approval untuk deployment production melibatkan beberapa peran dan sistem. Sequence diagram berikut menggambarkan alur lengkap dari merge sampai deployment, dengan kontrol yang relevan:

sequenceDiagram
    participant Dev as "Developer"
    participant Git as "Repository"
    participant CI as "CI/CD Pipeline"
    participant Rev as "Reviewer"
    participant Mgr as "SRE/Ops"
    participant Prod as "Production Server"

    Dev->>Git: "push branch + open MR"
    Git->>CI: "trigger pipeline"
    CI->>CI: "ansible-lint + --check staging"
    CI-->>Git: "status check passed"
    Dev->>Rev: "request review"
    Rev->>Git: "review diff"
    Rev-->>Dev: "approve"
    Dev->>Git: "merge to main"
    Git->>CI: "trigger deploy pipeline"
    CI->>CI: "auto deploy to staging"
    CI->>Prod: "health check + integration test"
    CI->>Mgr: "notification: ready for prod"
    Mgr->>Mgr: "review monitoring + change request"
    Mgr->>CI: "manual approval click"
    CI->>Prod: "deploy production"
    Prod-->>CI: "deployment success"
    CI->>Mgr: "notification: deployed"
    CI->>Git: "tag release + write audit log"

Perhatikan siapa yang punya kontrol di setiap tahap: developer hanya bisa push dan merge setelah approval, reviewer hanya menyetujui kode, SRE/Ops adalah satu-satunya yang bisa klik tombol deploy production. Pemisahan peran ini memastikan tidak ada satu orang pun yang bisa mengirim perubahan ke production sendirian — kontrol dua orang yang sederhana tapi efektif.


Rotasi Credential #

Credential yang tidak pernah dirotasi adalah risiko keamanan tersendiri — jika bocor, penyerang punya akses selamanya. Buat playbook untuk rotasi credential yang bisa dijalankan secara terjadwal, idealnya dari pipeline CI/CD dengan jadwal yang terdokumentasi:

# playbooks/rotate-credentials.yml
---
- name: Rotasi SSH key untuk user deployment
  hosts: all
  become: true
  vars_prompt:
    - name: "new_ssh_key"
      prompt: "Masukkan SSH public key baru"
      private: false

  tasks:
    - name: Tambahkan SSH key baru
      authorized_key:
        user: ansible-deploy
        key: "{{ new_ssh_key }}"
        state: present

    - name: Verifikasi koneksi dengan key baru berhasil
      ping:
      vars:
        ansible_ssh_private_key_file: /path/to/new_key

    - name: Hapus SSH key lama setelah verifikasi berhasil
      authorized_key:
        user: ansible-deploy
        key: "{{ old_ssh_key }}"
        state: absent

Urutan rotasi SSH key penting: tambahkan key baru, verifikasi koneksi berhasil, baru kemudian hapus key lama. Jika urutan ini dibalik (hapus dulu, tambah kemudian), ada jendela waktu di mana tidak ada key valid untuk Ansible — yang berarti playbook berikutnya akan gagal, dan kalau playbook tersebut adalah playbook rotasi, kita akan terkunci.

Rotasi tidak hanya soal SSH key. Pertimbangkan juga rotasi untuk:

Kredensial Frekuensi Otomasi
SSH key deployment 90 hari Pipeline terjadwal + verifikasi
Vault password 180 hari ansible-vault rekey + commit ke Git
Service account password 60 hari Secret manager + auto-rotate
API token 30–90 hari (tergantung provider) API secret manager + TTL
TLS certificate 30–90 hari (Let’s Encrypt) Certbot + auto-renew
Sebelum menghapus SSH key lama, SELALU verifikasi koneksi dengan key baru berhasil. Pola yang aman adalah: tambah key baru → jalankan playbook verifikasi dengan key baru → jika berhasil, hapus key lama. Membalik urutan ini bisa mengunci Ansible dari server. Untuk sertifikat TLS, selalu deploy sertifikat baru dan pastikan service sudah reload sebelum sertifikat lama expired.

Logging dan Audit Trail #

Setiap eksekusi playbook ke production harus tercatat. Audit trail bukan hanya untuk forensik saat insiden, tapi juga untuk deteksi dini: pola eksekusi yang tidak biasa (misalnya deploy di luar jam kerja, atau deploy dari user yang tidak pernah deploy sebelumnya) bisa menjadi indikasi kompromi kredensial.

# ansible.cfg
[defaults]
log_path = /var/log/ansible/ansible.log
# Tambahkan task audit di awal setiap playbook production
- name: Log deployment ke audit trail
  local_action:
    module: lineinfile
    path: /var/log/deployments.log
    line: "{{ ansible_date_time.iso8601 }} | {{ lookup('env','USER') }} | {{ inventory_dir | basename }} | {{ playbook_dir | basename }}"
    create: true
  run_once: true

Log ini menangkap empat informasi penting: kapan deployment terjadi, siapa yang menjalankan, inventory mana yang digunakan, dan playbook mana yang dieksekusi. Dari empat informasi ini kita bisa merekonstruksi timeline insiden dan membatasi scope investigasi.

Mengirim Log ke Sistem Terpusat #

Log yang hanya tersimpan di satu server sulit untuk diaudit dan mudah hilang. Kirim log Ansible ke sistem log terpusat (Loki, ELK, Splunk) agar audit trail bisa bertahan bahkan jika server bermasalah:

# tasks/post-log.yml — jalankan di akhir setiap playbook
- name: Kirim metadata deployment ke Loki
  uri:
    url: "https://loki.internal/loki/api/v1/push"
    method: POST
    body_format: json
    body:
      streams:
        - stream:
            job: ansible-deploy
            env: production
          values:
            - - "{{ ansible_date_time.iso8601 }}000000000"
              - "{{ lookup('env','USER') }} menjalankan {{ playbook_dir | basename }}"
  delegate_to: localhost
  run_once: true

Untuk integrasi lebih dalam dengan stack observability, lihat artikel Logging dan Monitoring yang membahas pola log terpusat dan alerting berdasarkan event infrastruktur.


Ringkasan #

  • Least privilege: user Ansible hanya boleh punya hak yang benar-benar diperlukan — buat sudoers yang spesifik per perintah, bukan ALL=(ALL) NOPASSWD: ALL. Pilih model privilege escalation yang sesuai konteks: sudo untuk granularitas OS-level, doas untuk konfigurasi ringkas, become_user untuk task sebagai service user tertentu.
  • Pisahkan akses per lingkungan dengan SSH key dan vault password yang berbeda — bocornya credential staging tidak membahayakan production.
  • Pipeline dengan security gate: lint → dry-run → review → staging → integration test → manual approval → production. Setiap gate menangkap kelas masalah yang berbeda.
  • Dry run wajib sebelum setiap deployment production — integrasikan --check --diff ke pipeline CI/CD sebagai gate otomatis yang tidak bisa di-bypass.
  • Code review adalah kontrol keamanan, bukan hanya kontrol kualitas — gunakan checklist review yang spesifik untuk hal-hal yang lolos dari automated tool (privilege, secret, blast radius).
  • Manual approval untuk deployment production di pipeline CI/CD — otomasi boleh, tapi manusia harus menyetujui perubahan ke production. Tombol deploy di UI pipeline adalah kontrol yang sederhana tapi efektif.
  • Rotasi credential secara berkala dengan urutan yang aman — tambah baru, verifikasi, baru hapus yang lama. SSH key, vault password, service account, dan sertifikat TLS semua butuh rotasi terjadwal.
  • Audit trail melalui Ansible log, deployment log, dan integrasi dengan sistem log terpusat — setiap perubahan ke production harus bisa ditelusuri siapa, apa, kapan, dan dari mana.

← Sebelumnya: Encryption   Berikutnya: Secret Management →

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