Bagian 1: Kenapa Saya Bikin Secure CI/CD Pipeline, dan Persiapan Tools-nya

· 7 menit baca

English
//TUTORIAL · 1 DARI 10 — Secure CI/CD Pipeline dengan Go, GitHub Actions, Vault, dan Dokploy

Di banyak tim yang pernah saya lihat, jalur dari commit sampai production itu bergantung pada ingatan orang. Ingat jalankan scanner. Ingat jangan commit .env. Ingat cek dependency. Begitu ada yang lupa sekali saja, ya sudah, lolos.

Di seri ini saya mau membalik itu. Security nggak dijalankan karena orang ingat, tapi karena platformnya memaksa. Kalau ada yang nggak beres, PR-nya nggak bisa di-merge. Titik.

Proyeknya saya beri nama secure-cicd-go. Repo-nya publik di github.com/dimss113/secure-cicd-go, jadi kamu bisa lihat hasil akhirnya sambil mengikuti seri ini.

Catatan

Aplikasi Go-nya sengaja dibuat sangat sederhana. Yang jadi "produk" di sini adalah pipeline-nya, bukan aplikasinya.

Apa yang mau dibuktikan

Saya mau menunjukkan bahwa security bisa dipasang otomatis di alur kerja developer, dari pull request sampai deploy, hanya pakai tools gratis atau open source:

  • Developer buka PR, lalu kodenya otomatis dicek: test, secret, SAST, dependency, Dockerfile, dan image.

  • Ada yang nggak beres, PR nggak bisa di-merge.

  • Kalau lolos, image dibangun sekali, di-scan, dibuatkan SBOM, disimpan di registry, lalu image yang sama persis di-deploy ke Dokploy.

  • Aplikasi nggak pernah menyimpan secret di repo. Secret diambil dari HashiCorp Vault lewat Vault Agent.

Ukuran suksesnya saya tulis dari awal, supaya jelas kapan proyek ini dianggap selesai:

  1. PR yang berisi secret, dependency rentan, kode berbahaya, atau Dockerfile jelek terbukti diblokir.

  2. Merge ke development otomatis deploy ke dev. Merge ke main deploy ke production setelah ada approval.

  3. Nggak ada satu pun nilai secret di repo publik.

  4. Orang lain bisa mengulang setup ini dari README tanpa harus tanya saya.

Saya juga tulis anti-goal, yaitu hal yang membuat proyek ini dianggap gagal walaupun "jalan":

  • Pipeline bisa dilewati, misalnya admin bisa push langsung ke main.

  • Secret muncul di log CI.

  • AppRole demo bisa membaca secret milik aplikasi lain.

Gambaran besar

Alur branch-nya seperti ini:

text
feature/*  ──PR──▶  development  ──PR──▶  main   │                     │                   │   │  [PR checks]        │ push              │ push   │  test, gitleaks,    ▼                   ▼   │  semgrep,      [deploy.yml]        [deploy.yml]   │  govulncheck,  build image         cari image dev   │  trivy config, trivy image         ⏸ approval manual   │  trivy image   SBOM (Syft)         promosi ke production   │                push GHCR           smoke test   │                deploy dev   │                smoke test   ▼gagal = tidak bisa merge

Untuk secret saat aplikasi jalan:

text
Dokploy (env: VAULT_ROLE_ID, VAULT_SECRET_ID)  → container vault-agent login AppRole ke Vault  → render template → config.json di volume tmpfs (RAM)  → container app membaca config.json (read-only)

Dan untuk secret yang dipakai CI waktu deploy, ada dua tahap:

text
Tahap A (Bagian 8) : GitHub Environment Secret DOKPLOY_API_KEYTahap B (Bagian 10): GitHub OIDC token → Vault (JWT auth) → token Dokploy

Tahap A itu sengaja dibuat dulu supaya pipeline-nya jalan. Di bagian terakhir, secret di GitHub itu saya hapus dan diganti OIDC, jadi GitHub nggak menyimpan kredensial apa pun.

Keputusan yang saya ambil di awal

Topik

Keputusan

Aplikasi

Project baru, Go, sangat sederhana

Repo

Publik. Ruleset untuk blokir merge, tab Security / code scanning, dan approval environment itu gratis hanya untuk repo publik

Branch

development jadi default branch dan deploy ke dev. main deploy ke production

Alur

feature/* → PR ke development → PR development → main

Registry

GHCR (GitHub Container Registry), gratis

Deploy

Dokploy, tipe Compose. Auto-deploy Dokploy dimatikan: hanya CI yang boleh memicu deploy

Secret runtime

Vault Agent sidecar yang me-render template ke file

Isolasi environment

AppRole dan policy terpisah untuk dev dan production. Role dev nggak bisa baca secret production

Tools

Semua gratis atau open source

Ada juga yang sengaja nggak saya kerjakan sekarang, supaya scope-nya nggak melebar:

Tidak dikerjakan

Alasan

Snyk

Bukan open source dan kuota gratisnya terbatas

Cosign (signing image), deploy pakai digest

Itu proyek tersendiri: container supply-chain security

DAST (OWASP ZAP)

Nanti, setelah environment dev stabil

Kubernetes, AWS, Terraform

Proyek lain

Database

Nggak perlu untuk demo pipeline

Security gate: kapan PR dianggap gagal

Ini inti proyeknya. Setiap baris di tabel ini nanti jadi required status check, jadi PR nggak bisa di-merge kalau salah satunya merah.

Cek

Tool

Gagal kalau

Format & test

gofmt, go vet, go test -race

Ada file belum diformat, vet error, atau test gagal

Secret scanning

Gitleaks

Ada secret apa pun

SAST

Semgrep CE (p/golang, p/default, p/secrets)

Ada temuan severity ERROR

Dependency (SCA)

govulncheck

Ada vuln yang benar-benar dipanggil kode

Dependency baru

dependency-review-action

Dependency baru dengan vuln High/Critical

Dockerfile

Trivy config

Misconfig HIGH/CRITICAL, misalnya container jalan sebagai root

Container image

Trivy image

Ada CVE CRITICAL yang sudah ada fix-nya

SBOM

Syft (CycloneDX JSON)

SBOM gagal dibuat, deploy ikut gagal

Branch asal ke main

workflow source-branch

PR ke main bukan dari development

Production

GitHub Environment production

Belum di-approve manual

Smoke test

curl /version

Versi yang jalan beda dengan commit yang di-deploy

Tips

Build sekali, promosikan ke production

Image hanya dibangun waktu push ke development, dengan tag sha-<commit>. Waktu development di-merge ke main, pipeline nggak build ulang. Ia mencari image sha-<commit> yang sudah jalan di dev, lalu memberi tag production ke image yang sama itu. Jadi yang jalan di production adalah image yang sudah di-scan dan sudah diuji di dev, bukan hasil build baru yang belum pernah dites.

Struktur repo akhirnya

Supaya kebayang, ini bentuk repo waktu semua bagian selesai:

text
secure-cicd-go/├── cmd/api/main.go              # entrypoint├── internal/                    # handler, config, jwt, aes├── Dockerfile                   # multi-stage, non-root, versi base dipatok├── docker-compose.development.yml├── docker-compose.production.yml├── vault-agent/│   ├── Dockerfile│   ├── agent.hcl                # auto_auth approle + template│   └── config.json.ctmpl        # template: isinya path secret saja├── vault/│   ├── policies/                # policy Vault, ikut di-review lewat PR│   └── roles/                   # role jwt-github (Bagian 10)├── .github/workflows/│   ├── pr-checks.yml│   ├── source-branch.yml│   └── deploy.yml├── .gitleaksignore              # exception Gitleaks (Bagian 9)├── .trivyignore                 # exception CVE + tanggal kedaluwarsa├── scripts/check-exceptions.sh  # penegak aturan exception└── docs/

Peta seri ini

  1. Gambaran dan persiapan tools (artikel ini)

  2. Vault: secret, policy, dan AppRole per environment

  3. Aplikasi Go sederhana yang membaca secret dari file

  4. Vault Agent sidecar dan Docker Compose

  5. Repo GitHub dan workflow PR sebagai security gate

  6. Ruleset yang memblokir merge, plus demo PR yang gagal

  7. Dokploy: environment dev dan production

  8. Workflow deploy: build, scan, SBOM, GHCR, Dokploy

  9. Proses exception untuk temuan security

  10. CI mengambil token Dokploy dari Vault lewat OIDC

Persiapan tools

Sekarang bagian praktiknya. Tujuannya sederhana: semua alat yang dibutuhkan sudah ada di laptop. Saya pakai Mac, jadi contohnya pakai Homebrew.

Cek dulu yang biasanya sudah ada:

bash
go version       # saya pakai go1.27docker versiongh --version

Lalu pasang Vault CLI dan scanner-nya. Scanner ini nanti jalan di CI, tapi saya pasang juga di lokal supaya bisa mencoba dulu sebelum push. Jauh lebih cepat daripada menunggu CI merah berkali-kali.

bash
brew install hashicorp/tap/vaultbrew install trivy gitleaks semgrep syft

Login GitHub CLI. Ini interaktif, jadi jalankan sendiri di terminal:

bash
gh auth login     # pilih GitHub.com → SSH → login via browsergh auth status    # harus muncul: Logged in to github.com account <username>

Cek hasil: semua perintah ini harus mencetak versi.

bash
vault versiontrivy --versiongitleaks versionsemgrep --versionsyft version

DNS untuk dev dan production

Saya butuh dua domain, satu per environment. Di panel DNS domain kamu, tambahkan dua A record yang mengarah ke IP VPS tempat Dokploy jalan:

Type

Name

Value

A

secure-cicd-dev

<IP_VPS>

A

secure-cicd

<IP_VPS>

Propagasi DNS bisa butuh beberapa menit. Cek dengan dig:

bash
dig +short secure-cicd-dev.domainkamu.com   # → IP VPSdig +short secure-cicd.domainkamu.com       # → IP VPS

HTTPS-nya nanti diurus Traefik dan Let's Encrypt di Dokploy, jadi di sini cukup A record saja.

Rangkuman

Di bagian ini belum ada kode. Yang sudah ada:

  • Tujuan, ukuran sukses, dan anti-goal yang jelas.

  • Alur branch dan alur secret.

  • Daftar security gate yang nanti jadi required check.

  • Tools di laptop dan dua record DNS.

Di Bagian 2, kita mulai dari fondasi paling penting: menyiapkan secret, policy, dan AppRole di Vault, lalu membuktikan bahwa kredensial dev memang nggak bisa membaca secret production.

Artikel terkait