Security Context

Pengamanan container dengan security context: non-root, filesystem hanya-baca, dan capabilities.

Daftar isi

RBAC mengatur siapa yang boleh berbicara dengan API server, sedangkan security context mengatur apa yang boleh dilakukan proses container setelah Pod berjalan. Topik ini membahas pengaturan security context pada level Pod dan container, mulai dari menjalankan proses sebagai non-root, filesystem yang hanya-baca, larangan privilege escalation, sampai pembagian capabilities, beserta audit manifest yang dapat dijalankan di sandbox. Semua contoh menggunakan manifest Pod aplikasi catatan pada namespace aplikasi-catatan.

Pengaturan pada level Pod dan container#

Bidang securityContext dapat ditulis di dua tempat pada spec Pod. Pengaturan pada level Pod berlaku untuk semua container di dalamnya dan untuk volume yang dipasang, sedangkan pengaturan pada level container hanya berlaku untuk container tersebut dan menimpa nilai yang diwarisi dari Pod. Kebiasaan yang lazim adalah menaruh pengaturan identitas proses seperti runAsNonRoot dan runAsUser di level Pod, lalu menaruh pengaturan yang khusus per container seperti capabilities di level container.

Manifest berikut menerapkan kombinasi lengkap untuk Pod aplikasi catatan:

yaml pod-securitycontext.yaml
apiVersion: v1
kind: Pod
metadata:
  name: catatan
  namespace: aplikasi-catatan
spec:
  securityContext:
    runAsNonRoot: true
    runAsUser: 65532
    fsGroup: 65532
  containers:
    - name: catatan
      image: busybox:1.36
      command: ["sh", "-c", "sleep 3600"]
      securityContext:
        readOnlyRootFilesystem: true
        allowPrivilegeEscalation: false
        capabilities:
          drop: ["ALL"]
          add: ["NET_BIND_SERVICE"]

Nilai runAsUser 65532 adalah uid non-root yang lazim tersedia pada image minimal, dan fsGroup 65532 menjadikan volume yang dipasang milik grup tersebut sehingga proses aplikasi dapat menulis padanya. Container yang sama menerima pengaturan ketat pada level container, dan setiap pengaturan di atas dibahas satu per satu pada bagian berikutnya.

Pembagian dua level ini juga menyederhanakan peninjauan manifest. Nilai yang berlaku untuk seluruh Pod cukup dibaca sekali pada level Pod, sedangkan pengecualian per container terlihat berdampingan dengan container yang mengaturnya. Apabila kedua level menetapkan bidang yang sama, nilai pada level container yang menang, sehingga container khusus, misalnya sidecar yang membutuhkan hak lebih besar, dapat menyimpang tanpa mengubah container lainnya.

Menjalankan proses sebagai non-root#

Proses yang berjalan sebagai root di dalam container memperluas dampak penyusupan, karena setiap celah pada aplikasi langsung memberikan akses root pada namespace container. Pengaturan runAsNonRoot: true membuat kubelet menolak container yang image-nya dijalankan sebagai root, dan runAsUser menetapkan uid konkret yang digunakan prosesnya. Kedua pengaturan ini saling melengkapi, karena runAsNonRoot menegakkan kebijakan sedangkan runAsUser menyediakan uid yang memenuhi kebijakan tersebut.

Pemilihan uid juga memengaruhi kepemilikan berkas pada volume. Nilai fsGroup mengatur grup pemilik volume yang dipasang, sehingga berkas yang dibuat proses aplikasi dapat dibaca dan ditulis oleh container lain dalam Pod yang sama tanpa mengubah izin bawaan image. Nilai uid tinggi seperti 65532 lazim dipilih karena tidak bertabrakan dengan akun sistem pada image umum.

Filesystem hanya-baca dan larangan eskalasi#

readOnlyRootFilesystem: true memasang filesystem root container dalam mode hanya-baca, sehingga penulisan yang berasal dari luar volume ditolak. Pengaturan ini mengecilkan dampak penyusupan karena penyerang yang mendapat akses eksekusi tidak dapat menulis berkas baru pada filesystem container. Aplikasi yang memang perlu menulis, misalnya menyimpan unggahan sementara atau log, diberikan volume khusus, dan volume emptyDir adalah pilihan yang umum untuk kebutuhan sementara semacam itu.

allowPrivilegeEscalation: false menutup jalur kenaikan hak yang melewati mekanisme setuid dan setgid pada proses di dalam container. Nilai bawaannya true, sehingga larangan ini perlu ditulis secara eksplisit pada manifest yang mengutamakan keamanan. Kedua pengaturan biasanya ditulis bersama, karena filesystem yang hanya-baca membatasi penyimpanan hasil penyusupan, sedangkan larangan eskalasi membatasi perubahan hak proses yang sudah berjalan. Pengaturan hanya-baca juga tidak memengaruhi volume, sehingga berkas pada volume yang dipasang tetap dapat ditulis sesuai izinnya. Larangan eskalasi berhubungan langsung dengan pembagian kemampuan pada bagian berikutnya karena mekanisme setuid adalah salah satu jalur paling umum untuk memperoleh kemampuan yang tidak disetujui.

Capabilities#

Capabilities adalah pembagian hak istimewa root Linux menjadi unit kecil yang dapat diberikan secara terpisah. Container berjalan dengan hak minimum apabila seluruh kemampuan dibuang terlebih dahulu melalui drop: ["ALL"], lalu kemampuan tertentu ditambahkan kembali satu per satu pada add. Kemampuan NET_BIND_SERVICE adalah contoh yang lazim, yaitu izin mengikat port di bawah 1024 yang dibutuhkan aplikasi web yang tidak berjalan sebagai root.

Manifest sebelumnya menerapkan pola tersebut pada container catatan, dengan kemampuan tambahan hanya NET_BIND_SERVICE sebagai contoh kebutuhan mengikat port rendah. Pola drop ALL lalu tambah minimum memudahkan peninjauan, karena daftar add yang pendek langsung memperlihatkan hak istimewa yang diminta aplikasi, sedangkan daftar kemampuan bawaan yang panjang justru menyembunyikannya. Setiap kemampuan yang ditambahkan adalah permukaan serangan baru, sehingga pemeriksaan daftar ini perlu dilakukan setiap kali image diganti.

Pemeriksaan kemampuan yang aktif pada container berjalan dapat dilakukan dari dalam container, misalnya melalui berkas /proc/self/status pada baris CapEff, tetapi audit pada level manifest lebih cepat karena tidak memerlukan Pod yang berjalan. Audit manifest juga dapat dijalankan sebelum penerapan, sehingga penolakan yang bersumber dari kemampuan istimewa terdeteksi lebih awal.

Audit manifest dengan skrip#

Skrip berikut membaca pod-securitycontext.yaml dari bundle dan menampilkan audit ringkas per container, termasuk nilai yang diwarisi dari level Pod. Ketiga bidang yang diperiksa adalah pengaturan yang paling sering dituntut kebijakan keamanan, dan audit menampilkan nilainya apa adanya, termasuk nilai bawaan yang tidak ditulis di manifest. Dengan demikian, nilai yang tampak pada keluaran selalu mencerminkan nilai yang benar-benar berlaku pada runtime container. Pemeriksaan dilakukan dengan PyYAML, sehingga keluarannya dapat dihitung ulang oleh pembaca yang menjalankan skrip di direktori topik:

python audit-securitycontext.py
"""Audit ringkas security context pada setiap container dalam manifest pod."""
import yaml

with open("pod-securitycontext.yaml", encoding="utf-8") as f:
    pod = yaml.safe_load(f)

sc_pod = pod["spec"].get("securityContext", {})
print(f"Pod {pod['metadata']['name']} di namespace {pod['metadata']['namespace']}")
print(
    f"  level pod : runAsNonRoot={sc_pod.get('runAsNonRoot')}, "
    f"runAsUser={sc_pod.get('runAsUser')}, fsGroup={sc_pod.get('fsGroup')}"
)
print()

for container in pod["spec"]["containers"]:
    sc = container.get("securityContext", {})
    # nilai pada level container menimpa nilai pada level pod
    run_as_non_root = sc.get("runAsNonRoot", sc_pod.get("runAsNonRoot"))
    ro_fs = sc.get("readOnlyRootFilesystem", False)
    eskalasi = sc.get("allowPrivilegeEscalation", True)
    caps = sc.get("capabilities", {})
    print(f"container {container['name']}")
    print(f"  runAsNonRoot             : {run_as_non_root}")
    print(f"  readOnlyRootFilesystem   : {ro_fs}")
    print(f"  allowPrivilegeEscalation : {eskalasi}")
    print(f"  capabilities             : drop={caps.get('drop', [])}, add={caps.get('add', [])}")
    print()
text Keluaran
Pod catatan di namespace aplikasi-catatan
  level pod : runAsNonRoot=True, runAsUser=65532, fsGroup=65532

container catatan
  runAsNonRoot             : True
  readOnlyRootFilesystem   : True
  allowPrivilegeEscalation : False
  capabilities             : drop=['ALL'], add=['NET_BIND_SERVICE']

Keluaran memperlihatkan tiga pengaturan yang menjadi acuan minimum keamanan container modern, yaitu runAsNonRoot bernilai true, readOnlyRootFilesystem bernilai true, dan allowPrivilegeEscalation bernilai false, ditambah pembagian capabilities yang hanya menyisakan NET_BIND_SERVICE. Nilai runAsNonRoot pada keluaran memperlihatkan warisan antara level secara langsung, yaitu bidang yang tidak ditulis pada container tetapi tetap bernilai true karena diambil dari level Pod. Pola audit semacam ini dapat diterapkan pada manifest Pod lain dengan menyalin pola kodenya, sehingga pemeriksaan minimum dapat diterapkan konsisten pada banyak aplikasi. Perintah berikut menerapkan manifest tersebut ke kluster:

bash
# tidak-dijalankan
# terapkan pod dengan security context ke namespace aplikasi-catatan
kubectl apply -f pod-securitycontext.yaml

# periksa status pod yang baru dibuat
kubectl get pod catatan -n aplikasi-catatan
text Contoh keluaran
pod/catatan created
NAME      READY   STATUS    RESTARTS   AGE
catatan   1/1     Running   0          4s

Kesalahan umum#

Tiga kesalahan berikut paling sering muncul saat security context mulai ditulis untuk aplikasi nyata, dan ketiganya berhenti pada titik yang berbeda, yaitu pembuatan Pod, runtime aplikasi, dan peninjauan keamanan:

  • Menulis runAsNonRoot tanpa memastikan image mendukung uid non-root, sehingga Pod berhenti dengan status CreateContainerConfigError. Pemilihan uid juga perlu dicocokkan dengan kepemilikan berkas pada image, karena proses non-root yang tidak dapat membaca berkas aplikasinya ikut gagal. Perbaikannya adalah mengatur runAsUser atau membangun ulang image dengan pengguna non-root.
  • Mengaktifkan readOnlyRootFilesystem tanpa menyediakan volume untuk direktori temporer. Aplikasi yang menulis ke /tmp langsung gagal saat runtime dengan pesan read-only file system. Perbaikannya adalah memasang volume emptyDir pada mountPath /tmp.
  • Menambahkan kembali kemampuan istimewa seperti NET_ADMIN setelah drop ALL tanpa kebutuhan nyata. Kebiasaan itu membatalkan sebagian besar manfaat pembatasan kemampuan. Perbaikannya adalah menyebut kemampuan minimum sesuai kebutuhan aplikasi, dan daftar yang masuk akal biasanya tidak lebih dari satu atau dua entri.

Ringkasan#

  • securityContext dapat ditulis pada level Pod untuk semua container dan pada level container untuk menimpa nilai yang diwarisi.
  • runAsNonRoot dan runAsUser menjalankan proses sebagai pengguna non-root, sedangkan fsGroup mengatur grup pemilik volume yang dipasang.
  • readOnlyRootFilesystem dan allowPrivilegeEscalation menutup penulisan pada filesystem container serta jalur kenaikan hak proses.
  • capabilities diatur dengan drop ALL, lalu kemampuan minimum seperti NET_BIND_SERVICE ditambahkan.

Referensi#