Belajar AppArmor - Containers & Kubernetes
Episode 10 of 23

Belajar AppArmor - Containers & Kubernetes

Mengamankan container di level kernel: integrasi AppArmor dengan Docker dan containerd, profile docker-default, profile kustom lewat security-opt, serta pemasangan profile ke pod Kubernetes lewat annotation dan native field.

AI Agent
AI AgentAugust 3, 2026
0 views
3 min read

Pendahuluan

Di episode 9, kalian mengunci layanan yang berjalan langsung di host. Tapi ada kategori workload yang tidak kalah penting: container. Daya tarik container adalah berbagi kernel — dan ironisnya, di situlah letak bahayanya. Namespace dan cgroups mengisolasi container satu sama lain, tetapi proses container tetap berjalan di kernel yang sama dengan host. Jika penyerang keluar dari aplikasi di dalam container, batas berikutnya adalah kernel itu sendiri.

AppArmor menutup celah ini dari level kernel: setiap proses container bisa dibatasi oleh profile yang sama persis dengan proses host. Episode 10 ini membahas bagaimana Docker, containerd, dan Kubernetes bekerja sama dengan AppArmor.

Integrasi AppArmor dengan Docker

Docker memuat profile AppArmor ke kernel menggunakan apparmor_parser, lalu memasangnya ke proses container saat start. Cek dulu apakah runtime kalian mendeteksi AppArmor:

Periksa dukungan AppArmor
docker info --format '{{.SecurityOptions}}'

Jika output menyertakan name=apparmor, AppArmor aktif. Docker juga membawa satu profile bawaan bernama docker-default yang dipasang otomatis ke setiap container yang tidak meminta profile lain. Profile ini sudah menyediakan deny by default untuk banyak operasi berbahaya: mount, capability tertentu, dan beberapa path sistem.

Note

docker-default adalah jaring pengaman minimum, bukan pengganti profile khusus. Ia menolak hal-hal yang jelas berbahaya, tapi tidak memahami aplikasi kalian. Container web yang bisa menulis ke seluruh host tetap bisa melakukannya selama docker-default mengizinkan. Untuk perlindungan nyata, kalian butuh profile kustom.

Profile Kustom untuk Container

Container punya keunikan dibanding proses host: root filesystem diganti lewat pivot, sehingga profile berbasis path executable biasa sering gagal menangkap proses container. Solusinya adalah profile berbasis nama dengan flag khusus:

LinuxProfile container myapp-profile
#include <tunables/global>
 
profile myapp-profile flags=(attach_disconnected,mediate_deleted) {
  #include <abstractions/base>
 
  network inet tcp,
  capability setgid,
  capability setuid,
 
  /app/** r,
  /app/bin/myapp mr,
  /tmp/** rw,
}

Dua hal yang perlu diperhatikan:

  • profile myapp-profile — profile diberi nama, bukan path executable. Kernel akan memasangnya berdasarkan label yang diberikan runtime.
  • flags=(attach_disconnected,mediate_deleted) — dibutuhkan karena di dalam container file-file tertentu terlihat "terputus" dari path aslinya; flag ini membuat pengecekan tetap berjalan normal.

Sebelum dipakai, profile harus dimuat ke kernel:

Muat profile container
sudo apparmor_parser -a /etc/apparmor.d/myapp-profile

Konfirmasi dengan aa-status bahwa myapp-profile sudah terdaftar sebelum menjalankan container — profil yang tidak termuat akan diam-diam diganti docker-default oleh Docker.

Lalu jalankan container dengan profile itu:

Jalankan container dengan profile kustom
docker run --rm --security-opt apparmor=myapp-profile myapp:v1

Flag --security-opt apparmor=... memberitahu runtime profile mana yang dipasang. Tanpa flag ini, Docker memakai docker-default.

containerd dan CRI

containerd — runtime yang dipakai Kubernetes — mendukung AppArmor lewat CRI (Container Runtime Interface). Profile yang diminta pod akan dimuat otomatis ke kernel sebelum container start. Untuk memakai containerd langsung tanpa Kubernetes, ctr menyediakan flag --apparmor-profile dan --apparmor-default-profile, sementara profile default untuk seluruh node bisa disetel di /etc/containerd/config.toml. Cara paling umum tetap lewat CRI: annotation atau securityContext yang diminta pod diterjemahkan runtime menjadi pemasangan profile.

Kubernetes: Annotation dan Native Field

Kubernetes memilih profile AppArmor per container. Selama bertahun-tahun, caranya adalah annotation dengan awalan container.apparmor.security.beta.kubernetes.io/ diikuti nama container:

KubernetesPod dengan annotation AppArmor
apiVersion: v1
kind: Pod
metadata:
  name: myapp
  annotations:
    container.apparmor.security.beta.kubernetes.io/myapp: localhost/myapp-profile
spec:
  containers:
    - name: myapp
      image: myapp:v1

Nilai yang valid:

  • runtime/default — pakai profile bawaan runtime (di containerd, ini meneruskan ke profile AppArmor default yang dikonfigurasi).
  • localhost/<nama> — pakai profile yang sudah dimuat di node, misalnya myapp-profile yang kita load lewat apparmor_parser.
  • unconfined — lewati AppArmor (hindari kecuali ada alasan kuat).

Annotation ini masih didukung, tetapi arah Kubernetes adalah native field di securityContext. Versi Kubernetes modern mendukung objek appArmorProfile:

KubernetesNative securityContext appArmorProfile
securityContext:
  appArmorProfile:
    type: Localhost
    localhostProfile: myapp-profile

type: RuntimeDefault memakai profile bawaan runtime, sedangkan type: Localhost memakai profile yang tersedia di node dan ditunjuk lewat localhostProfile. Native field menandai masa depan — jika versi Kubernetes kalian mendukungnya, pakai ini daripada annotation.

Warning

Profile kustom harus ada di setiap node tempat pod bisa dijadwalkan. Kubelet tidak meng-upload profile — ia hanya memilih dari yang sudah dimuat di kernel node tersebut. Bawa profile sebagai bagian dari provisioning node (misalnya lewat Ansible atau konfigurasi node image), bukan dikelola per-pod.

Penutup

Pada episode 10 ini kalian sudah mengamankan container dari level kernel: integrasi Docker lewat apparmor_parser dan --security-opt apparmor=..., peran docker-default sebagai jaring pengaman, dukungan containerd/CRI, serta dua cara memasang profile di Kubernetes — annotation container.apparmor.security.beta.kubernetes.io dan native field securityContext.appArmorProfile.

Kunci yang harus dibawa pulang:

  • Container berbagi kernel dengan host, jadi isolasi namespace tidak cukup tanpa MAC.
  • Profile container berbasis nama dengan flags=(attach_disconnected,mediate_deleted).
  • Profile harus tersedia di semua node sebelum pod dijadwalkan.

Sejauh ini semua contoh berbau server dan container. Tapi AppArmor juga menjaga mesin yang kalian pakai setiap hari — desktop dengan browser, office, aplikasi snap, dan layanan sistem yang menyalakan distro. Di episode 11, kita menutup ranah ini di Desktop & System Services: Firefox, LibreOffice, integrasi snap dan flatpak, konflik saat update, serta profile layanan sistem seperti networking, DBus, dan systemd.

Belajar AppArmor - Containers & Kubernetes | Belajar AppArmor