Di episode ini kita akan coba bahas Kubernetes Pod Security Context untuk control Pod dan container security setting. Kita akan mempelajari bagaimana run Pod sebagai non-root user, set capability, enforce read-only filesystem, dan best practice untuk Pod security.

Catatan
Untuk kalian yang ingin membaca episode sebelumnya, bisa click thumbnail episode 36 di bawah ini
Di episode sebelumnya, kita menjelajahi NetworkPolicy, yang mengontrol lalu lintas jaringan antara Pod dan jaringan eksternal. Sekarang kita akan mendalami Pod Security Context, yang mengontrol pengaturan keamanan untuk Pod dan container.
Catatan: Disini saya akan menggunakan Kubernetes Cluster yang di install melalui K3s.
Pod Security Context mendefinisikan pengaturan terkait privilege dan access control untuk Pod atau container. Ini mengontrol aspek seperti user ID, group ID, izin filesystem, dan Linux capability. Dengan mengonfigurasi security context dengan benar, Anda dapat secara signifikan mengurangi attack surface aplikasi Anda.
Pod Security Context adalah serangkaian pengaturan terkait keamanan yang berlaku untuk Pod dan container. Ini beroperasi pada tingkat kernel Linux dan mengontrol bagaimana container berinteraksi dengan sistem host.
1. Principle of Least Privilege
Jalankan container dengan izin minimal yang diperlukan. Jangan jalankan sebagai root kecuali benar-benar diperlukan.
2. Prevent Privilege Escalation
Batasi container dari mendapatkan privilege tambahan.
3. Enforce Read-Only Filesystem
Cegah container dari memodifikasi filesystem.
4. Control Linux Capability
Berikan hanya Linux capability yang diperlukan daripada semua root capability.
5. Compliance and Security Standards
Penuhi persyaratan compliance keamanan seperti PCI-DSS, HIPAA, dan SOC 2.
Security context dapat diterapkan pada dua level:
Pod-Level - Berlaku untuk semua container di Pod Container-Level - Berlaku untuk container tertentu (menimpa pengaturan Pod-level)
apiVersion: v1
kind: Pod
metadata:
name: security-context-demo
spec:
securityContext:
runAsUser: 1000
runAsGroup: 3000
fsGroup: 2000
containers:
- name: app
image: myapp:latest
securityContext:
runAsUser: 2000
allowPrivilegeEscalation: falseapiVersion: v1
kind: Pod
metadata:
name: non-root-pod
spec:
securityContext:
runAsUser: 1000
containers:
- name: app
image: myapp:latestIni menjalankan semua container di Pod sebagai user ID 1000.
apiVersion: v1
kind: Pod
metadata:
name: mixed-users
spec:
containers:
- name: app1
image: app1:latest
securityContext:
runAsUser: 1000
- name: app2
image: app2:latest
securityContext:
runAsUser: 2000Container berbeda berjalan sebagai user berbeda.
apiVersion: v1
kind: Pod
metadata:
name: enforce-non-root
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
containers:
- name: app
image: myapp:latestPengaturan runAsNonRoot: true mencegah container berjalan sebagai root. Jika image mencoba berjalan sebagai root, Pod akan gagal dimulai.
apiVersion: v1
kind: Pod
metadata:
name: group-demo
spec:
securityContext:
runAsUser: 1000
runAsGroup: 3000
containers:
- name: app
image: myapp:latestIni menetapkan primary group ID ke 3000.
apiVersion: v1
kind: Pod
metadata:
name: fsgroup-demo
spec:
securityContext:
runAsUser: 1000
fsGroup: 2000
containers:
- name: app
image: myapp:latest
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
emptyDir: {}Pengaturan fsGroup mengubah kepemilikan group dari volume yang di-mount. Semua proses di container juga merupakan bagian dari group ini.
apiVersion: v1
kind: Pod
metadata:
name: supplemental-groups
spec:
securityContext:
runAsUser: 1000
supplementalGroups: [4000, 5000]
containers:
- name: app
image: myapp:latestProses container adalah anggota dari group 4000 dan 5000 selain primary group.
apiVersion: v1
kind: Pod
metadata:
name: readonly-fs
spec:
containers:
- name: app
image: myapp:latest
securityContext:
readOnlyRootFilesystem: true
volumeMounts:
- name: tmp
mountPath: /tmp
- name: var-log
mountPath: /var/log
volumes:
- name: tmp
emptyDir: {}
- name: var-log
emptyDir: {}Ini membuat root filesystem read-only. Container hanya dapat menulis ke volume yang di-mount.
Linux capability memecah privilege root menjadi unit yang lebih kecil. Daripada memberikan semua root capability, Anda dapat memberikan hanya yang diperlukan.
apiVersion: v1
kind: Pod
metadata:
name: drop-caps
spec:
containers:
- name: app
image: myapp:latest
securityContext:
capabilities:
drop:
- ALLIni menghapus semua Linux capability. Container berjalan dengan privilege minimal.
apiVersion: v1
kind: Pod
metadata:
name: add-caps
spec:
containers:
- name: app
image: myapp:latest
securityContext:
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICEIni menghapus semua capability tetapi menambahkan NET_BIND_SERVICE untuk memungkinkan binding ke port di bawah 1024.
| Capability | Tujuan |
|---|---|
| NET_BIND_SERVICE | Bind ke port < 1024 |
| NET_RAW | Gunakan raw socket |
| SYS_ADMIN | Berbagai operasi admin |
| SYS_TIME | Set system time |
| CHOWN | Ubah kepemilikan file |
| DAC_OVERRIDE | Bypass izin file |
| SETUID | Ubah user ID |
| SETGID | Ubah group ID |
apiVersion: v1
kind: Pod
metadata:
name: no-priv-escalation
spec:
containers:
- name: app
image: myapp:latest
securityContext:
allowPrivilegeEscalation: falseIni mencegah container mendapatkan privilege tambahan melalui setuid atau setgid binary.
apiVersion: v1
kind: Pod
metadata:
name: privileged-pod
spec:
containers:
- name: app
image: myapp:latest
securityContext:
privileged: trueContainer privileged memiliki akses ke semua device host dan capability. Gunakan hanya jika benar-benar diperlukan.
apiVersion: v1
kind: Pod
metadata:
name: selinux-demo
spec:
securityContext:
seLinuxOptions:
level: "s0:c123,c456"
type: "spc_t"
containers:
- name: app
image: myapp:latestIni menetapkan konteks SELinux untuk Pod.
Seccomp (Secure Computing) membatasi system call mana yang dapat dibuat container.
apiVersion: v1
kind: Pod
metadata:
name: seccomp-default
spec:
securityContext:
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: myapp:latestIni menggunakan default seccomp profile dari container runtime.
apiVersion: v1
kind: Pod
metadata:
name: seccomp-custom
spec:
securityContext:
seccompProfile:
type: Localhost
localhostProfile: my-profile.json
containers:
- name: app
image: myapp:latestIni menggunakan custom seccomp profile yang disimpan di node.
apiVersion: v1
kind: Pod
metadata:
name: secure-web
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 3000
fsGroup: 2000
containers:
- name: web
image: nginx:latest
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE
volumeMounts:
- name: tmp
mountPath: /tmp
- name: cache
mountPath: /var/cache/nginx
- name: run
mountPath: /var/run
volumes:
- name: tmp
emptyDir: {}
- name: cache
emptyDir: {}
- name: run
emptyDir: {}apiVersion: v1
kind: Pod
metadata:
name: secure-db
spec:
securityContext:
runAsNonRoot: true
runAsUser: 999
runAsGroup: 999
fsGroup: 999
containers:
- name: postgres
image: postgres:15
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
volumes:
- name: data
emptyDir: {}apiVersion: v1
kind: Pod
metadata:
name: minimal-privilege
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
containers:
- name: app
image: myapp:latest
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
volumeMounts:
- name: tmp
mountPath: /tmp
volumes:
- name: tmp
emptyDir: {}apiVersion: apps/v1
kind: Deployment
metadata:
name: secure-app
spec:
replicas: 3
selector:
matchLabels:
app: secure-app
template:
metadata:
labels:
app: secure-app
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 2000
containers:
- name: app
image: myapp:latest
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
volumeMounts:
- name: tmp
mountPath: /tmp
volumes:
- name: tmp
emptyDir: {}Problem: Container memiliki privilege yang tidak perlu.
# JANGAN LAKUKAN INI - Berjalan sebagai root
apiVersion: v1
kind: Pod
metadata:
name: root-pod
spec:
containers:
- name: app
image: myapp:latestSolusi: Selalu tentukan runAsUser:
apiVersion: v1
kind: Pod
metadata:
name: non-root-pod
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
containers:
- name: app
image: myapp:latestProblem: Container dapat mendapatkan privilege tambahan.
# JANGAN LAKUKAN INI - Mengizinkan privilege escalation
apiVersion: v1
kind: Pod
metadata:
name: escalation-pod
spec:
containers:
- name: app
image: myapp:latestSolusi: Nonaktifkan privilege escalation:
apiVersion: v1
kind: Pod
metadata:
name: secure-pod
spec:
containers:
- name: app
image: myapp:latest
securityContext:
allowPrivilegeEscalation: falseProblem: Container dapat memodifikasi filesystem.
# JANGAN LAKUKAN INI - Root filesystem dapat ditulis
apiVersion: v1
kind: Pod
metadata:
name: writable-pod
spec:
containers:
- name: app
image: myapp:latestSolusi: Buat root filesystem read-only:
apiVersion: v1
kind: Pod
metadata:
name: readonly-pod
spec:
containers:
- name: app
image: myapp:latest
securityContext:
readOnlyRootFilesystem: true
volumeMounts:
- name: tmp
mountPath: /tmp
volumes:
- name: tmp
emptyDir: {}Problem: Container memiliki capability yang tidak perlu.
# JANGAN LAKUKAN INI - Container memiliki semua capability
apiVersion: v1
kind: Pod
metadata:
name: all-caps-pod
spec:
containers:
- name: app
image: myapp:latestSolusi: Hapus semua capability dan tambahkan hanya yang diperlukan:
apiVersion: v1
kind: Pod
metadata:
name: minimal-caps-pod
spec:
containers:
- name: app
image: myapp:latest
securityContext:
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICEProblem: Container memiliki akses penuh ke host.
# JANGAN LAKUKAN INI - Privileged container
apiVersion: v1
kind: Pod
metadata:
name: privileged-pod
spec:
containers:
- name: app
image: myapp:latest
securityContext:
privileged: trueSolusi: Gunakan capability tertentu sebagai gantinya:
apiVersion: v1
kind: Pod
metadata:
name: specific-caps-pod
spec:
containers:
- name: app
image: myapp:latest
securityContext:
capabilities:
drop:
- ALL
add:
- NET_ADMINsecurityContext:
runAsNonRoot: true
runAsUser: 1000securityContext:
capabilities:
drop:
- ALLsecurityContext:
readOnlyRootFilesystem: true
volumeMounts:
- name: tmp
mountPath: /tmpsecurityContext:
allowPrivilegeEscalation: falseTerapkan security context di Pod level sehingga semua container mewarisinya:
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 2000
containers:
- name: app
image: myapp:latestGunakan Pod Security Policy (atau Pod Security Standard di versi lebih baru) untuk menerapkan persyaratan security context di seluruh cluster.
Verifikasi bahwa pengaturan security context Anda berfungsi dengan benar:
# Periksa running user
kubectl exec -it pod-name -- id
# Periksa izin filesystem
kubectl exec -it pod-name -- ls -la /
# Periksa capability
kubectl exec -it pod-name -- grep Cap /proc/self/statuskubectl describe pod pod-name
# Cari bagian "Security Context"kubectl get pod pod-name -o yaml
# Cari field securityContextkubectl exec -it pod-name -- id
# Output: uid=1000(user) gid=3000(group) groups=2000(fsgroup)kubectl exec -it pod-name -- grep Cap /proc/self/status
# Menunjukkan capability saat ini1. Container Runtime Dependent
Beberapa pengaturan security context bergantung pada container runtime (Docker, containerd, dll).
2. Host Kernel Limitations
Security context bergantung pada fitur kernel Linux. Beberapa pengaturan mungkin tidak berfungsi di semua sistem.
3. No Application-Level Security
Security context tidak melindungi dari kerentanan tingkat aplikasi.
4. Requires Proper Image Setup
Container image harus dirancang untuk berjalan sebagai non-root.
Pada episode 37 ini, kita telah membahas Pod Security Context di Kubernetes secara mendalam. Kita sudah belajar bagaimana control security setting untuk Pod dan container, jalankan sebagai non-root user, manage capability, dan enforce read-only filesystem.
Key takeaway:
Dengan mengonfigurasi Pod Security Context dengan benar, Anda secara signifikan mengurangi attack surface aplikasi Anda dan mengikuti best practice keamanan.
Catatan
Untuk kalian yang ingin melanjutkan ke episode selanjutnya, bisa click thumbnail episode 38 di bawah ini