Penutup chapter namespaces
meninggalkan satu ganjalan: di dalam container proses kita tampak sebagai root, tetapi jelas tidak bisa memformat disk host atau memuat modul kernel. Sebaliknya, banyak program hanya membutuhkan satu hak kecil dari root — web server cukup hak untuk bind port 80 — namun secara tradisional harus dijalankan sebagai root penuh dengan ratusan hak lain yang menyertainya. Linux memecah dilema ini dengan memecah root itu sendiri: privilege root diurai menjadi sekitar empat puluh satuan bernama capability, dan proses boleh membawa beberapa satuan saja. Pada chapter ini kita pelajari daftar satuan tersebut, membaca statusnya dari /proc, lalu memberi capability langsung ke sebuah biner dengan setcap.
Masalah: Satu Kunci untuk Semua Pintu #
Model permission klasik Unix mengenal dua dunia: user biasa dan UID 0. Begitu sebuah program berjalan sebagai UID 0, kernel tidak lagi memeriksa permission file, pembatasan port, maupun kuota resource — semuanya terbuka sekaligus. Model serba-atau-tidak-apa-apa ini berbahaya karena satu celah kecil pada program root langsung membuka seluruh sistem: ping misalnya hanya butuh membuat socket mentah (raw socket), tetapi karena dulu hanya root yang boleh, biner ping dijadikan setuid root — dan setiap bug di dalamnya menjadi pintu masuk ke seluruh mesin. Capability memperbaiki model ini dengan mengganti pertanyaan “apakah Anda root?” menjadi “capability mana yang Anda pegang?” — kernel memeriksa satuan yang relevan pada setiap operasi.
Root yang Dipecah menjadi Satuan Capability #
Sejak kernel 2.2, setiap pemeriksaan privilege di dalam kernel memakai satuan capability, dan “root penuh” kini hanyalah proses yang memegang semua satuan tersebut. Daftar lengkap 41 satuan beserta operasi yang dijaganya terdokumentasi di capabilities(7)
; berikut yang paling sering kita jumpai sehari-hari:
| Capability | Operasi yang dijaga |
|---|---|
CAP_NET_BIND_SERVICE | bind port di bawah 1024 |
CAP_NET_RAW | socket mentah — kebutuhan klasik ping |
CAP_NET_ADMIN | mengubah interface, routing, firewall |
CAP_DAC_OVERRIDE | melewati pemeriksaan permission file |
CAP_SYS_ADMIN | mount, pembuatan namespace, dan ratusan hal lain |
CAP_KILL | mengirim sinyal ke proses milik user lain |
Setiap proses menyimpan capability-nya dalam beberapa set yang berbeda peran: effective (yang sedang dipakai kernel saat memeriksa), permitted (persediaan yang boleh dinaikkan ke effective), dan bounding (batas atas yang tidak mungkin dilewati walau program mencoba menaikkan haknya). Status ketiganya dapat kita baca langsung dari procfs.
Membaca Bitmask Capability dari /proc/self/status #
Field CapEff dan CapBnd pada /proc/self/status menyimpan set effective dan bounding sebagai bitmask heksadesimal — bit ke-10 misalnya berarti CAP_NET_BIND_SERVICE. Karena shell kita berjalan sebagai user biasa, bitmasknya nol; bandingkan dengan proses yang menjadi “root” di dalam user namespace:
printf 'shell biasa CapEff: %s\n' "$(grep CapEff /proc/self/status | cut -f2)"
printf 'root di userns CapEff: %s\n' "$(unshare -Ur grep CapEff /proc/self/status | cut -f2)"Output yang Dihasilkan:
shell biasa CapEff: 0000000000000000
root di userns CapEff: 000001ffffffffffNilai nol pada baris pertama menandakan shell kita tidak memegang satu capability pun. Nilai kedua menunjukkan proses di dalam user namespace memegang seluruh 41 bit — ingat chapter namespaces: “root” di sana hanya berlaku di dalam kaca, dan kernel tetap memakai nilai ini saat proses menyentuh resource milik namespace-nya sendiri. Bitmask mentah sulit dibaca manusia, jadi program kecil berikut menerjemahkannya menjadi daftar nama:
#!/usr/bin/env python3
"""Dekode bitmask capability pada /proc/self/status menjadi nama."""
NAMA_CAP = [
"cap_chown", "cap_dac_override", "cap_dac_read_search", # bit 0-2
"cap_fowner", "cap_fsetid", "cap_kill", # bit 3-5
"cap_setgid", "cap_setuid", "cap_setpcap", # bit 6-8
"cap_linux_immutable", "cap_net_bind_service", # bit 9-10
"cap_net_broadcast", "cap_net_admin", "cap_net_raw", # bit 11-13
"cap_ipc_lock", "cap_ipc_owner", "cap_sys_module", # bit 14-16
"cap_sys_rawio", "cap_sys_chroot", "cap_sys_ptrace", # bit 17-19
"cap_sys_pacct", "cap_sys_admin", "cap_sys_boot", # bit 20-22
"cap_sys_nice", "cap_sys_resource", "cap_sys_time", # bit 23-25
"cap_sys_tty_config", "cap_mknod", "cap_lease", # bit 26-28
"cap_audit_write", "cap_audit_control", "cap_setfcap", # bit 29-31
"cap_mac_override", "cap_mac_admin", "cap_syslog", # bit 32-34
"cap_wake_alarm", "cap_block_suspend", "cap_audit_read", # bit 35-37
"cap_perfmon", "cap_bpf", "cap_checkpoint_restore", # bit 38-40
]
def baca_bitmask(bidang):
"""Ambil nilai heksadesimal field tertentu dari /proc/self/status."""
with open("/proc/self/status") as f:
for baris in f:
if baris.startswith(bidang + ":"):
return int(baris.split()[1], 16)
return 0
def dekode(nilai):
"""Ubah bitmask integer menjadi daftar nama capability."""
hasil = []
for i, nama in enumerate(NAMA_CAP):
if nilai >> i & 1:
hasil.append(nama)
return hasil
def tampil(label, daftar):
if not daftar:
print(f"{label}: (kosong)")
return
print(f"{label}: {len(daftar)} capability")
for nama in daftar:
print(f" {nama}")
tampil("CapEff (efektif)", dekode(baca_bitmask("CapEff")))
tampil("CapBnd (bounding)", dekode(baca_bitmask("CapBnd")))Program di atas tersimpan sebagai decode-cap.py pada bundle chapter ini. Jalankan dengan:
python3 decode-cap.pyOutput yang Dihasilkan:
CapEff (efektif): (kosong)
CapBnd (bounding): 14 capability
cap_chown
cap_dac_override
cap_fowner
cap_fsetid
cap_kill
cap_setgid
cap_setuid
cap_setpcap
cap_net_bind_service
cap_net_raw
cap_sys_chroot
cap_mknod
cap_audit_write
cap_setfcapMari kita bedah cara kerjanya:
NAMA_CAP: daftar nama capability menurut urutan bitnya di bitmask — urutan ini persis mengikuti tabel padacapabilities(7), sehingga indeksidi daftar sama dengan nomor bit di kernel.baca_bitmask(): membaca/proc/self/statusbaris demi baris; saat menemukan field yang dicari, teks heksadesimalnya dikonversi denganint(nilai, 16).dekode(): ekspresinilai >> i & 1menggeser bitmaskibit lalu menyaring bit paling kanan — hasilnya 1 bila capability nomoriaktif.tampil(): mencetak daftar hasil dekode; setCapEffkita kosong karena user biasa, sedangkanCapBndberisi 14 capability.
File Capability: Menyematkan Hak pada Biner #
Set kedua effective dan permitted diisi ulang setiap kali proses mengeksekusi program baru, dan salah satu sumbernya adalah file capability — extended attribute bernama security.capability yang tersimpan menempel pada file biner. Perintah setcap menyematkannya; getcap membacanya kembali:
unshare -Urm bash -c '
mkdir -p latih-mnt
mount -t tmpfs tmpfs latih-mnt
cp /bin/true latih-mnt/server-kecil
/usr/sbin/setcap cap_net_bind_service=+ep latih-mnt/server-kecil
/usr/sbin/getcap latih-mnt/server-kecil
'Output yang Dihasilkan:
latih-mnt/server-kecil cap_net_bind_service=epBedah blok di atas langkah demi langkah:
unshare -Urm bash -c '...': seperti pada chapter namespaces, kita meminjam user namespace (-Ur) dan mount namespace (-m) agar bisa bekerja sebagai “root” tanpa root sungguhan.mount -t tmpfs tmpfs latih-mnt:setcapmembutuhkanCAP_SETFCAPatas filesystem tempat file berada — tmpfs di dalam namespace kita memenuhinya, sementara filesystem sandbox utama milik host tidak.cp /bin/true latih-mnt/server-kecil: biner apa pun boleh; di sini kita menyalin program kecil sebagai contoh biner “server”.setcap cap_net_bind_service=+ep: operator+epmenambahkan capability ke set effective dan permitted biner tersebut; sejak detik itu siapa pun yang mengeksekusinya diberiCAP_NET_BIND_SERVICE— cukup untuk bind port 80 tanpa root.getcap: memastikan atribut telah menempel, terbaca sebagaicap_net_bind_service=ep.
Capabilities di Dunia Container #
Kombinasi namespaces + cgroups + capabilities inilah mesin keamanan container: runtime seperti Docker memulai container dengan melepas semua capability lalu menambahkan kembali daftar kecil yang aman. Parameter --cap-drop=ALL dan --cap-add=NET_BIND_SERVICE pada docker run memanipulasi himpunan tersebut secara langsung, dan perilakunya terdokumentasi pada referensi Docker run
. Untuk memeriksa set yang aktif, alat capsh --print milik paket libcap2-bin merangkum kelima set sekaligus — berikut contoh tampilannya pada sesi user biasa:
Current: =
Bounding set =cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,
cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,
cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap
Ambient set =
uid=1000(budi) euid=1000(budi)Perhatikan kesamaan pola dengan output decode-cap.py kita: baris Bounding set berisi 14 nama yang sama. Praktik paling aman untuk layanan produksi adalah merunut satu per satu: mulai dari --cap-drop=ALL, lalu tambahkan capability minimum yang membuat aplikasi berjalan — filosofi yang sama dengan principle of least privilege pada chapter security hardening
, kini dalam satuan kernel.
Satu kejanggalan masih tersisa dari perjalanan kita: proses “root” di user namespace ternyata tetap tidak bisa seenaknya memakai memori host sepuasnya. Pembatasan itu dijaga mekanisme lain — pengelola memori kernel, lengkap dengan page fault, page cache, dan hakimnya yang bernama OOM killer — yang menjadi topik chapter berikutnya.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
decode-cap.py
Unduh berkas#!/usr/bin/env python3
"""Dekode bitmask capability pada /proc/self/status menjadi nama."""
NAMA_CAP = [
"cap_chown", "cap_dac_override", "cap_dac_read_search", # bit 0-2
"cap_fowner", "cap_fsetid", "cap_kill", # bit 3-5
"cap_setgid", "cap_setuid", "cap_setpcap", # bit 6-8
"cap_linux_immutable", "cap_net_bind_service", # bit 9-10
"cap_net_broadcast", "cap_net_admin", "cap_net_raw", # bit 11-13
"cap_ipc_lock", "cap_ipc_owner", "cap_sys_module", # bit 14-16
"cap_sys_rawio", "cap_sys_chroot", "cap_sys_ptrace", # bit 17-19
"cap_sys_pacct", "cap_sys_admin", "cap_sys_boot", # bit 20-22
"cap_sys_nice", "cap_sys_resource", "cap_sys_time", # bit 23-25
"cap_sys_tty_config", "cap_mknod", "cap_lease", # bit 26-28
"cap_audit_write", "cap_audit_control", "cap_setfcap", # bit 29-31
"cap_mac_override", "cap_mac_admin", "cap_syslog", # bit 32-34
"cap_wake_alarm", "cap_block_suspend", "cap_audit_read", # bit 35-37
"cap_perfmon", "cap_bpf", "cap_checkpoint_restore", # bit 38-40
]
def baca_bitmask(bidang):
"""Ambil nilai heksadesimal field tertentu dari /proc/self/status."""
with open("/proc/self/status") as f:
for baris in f:
if baris.startswith(bidang + ":"):
return int(baris.split()[1], 16)
return 0
def dekode(nilai):
"""Ubah bitmask integer menjadi daftar nama capability."""
hasil = []
for i, nama in enumerate(NAMA_CAP):
if nilai >> i & 1:
hasil.append(nama)
return hasil
def tampil(label, daftar):
if not daftar:
print(f"{label}: (kosong)")
return
print(f"{label}: {len(daftar)} capability")
for nama in daftar:
print(f" {nama}")
tampil("CapEff (efektif)", dekode(baca_bitmask("CapEff")))
tampil("CapBnd (bounding)", dekode(baca_bitmask("CapBnd")))