Mengenkripsi Secrets di Git dengan SOPS dan age

Foto oleh woodleywonderworks on flickr
SOPS adalah tool command-line yang mengenkripsi nilai individual di dalam file YAML, JSON, ENV, INI, atau binary sambil membiarkan nama key tetap terbaca. Ini memungkinkan tim meng-commit secret ke Git sebagai file terenkripsi, menjaga diff pull request tetap bermakna, dan mengedit nilai tanpa siklus dekripsi-edit-enkripsi manual.
age memakai keypair kecil dan eksplisit yang dibuat dengan satu perintah dan tidak punya opsi konfigurasi yang bisa salah, berbeda dengan web of trust dan infrastruktur keyserver milik PGP. Untuk tim kecil yang mengenkripsi secret deploy, age lebih cepat disiapkan dan lebih mudah dipahami dibanding mengelola keyring PGP.
Simpan private key age sebagai secret yang di-mask di secret store milik platform CI, lalu export sebagai environment variable SOPS_AGE_KEY hanya untuk langkah dekripsi. SOPS otomatis membaca variable tersebut dan menulis hasil dekripsi ke file runtime yang dipakai langkah deploy lalu dibuang setelah job selesai.
Bisa. File .sops.yaml di root repository mencocokkan path file dengan regular expression di creation_rules dan menetapkan recipient age berbeda untuk tiap kecocokan. Menyimpan secret staging dan production di file terpisah dengan recipient terpisah berarti key staging yang bocor tidak akan pernah membuka nilai production.
Hapus public key age orang tersebut dari .sops.yaml lalu jalankan ulang perintah update SOPS di setiap file yang terdampak, yang membungkus ulang data key hanya untuk recipient yang tersisa. Ini memblokir dekripsi ke depan, tapi jika orang tersebut sudah menyimpan nilai plaintext secara lokal, secret aslinya juga perlu dirotasi.

Foto oleh woodleywonderworks on flickr
Ringkasan Utama
SOPS yang dipadukan dengan age memungkinkan tim mengenkripsi nilai secret satu per satu di dalam file yang tetap tersimpan di Git, sehingga diff tetap terbaca dan merge tidak bentrok. Cukup buat keypair age, atur recipient per environment lewat .sops.yaml, enkripsi dengan sops, lalu dekripsi di CI lewat environment variable SOPS_AGE_KEY, tanpa perlu secrets manager eksternal.
Setiap tim pada akhirnya menghadapi masalah yang sama: file docker-compose butuh password database, pipeline CI butuh API token, dan tidak ada yang mau menempelkannya ke Slack atau spreadsheet bersama lagi. Solusi darurat yang biasa dipakai adalah file dot-env plaintext yang disalin manual dari satu laptop ke laptop lain, perlahan tidak sinkron, dan sesekali malah ikut ter-commit tanpa sengaja. SOPS yang dipadukan dengan age menutup celah ini dengan membiarkan file terenkripsi itu sendiri hidup di Git, ter-versi dan bisa direview, sementara hanya orang dan mesin yang memegang kunci yang tepat yang bisa melihat nilai aslinya.
Ini bukan secrets manager dalam arti seperti Vault atau AWS Secrets Manager. Tidak ada server yang perlu dijalankan, tidak ada API yang dipanggil saat deploy, dan tidak ada infrastruktur tambahan yang perlu di-patch. SOPS adalah editor command-line yang mengenkripsi nilai secara langsung di dalam file YAML, JSON, ENV, INI, atau binary, dan age adalah backend enkripsi yang menghasilkan keypair kecil dan eksplisit yang menjadi dasar enkripsi SOPS. Bersama-sama, keduanya mengubah distribusi secret menjadi alur kerja Git biasa: clone repo, dekripsi dengan kunci milikmu, selesai.
Pendekatan naif untuk secrets di Git adalah mengenkripsi seluruh file dengan GPG atau password zip lalu meng-commit blob-nya. Cara ini bekerja tapi menghancurkan dua hal yang membuat Git berguna sejak awal: diff yang bisa dibaca dan kemampuan merge. SOPS sebaliknya menelusuri struktur dokumen YAML atau JSON dan hanya mengenkripsi nilai daun, membiarkan setiap nama key tetap dalam bentuk teks biasa. Hasilnya terlihat tidak biasa saat pertama kali dibuka: nama key normal di sebelah kiri, string base64 panjang di sebelah kanan, dan blok metadata kecil yang tidak terenkripsi di bagian bawah yang mencatat recipient mana saja yang bisa membuka file tersebut.
Kelompokkan secrets berdasarkan environment dalam file terpisah, seperti secrets/staging/.env dan secrets/production/.env, alih-alih satu file terenkripsi raksasa. Ini memperkecil dampak jika satu age key sampai bocor, dan menjaga agar key staging yang hanya ada di laptop tidak masuk ke daftar recipient production.
age menghasilkan keypair dengan satu perintah. Private key tetap disimpan lokal, idealnya sama sekali di luar repository, sementara public key menjadi recipient yang menjadi dasar enkripsi SOPS. Siapa pun yang memegang private key yang cocok bisa mendekripsi; selain itu tidak bisa, berapa pun luasnya file terenkripsi itu dibagikan. Berbeda dengan PGP, tidak ada keyserver untuk dipublikasikan dan tidak ada web of trust yang perlu dipikirkan; public key hanyalah string pendek yang bisa ditempelkan ke file config atau diberikan langsung ke rekan tim.
# generate a keypair
age-keygen -o keys.txt
# Public key: age1qyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqycx3z9d
# install sops (macOS example)
brew install sopsFile .sops.yaml di root repository memberi tahu SOPS public key age mana yang menjadi recipient untuk path tertentu, dicocokkan lewat regular expression. Inilah mekanisme yang memberi kunci per-environment tanpa perlu mengubah perintah encrypt itu sendiri: SOPS membaca path file, mencocokkannya dengan creation_rules, lalu otomatis memilih recipient yang tepat.
# .sops.yaml at repo root
creation_rules:
- path_regex: secrets/staging/.*\.env$
age: age1staging0000000000000000000000000000000000000000000000000000
- path_regex: secrets/production/.*\.env$
age: age1prod00000000000000000000000000000000000000000000000000000000Setelah .sops.yaml ada, mengenkripsi file baru hanya butuh satu perintah, dan hasilnya aman untuk di-commit bersama kode lainnya. Mengedit belakangan bukan berarti dekripsi-edit-enkripsi ulang secara manual; SOPS membuka konten yang sudah didekripsi di editor yang dikonfigurasi, memantau perubahan, dan mengenkripsi ulang secara otomatis begitu file disimpan dan ditutup.
sops --encrypt secrets/production/.env > secrets/production/.env.enc
git add secrets/production/.env.enc .sops.yaml
git commit -m "chore: add encrypted production secrets"
# edit later (opens decrypted content in $EDITOR, re-encrypts on save)
sops secrets/production/.env.encDetail tunggal inilah yang membuat SOPS nyaman dipakai sehari-hari, bukan sekadar bisa ditoleransi. Tidak ada yang perlu mengingat prosedur tiga langkah setiap kali connection string berubah. Tabel berikut membandingkan tiga tingkat penanganan secret yang biasa dilalui sebagian besar tim, untuk melihat posisi SOPS dan age dibanding alternatif lain.
| Pendekatan | Native di Git | Biaya berkelanjutan |
|---|---|---|
| Plaintext .env dibagikan manual | Tidak, berbahaya jika ter-commit | Tinggi: distribusi ulang manual setiap rotasi |
| SOPS dengan age | Ya, nilai terenkripsi hidup di dalam repo | Rendah: satu perintah untuk enkripsi, edit, atau rotasi |
| Secrets manager terkelola (Vault, AWS Secrets Manager) | Tidak, secret hidup di luar repo | Sedang: perlu infrastruktur untuk dijalankan dan diautentikasi |
Continuous integration butuh nilai terdekripsi yang sama seperti yang dibutuhkan aplikasi saat runtime, tapi runner CI sebaiknya tidak pernah menyimpan file kunci permanen di disk. SOPS membaca private key langsung dari environment variable SOPS_AGE_KEY, sehingga pola alaminya adalah menyimpan private key age sebagai secret CI terenkripsi dan meng-export-nya hanya selama job berjalan.
# GitHub Actions step
- name: Decrypt production secrets
env:
SOPS_AGE_KEY: ${{ secrets.SOPS_AGE_KEY_PRODUCTION }}
run: |
sops --decrypt secrets/production/.env.enc > .env
docker compose up -dPola ini berlaku di platform CI mana pun, karena bagian yang bergerak selalu sama, yaitu tiga langkah berikut.
Jangan pernah menampilkan isi file yang sudah didekripsi ke log CI untuk keperluan debugging. Sebagian besar platform CI otomatis menyamarkan nilai yang diambil dari secret store miliknya sendiri, tapi output SOPS yang sudah didekripsi hanyalah file biasa bagi runner, dan akan tercetak penuh jika kamu menjalankan cat atau mengaktifkan logging verbose.
SOPS cocok untuk secret yang kecil, terstruktur, dan berubah mengikuti ritme deploy aplikasi: kredensial database, API key pihak ketiga, signing secret, dan feature flag per environment. Ini bukan pengganti secrets manager lengkap saat kamu butuh kredensial jangka pendek yang dinamis, audit akses yang sangat rinci lintas puluhan tim, atau rotasi otomatis yang dipicu oleh event eksternal. Anggap saja SOPS sebagai konfigurasi yang ter-versi dan kebetulan terenkripsi, bukan sebagai broker kredensial runtime.
Jika kebutuhanmu melebihi kapasitas SOPS, jalur migrasinya biasanya bersifat menambah, bukan menulis ulang semuanya: tetap pakai SOPS untuk secret bootstrap dan infrastruktur, lalu pindahkan secret aplikasi yang sering berubah ke secrets manager terkelola begitu tim dan jumlah secret sudah cukup besar untuk menjustifikasinya.
Karena setiap file mencantumkan recipient-nya lewat public key, menambah atau menghapus seseorang dari tim hanyalah perubahan metadata, bukan enkripsi ulang setiap nilai secara manual. Tambahkan public key age teman tim baru ke .sops.yaml, lalu jalankan perintah update, yang membungkus ulang data key yang sudah ada untuk setiap recipient yang terdaftar tanpa menyentuh nilai terenkripsi itu sendiri.
# rotate: add a new team member's key, re-encrypt all files
sops updatekeys secrets/production/.env.enc
# revoke: remove a departing team member's age key from .sops.yaml,
# then re-run updatekeys against every affected fileMencabut akses bekerja dengan cara yang sama secara terbalik: hapus public key orang yang keluar dari .sops.yaml lalu jalankan ulang perintah update di setiap file yang terdampak. Ini hanya memblokir dekripsi ke depan; jika orang tersebut sudah menyimpan nilai plaintext lama secara lokal, rotasi secret aslinya, bukan sekadar daftar recipient SOPS.