- Meskipun perangkat lunak berkembang sangat pesat, sistem variabel lingkungan pada sistem operasi masih mempertahankan struktur yang sama seperti puluhan tahun lalu
- Variabel lingkungan berbentuk kamus string global, sebuah struktur sederhana tanpa namespace maupun tipe
- Di Linux, variabel lingkungan diteruskan dari proses induk ke proses anak melalui system call
execve
- Bash, glibc, Python, dan lainnya masing-masing mengelola variabel lingkungan dalam bentuk hash map, array, atau pembungkus dictionary
- Standar POSIX tidak mewajibkan nama hanya huruf besar, dan pada praktiknya aturannya cukup fleksibel, termasuk menganjurkan penggunaan nama huruf kecil dalam kasus tertentu
Apa itu variabel lingkungan
- Walaupun bahasa pemrograman berkembang cepat, fondasi eksekusi proses yang disediakan sistem operasi—khususnya bagian variabel lingkungan—hampir tidak berubah
- Saat ingin meneruskan parameter runtime ketika menjalankan aplikasi tanpa file terpisah atau IPC, kenyataannya kita sering tidak punya pilihan selain memakai antarmuka berbasis variabel lingkungan
- Variabel lingkungan berfungsi sebagai kamus string datar tanpa namespace dan tanpa tipe
Struktur pembuatan dan penerusan variabel lingkungan
- Variabel lingkungan adalah cara tradisional untuk meneruskan nilai antarproses, dikirim bersama saat proses induk menjalankan proses anak
- Artinya, strukturnya adalah pewarisan dari proses induk ke proses anak
- Di Linux, system call
execve menerima berkas executable, argumen, dan array variabel lingkungan (envp) sebagai parameter
- Contoh perintah eksekusi:
ls -lah
- filename:
/usr/bin/ls
- argv:
['ls', '-lah']
- envp:
['PATH=...','USER=...']
- Proses induk bisa meneruskan lingkungan yang sudah ada apa adanya, atau menyusun lingkungan yang sepenuhnya baru untuk proses anak
- Hampir semua alat (Bash,
subprocess.run milik Python, pustaka C execl, dll.) meneruskan variabel lingkungan apa adanya
- Sebagai pengecualian, beberapa alat seperti
login membentuk lingkungan baru
Lokasi penyimpanan dan pemrosesan internal variabel lingkungan
- Saat program dimulai, kernel menyimpan variabel lingkungan di stack dalam bentuk string null-terminated
- Data ini sulit diubah langsung oleh program, sehingga biasanya disalin lalu dikelola dalam struktur internal program itu sendiri
- Cara tiap bahasa dan shell menyimpan variabel lingkungan
- Bash: dikelola sebagai hash map (dictionary) bertumpuk
- Membuat map dengan scope lokal pada setiap pemanggilan fungsi
- Hanya variabel yang di-
export yang diteruskan ke proses anak
- Variabel yang dideklarasikan dengan
local juga bisa diteruskan ke proses anak melalui export
- Contoh: perubahan lokal pada
PATH dapat diteruskan ke proses anak lewat export PATH, tetapi tidak memengaruhi nilai global
- glibc (pustaka C): mengelola
environ berbentuk array dinamis melalui putenv dan getenv
- Karena berbentuk array, pencarian maupun perubahan memiliki kompleksitas waktu linear
- Karena itu, strukturnya tidak cocok untuk penyimpanan data yang menuntut performa tinggi
- Python: secara internal menyediakannya sebagai
os.environ yang tampak seperti dictionary, tetapi sebenarnya terhubung dengan array environ milik pustaka C
- Saat nilai
os.environ diubah, os.putenv dipanggil sehingga perubahan juga diterapkan ke pustaka C
- Sinkronisasi tidak terjadi ke arah sebaliknya, sehingga ada sifat satu arah
Format dan rentang yang diizinkan untuk variabel lingkungan
- Kernel Linux dan glibc sangat longgar terhadap format variabel lingkungan
- Nama yang sama bisa muncul berulang dengan beberapa nilai berbeda
- Entri tanpa
= juga bisa didaftarkan, dan karakter khusus seperti emoji pun tidak dibatasi
- Batas ukuran yang tersedia
- Variabel tunggal: 128 KiB (umumnya pada lingkungan x64)
- Total keseluruhan: 2 MiB (berbagi dengan argumen baris perintah)
- Variabel lingkungan dibatasi agar tidak melebihi 1/4 ruang stack
Keanehan dan edge case variabel lingkungan
- Bash, untuk variabel lingkungan yang aneh (nama duplikat, entri tanpa
=, dan sebagainya), akan menghapus nama duplikat dan mengabaikan entri yang tidak normal
- Jika nama variabel mengandung spasi, Bash tidak bisa mereferensikannya, tetapi variabel itu tetap bisa diteruskan ke proses anak
- Misalnya, Nushell dan Python dapat membuat variabel dengan nama yang mengandung spasi
- Bash menyimpan entri seperti ini dalam hash map terpisah (
invalid_env)
Aturan format standar dan penamaan variabel lingkungan
- Standar POSIX menganggap apa pun sebagai variabel selama namanya tidak mengandung tanda sama dengan (
=)
- Rekomendasi resmi: nama hanya boleh berisi huruf besar, angka, dan underscore (karakter pertama tidak boleh angka)
- Variabel huruf kecil ditujukan untuk namespace khusus aplikasi
- Alat standar hanya memakai huruf besar, tetapi penggunaan variabel huruf kecil juga diperbolehkan
- Dalam praktiknya, para pengembang umumnya memakai gaya penamaan ALL_UPPERCASE
- Aturan yang disarankan: gunakan regex
^[A-Z_][A-Z0-9_]*$ untuk nama variabel, dan UTF-8 untuk nilainya
- Jika khawatir soal pengecualian atau kompatibilitas, disarankan memakai Portable Character Set (ASCII) milik POSIX
Kesimpulan
- Variabel lingkungan tetap merupakan antarmuka yang usang tetapi esensial, berperan sebagai batas antara sistem operasi dan aplikasi
- Terlepas dari keterbatasan strukturnya, Bash, C, Python, dan lainnya tetap memanfaatkannya dengan pembungkus yang berbeda-beda
- Dalam sistem modern, kebutuhan akan pengelolaan konfigurasi dengan namespace yang jelas dan sistem tipe semakin besar
2 komentar
Sekilas tampak makin kurang penting, tetapi dengan munculnya Docker dan cloud, hal ini kembali menjadi sesuatu yang tak bisa dihindari.
Komentar Hacker News
Saya bekerja sebagai SRE/Sysadmin/DevOps/Whatever, dan walaupun di blog saya hanya membahas standardisasi environment variable secara ringan, saya ingin menekankan bahwa alternatifnya pun sama-sama menimbulkan frustrasi, terutama saat secrets ikut terlibat
Struktur di mana aplikasi mengakses penyimpanan rahasia tertentu seperti Hashicorp Vault/OpenBao/Secrets Manager dengan cepat menimbulkan vendor lock-in yang serius, dan penggantiannya merembet sampai level library sehingga menjadi sangat sulit
Ketersediaan Vault menjadi sangat krusial, dan saat upgrade atau maintenance dibutuhkan, tim operasi jadi sangat kesulitan
Jika secret dikirim lewat file config, jadi membingungkan bagaimana menyimpannya, karena file config biasanya berada di jalur publik
Pada akhirnya, Anda bergantung pada salah satu dari "mengganti lewat template sebelum sistem berprivilege menyerahkannya ke aplikasi" atau "menyimpan seluruh file config di penyimpanan rahasia lalu memberikannya ke aplikasi"
Pekerjaan templating mudah menimbulkan kesalahan, dan saat seluruh file config dipindahkan ke penyimpanan rahasia pun ada risiko seseorang mengunggah yang salah, yang cukup membuat stres
Saat ini kebanyakan sistem berjalan di atas container, dan kecuali di perusahaan yang menjaga infrastrukturnya dengan baik, file config selalu berada di lokasi yang aneh sehingga proses mount menjadi makin membingungkan dan kesalahan sering terjadi
Apa pun format yang dipakai, seperti JSON/YAML/TOML, bug khas masing-masing sudah jadi makanan sehari-hari, misalnya masalah Norway pada YAML
Saya pernah melihat pendekatan menerima secret lewat Kubernetes Secrets API, tetapi ini juga menghadapi masalah vendor lock-in yang kuat
Kecuali Anda memang sedang merancang sistem seperti operator, saya tidak terlalu menganjurkan metode ini
Saya juga pernah melihat masalah yang muncul saat menyiapkan environment variable lewat subprocess, tetapi menurut saya tim modern sekarang lebih banyak memakai sistem berbasis message bus sehingga lebih kokoh dan bisa diskalakan secara independen
Di tim kami, kami pernah membuat library Secrets umum yang ringan, lalu hanya memasang backend spesifik vendor seperti AWS Secrets Manager di belakangnya dengan model plugin
Pengaturan cache lokal dan opsi bypass cache per parameter dimungkinkan, jadi logika yang benar-benar bergantung vendor hanya tersisa di backend, sementara library dan aplikasi tetap bisa dijaga agar tidak terikat vendor
Saat pindah ke Vault, kami hanya menambahkan satu backend dan mengubah konfigurasi, lalu semuanya berjalan tanpa masalah berarti
Saya penasaran kenapa Kubernetes secret API dianggap sebagai masalah vendor lock-in
Apakah ini mungkin kasus di mana deployment yaml ingin dipakai untuk tujuan selain deployment Kubernetes?
Di kebanyakan aplikasi, secret bisa di-mount ke container lalu disuntikkan ke aplikasi dalam bentuk environment variable atau file json sehingga bisa dibaca dan ditulis secara independen dari lingkungan
Sepengetahuan saya, enkripsi backend etcd juga bisa dikonfigurasi dengan KMS
Saya juga kurang paham kenapa menerima secret lewat Kubernetes Secrets API disebut lock-in
Pada dasarnya K8s secrets tidak disimpan dalam keadaan terenkripsi, jadi menurut saya itu hanya bermakna jika Anda (0) memang memakai K8s, (1) sudah menyiapkan enkripsi di control plane, dan (2) wajib memakai solusi tambahan seperti CSI driver
Selain itu, Secret Store CSI Driver mendukung berbagai backend seperti Conjur, jadi justru kebalikannya dari lock-in
Karena alasan-alasan ini, saya masih tetap memakai config berbasis env vars dan dotenv
Struktur konfigurasi berbasis environment variable terlalu sederhana, dan kompatibel dengan baik dengan berbagai alat seperti secret manager
Dalam beberapa tahun terakhir saya mulai tertarik sedikit demi sedikit pada sOps berbasis YAML
YAML memang sangat intuitif untuk mengekspresikan struktur konfigurasi aplikasi, dan dengan sops mudah mengelola hanya sebagian yang dienkripsi
Hanya saja, pengelolaan kunci GPG cukup merepotkan, walaupun bisa diatasi dengan Vault atau OpenBao
Tetapi dalam proses itu, muncul lagi isu vendor lock-in, meski OpenBao tampaknya agak lebih ringan dalam hal itu
Kita juga bisa menerima environment variable dari hasil eksekusi command, jadi bisa ditangani tanpa vendor lock-in dan tanpa proses templating
Fakta menarik lainnya,
setenv()secara fundamental rusak di POSIX, jadi menurut saya itu tidak boleh dipakai di kode libraryKalaupun harus dipakai di kode aplikasi, itu pilihan terakhir, dan harus hanya dilakukan sebelum membuat thread
getenv()mengembalikan langsung pointer asli environment variable, jadi saatsetenv()menimpa variabel, tidak ada perlindungan sama sekaliHarus sangat berhati-hati
Menurut saya, cara yang benar untuk mengatur environment variable adalah menentukannya lewat
execve()Metode ini hanya cocok ketika informasi dikirim lewat environment variable tepat sebelum atau sesudah
exec()Saya tidak paham kenapa seseorang perlu memakai setenv di kode library
Solaris sudah menyelesaikan masalah ini, tetapi Linux masih bersikeras memakai cara yang sama
NetBSD sudah lama punya alternatif aman bernama
getenv_r(), dan baru-baru ini FreeBSD juga mengadopsinyamacOS mungkin akan segera menyusul
Sudah pernah ada upaya memasukkannya ke glibc atau POSIX, tetapi ditolak
Jika semakin banyak platform mengadopsinya, saya berharap suatu hari nanti itu juga akan diterima secara resmi
Dokumentasi NetBSD getenv_r
Commit FreeBSD
Environment variable memang sering dipakai untuk mengirim secret, tetapi menurut saya itu bukan praktik yang bagus
Di Linux, semua proses yang berjalan dengan user yang sama bisa saling melihat environment variable satu sama lain
Apa pun model ancamannya, terutama di sistem developer, ini mengkhawatirkan karena ada sangat banyak proses yang berjalan di bawah user yang sama
Masalah ini menjadi lebih serius ketika banyak proses berjalan di luar container, seperti agen LLM
Selain itu, environment variable biasanya diwariskan apa adanya ke child process, sehingga cenderung terekspos secara berlebihan meskipun hanya satu proses yang benar-benar memerlukan secret tersebut
systemd menampilkan environment variable ke semua klien sistem melalui DBUS, dan di dokumentasi resminya pun ada peringatan agar tidak menaruh secret di environment variable
Jika ini benar, berarti environment variable yang disetel pada unit khusus root pun bisa terlihat oleh user biasa, dan saya rasa ini akan cukup mengejutkan bagi banyak administrator sistem
Pada akhirnya, menurut saya hanya struktur di mana secret manager menyerahkan secret lewat berbagi file sementara (misalnya
opCLI dari 1Password, flask, terraform, dan sebagainya) yang bisa menjadi solusi untuk lepas dari paparan environment variable dan file plaintextSistem credentials milik systemd memakai struktur seperti ini. Namun dukungannya masih minim
Kalau ada yang tahu cara bagus untuk mengirim secret tanpa environment variable atau file plaintext, saya ingin sekali mendengarnya
Sebagai referensi, dalam kasus 1Password op client, saya merasa aman memakainya di sesi CLI karena setiap sesi memerlukan persetujuan saya, jadi walaupun ada proses jahat yang memanggil biner
op, tetap akan meminta persetujuan terpisahSekarang masalah yang tersisa adalah bagaimana menyerahkan secret itu ke proses yang benar-benar membutuhkannya, dan rasanya kembali lagi ke titik awal
Lihat dokumentasi resmi systemd tentang environment variable
Sejak sekitar 2012, environment variable sudah sama amannya dengan memori biasa
Riwayat commit terkait
Untuk membaca environment variable proses lain, izin ptrace mutlak diperlukan, dan jika memang sudah bisa membaca lewat ptrace, sebenarnya semua secret lain juga bisa dibaca, jadi kekhawatiran itu dianggap tidak terlalu berarti
Informasi command line (
cmdline) adalah hal lain, tetapi environment variable tidak lagi mudah bocor dengan cara iniDalam model keamanan kebanyakan sistem operasi, berjalan sebagai satu user berarti sama dengan menyerahkan seluruh hak user itu sepenuhnya
Ada fitur keamanan tambahan seperti capsicum di FreeBSD, landlock, SELinux, AppArmor di Linux, atau integrity label di Windows, tetapi kebanyakan punya keterbatasan yang jelas
Pada akhirnya saya bebas mematikan, menghentikan, atau men-debug proses saya sendiri, dan secret dari proses yang saya miliki selalu bisa diakses lewat
ptrace/process_vm_readv/ReadProcessMemorydan sebagainyaAda juga model keamanan yang sama sekali berbeda, seperti OS berbasis capability yang sempurna, tetapi sebagian besar sistem mengikuti model ini, jadi kita harus memahami keterbatasan dan tanggung jawabnya
Salah satu cara bagus untuk mengirim secret tanpa memakai environment variable atau file plaintext yang terpikir oleh saya adalah
memfd_secretman page memfd_secret
Dukungan per bahasa atau framework belum banyak, jadi mungkin layak dicoba lewat FFI, terutama di Rust atau mungkin juga Go jika memungkinkan
Saya pernah mempertimbangkan untuk membungkusnya langsung ke sisi PHP, tetapi berhenti karena tidak ingin sampai memodifikasi php-fpm
Dalam praktiknya, akan paling aman jika process manager membuka file descriptor secret terlebih dahulu lalu meneruskannya ke child process, sehingga bisa dipakai tanpa terekspos ke memori dan sebagainya
Model keamanan Unix klasik memang masih banyak dipakai dalam bentuk yang sedikit diperbaiki, tetapi keterbatasannya sangat jelas di lingkungan komputasi murah atau lingkungan modern
Jika Anda perlu menyembunyikan secret dari proses lain, pendekatan yang benar sejak awal adalah memisahkannya dan menjalankannya sebagai user lain
Atau Anda bisa memakai pendekatan akses jarak jauh sejak awal, tetapi tentu itu juga membawa kekurangan dan kompleksitas sendiri
Saat ini di platform container, mengirim config atau secret lewat environment variable cenderung direkomendasikan
Di dalam container, proses lain memang dirancang agar tidak bisa melihat environment variable
Fakta bahwa environment variable diwariskan ke child process juga memang disengaja dalam desain, karena pihak yang menyusun environment sekaligus adalah pihak yang memegang nilai secret dan menyiapkan environment itu secara langsung
Kebanyakan isu yang dikhawatirkan menurut saya bukan masalah besar, dan saya bersedia membahasnya lebih konkret jika perlu
Banyak komentar berfokus pada pengelolaan secret dan masalah-masalahnya, tetapi mungkin ada baiknya juga memikirkan kelebihan environment variable
Environment variable adalah "binding variabel yang dapat diperluas secara dinamis dengan scope tak terbatas" yang menghubungkan proses Unix secara struktural
Daripada langsung membandingkannya dengan file teks sederhana, perlu diingat bahwa alasan keberadaan environment variable adalah untuk membawa konteks dan mengirim informasi secara aman ke child process
Semakin kompleks struktur prosesnya, seperti shell bertingkat atau subprocess dari program yang rumit, menurut saya justru semakin terasa peran environment variable
Saya benar-benar ingin merekomendasikan Varlock karena sangat berguna
Ia mendefinisikan dengan jelas status wajib/opsional dari environment variable yang dibutuhkan proyek, tipe datanya, sampai dari mana nilainya diambil, dan pengelolaannya jadi mudah
Situs resmi Varlock
Berdasarkan pengalaman nyata, sebagai contoh betapa rumitnya environment variable bisa menjadi, saya pernah benar-benar tersesat saat men-debug dari mana sebuah ENV variable tertentu disetel di perusahaan lama saya
Awalnya saya kira itu diatur di
.bashrcatau hanya di satu tempat sederhana, tetapi ternyata environment variable itu disetel melalui setidaknya 10 lapisan: tingkat perusahaan, wilayah, unit bisnis, tim, individu, dan seterusnyaPada akhirnya saya baru bisa menelusurinya satu per satu setelah mengaktifkan flag debug bash
Saya tidak tahu apakah bahasa lain juga mendukung ini, tetapi Node.js baru-baru ini menambahkan flag command line untuk melacak akses dan perubahan environment variable secara akurat
Dokumentasi Node.js --trace-env
Karena nilai bisa disetel atau diubah lewat sangat banyak API, ini tampaknya akan sangat berguna saat debugging yang rumit
Ini mengingatkan pada kalimat "bukankah satu namespace saja sudah cukup"
Saya sudah lama berhenti memakai environment variable
Sekarang saya menaruh file
dmd.confdi samping compiler, dan membiarkan compiler membaca file itu langsungMasalah paling serius dari environment variable adalah sifatnya yang implisit dan tidak transparan
Dunia *nix cenderung membuat sebagian besar aplikasi bergantung pada environment variable
Bahkan jika metode konfigurasi yang eksplisit dan transparan seperti file konfigurasi, layanan remote, atau argumen command line juga didukung, dukungan untuk environment variable tetap menjadi tradisi di dunia ini
Pada akhirnya environment variable juga hanyalah hash map global yang dikloning dan diperluas untuk child process; mungkin itu desain yang masuk akal pada 1979, tetapi sekarang justru sering menjadi racun
Sebagai contoh, Kubernetes secara default mencemari environment container dengan environment variable "service link"
Jika environment variable yang diharapkan aplikasi berbenturan dengan default env var, debugging menjadi sangat sulit
Lihat dokumentasi resmi Kubernetes
Di luar itu pun, saya merasa ada sangat banyak praktik yang mempertahankan kerangka lama tanpa kritik, seperti
/bin,/usr/bin,/lib,/usr/libReferensi: Ubuntu Q&A tentang mempertahankan direktori legacy
hjkljuga bisa dilihat sebagai contoh utama dari kebiasaan usang seperti inihjkldi vi berasal dari dumb terminal 40 tahun lalu, dan terminal itu pun tidak banyak terjual(Bahkan lebih sedikit daripada Nokia N9)
Setiap kali saya mengatur environment variable di Linux, saya merasa cemas
Secara resmi, cara yang benar bekerja sedikit berbeda antar distribusi, dan meskipun mengikuti panduan online, semuanya hilang lagi setelah reboot atau menutup terminal
Saya berharap ada editor GUI environment variable global yang sederhana seperti di Windows
Memang di Windows kita harus membuka ulang terminal agar perubahan diterapkan, tetapi selain itu biasanya selalu berjalan baik
Environment variable memang sewajarnya tidak bertahan saat sesi berganti, jadi normal jika harus ditulis di tempat yang dijalankan lagi setiap sesi, seperti login atau terminal
Saat login,
.bash_profiledijalankan, dan pada child session,.bashrcyang dijalankanJika
.bash_profilediatur untuk men-source.bashrc, lalu sebagian besar konfigurasi ditaruh di.bashrc, pengelolaannya jadi mudahJika Anda tidak memakai Bash melainkan shell lain seperti zsh/fish, maka harus menyesuaikannya berdasarkan shell itu
Linux memang tidak punya GUI environment variable yang resmi dan terintegrasi yang berlaku untuk semua terminal
Bisa saja membuat GUI parsing yang rumit, tetapi biasanya lebih mudah mengubahnya langsung lewat editor teks
Dari sudut pandang saya yang terutama memakai Linux, perilaku Windows justru terasa lebih tidak nyaman
Terlalu banyak aplikasi mencemari environment variable, jadi saat sesuatu tidak berfungsi, sering kali ternyata
$SOFTWAREdijalankan dari folder yang aneh atau masalah serupa lainnyaJika memakai systemd, Anda juga bisa menulis
KEY=VALUEdi/etc/environmentatau/etc/environment.d/Sepertinya GUI untuk metode ini juga bisa dibuat
Hanya saja, environment variable tidak bisa disuntikkan ke proses yang sedang berjalan, jadi ada batasannya karena perubahan hanya berlaku setelah restart
Lihat dokumentasi resmi systemd
Komik xkcd Standards
Ini dengan jenaka menunjukkan situasi Linux, di mana sudah ada 14 cara yang saling bersaing untuk mengatur environment variable, lalu ketika seseorang berkata "mari kita satukan menjadi satu", besoknya justru ada 15 standar
Trivia environment variable favorit saya adalah bahwa PS1 dan semacamnya, yang semua orang anggap sebagai environment variable, sebenarnya bukan environment variable melainkan shell variable
PS1 bahkan tidak bisa dilihat dengan perintah
env