# Mengenkripsi Secrets di Git dengan SOPS dan age

> Cara menyimpan API key dan kredensial database dengan aman di dalam repository Git menggunakan SOPS dan age, lengkap dengan kunci per-environment dan dekripsi di CI.

URL: https://www.matthewswong.com/id/blog/sops-age-encrypted-secrets-git/

- Author: Matthews Wong (https://www.matthewswong.com/id/about/)
- Published: 2026-07-08
- Updated: 2026-07-08
- Category: Security

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.

## Kenapa mengenkripsi nilai, bukan seluruh file

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.

Reviewer pull request bisa melihat bahwa DATABASE_PASSWORD berubah tanpa pernah melihat nilai lama maupun barunya.

Dua engineer bisa menambahkan key berbeda ke file yang sama di branch terpisah dan melakukan merge tanpa konflik, karena baris yang tidak berubah tetap identik secara byte.

File tetap terlihat seperti file env atau config biasa secara struktur, sehingga tooling yang sudah ada yang memeriksa nama key tetap berfungsi.

> 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.

## Menyiapkan age key dan konfigurasi SOPS

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.

File .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.

## Alur kerja harian: enkripsi, commit, edit

Setelah .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.

Detail 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

## Dekripsi di CI tanpa menyimpan file kunci

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.

Pola ini berlaku di platform CI mana pun, karena bagian yang bergerak selalu sama, yaitu tiga langkah berikut.

Simpan private key age sebagai secret yang di-mask di secret store milik platform CI, dibatasi hanya untuk branch atau environment yang membutuhkannya.

Export sebagai SOPS_AGE_KEY hanya untuk langkah dekripsi, sehingga tidak pernah muncul di build log atau file perantara.

Dekripsi ke file runtime seperti .env tepat sebelum proses yang memakainya dimulai, dan biarkan file itu dibuang saat container job berakhir.

> 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.

## Apa yang cocok masuk SOPS dan apa yang tidak

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.

Cocok: beberapa environment variable per target deploy yang diedit tim kecil beberapa kali sebulan.

Cocok: repository infrastructure-as-code, di mana variabel Terraform atau secret Kubernetes perlu bergerak bersama kode yang mereferensikannya.

Kurang cocok: ribuan kredensial database yang diterbitkan secara dinamis dan butuh jejak audit per-request, yang lebih pas ditangani secrets manager sungguhan dengan API.

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.

## Rotasi kunci dan mencabut akses

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.

Mencabut 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.

## Sumber

- Dokumentasi SOPS: identitas age
- getsops/sops di GitHub
- FiloSottile/age di GitHub

> **Key takeaway:** 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.

## Source links

- https://getsops.io/docs/usage/identities/age/
- https://github.com/getsops/sops
- https://github.com/FiloSottile/age

## FAQ

### Apa kegunaan SOPS?

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.

### Kenapa memakai age dan bukan PGP untuk SOPS?

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.

### Bagaimana cara mendekripsi secret SOPS di pipeline CI?

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.

### Bisakah saya memakai age key berbeda untuk staging dan production?

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.

### Bagaimana cara mencabut akses seseorang ke secret terenkripsi di Git?

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.

---

- HTML version: https://www.matthewswong.com/id/blog/sops-age-encrypted-secrets-git/
- English: https://www.matthewswong.com/en/blog/sops-age-encrypted-secrets-git/
- Site index for agents: https://www.matthewswong.com/llms.txt
- Sitemap: https://www.matthewswong.com/sitemap.xml
- API description (OpenAPI): https://www.matthewswong.com/openapi.json
