Network Policy #

Di dalam lingkungan Kubernetes standar, model jaringan yang dianut adalah flat network yang bersifat terbuka (default-open). Ini berarti, secara bawaan, semua Pod di dalam kluster dapat saling berkomunikasi satu sama lain secara langsung tanpa adanya hambatan firewall. Dari perspektif kemudahan development, hal ini sangat menyenangkan. Namun, untuk lingkungan kluster produksi—terutama yang berjalan secara multi-tenant atau meng-host berbagai microservices dengan tingkat klasifikasi data yang berbeda—kebijakan terbuka ini adalah mimpi buruk keamanan (security nightmare).

Tanpa adanya pembatasan, sebuah Pod staging yang rentan atau Pod ujicoba (development) yang tidak aman dapat langsung mengirimkan lalu lintas data ke Pod database produksi lintas namespace. Jika satu kontainer web publik berhasil dieksploitasi oleh hacker, hacker tersebut dapat dengan bebas melakukan pemindaian port (port scanning) ke seluruh layanan internal kluster kita.

Untuk mengatasi celah keamanan ini, Kubernetes menyediakan resource NetworkPolicy. NetworkPolicy bertindak sebagai firewall internal kluster pada Layer 3 (IP Address) dan Layer 4 (Port/Protokol) yang dikendalikan secara deklaratif menggunakan label selector.

Artikel ini akan mengupas tuntas prinsip kerja NetworkPolicy, implementasi arsitektur keamanan Zero-Trust, rincian sintaks whitelisting, pentingnya DNS egress rules, serta keterbatasan teknisnya.


Prinsip Kerja NetworkPolicy: Firewall Berbasis Label #

Berbeda dengan firewall tradisional yang mengharuskan kita menuliskan aturan berdasarkan alamat IP statis yang kaku, NetworkPolicy bekerja sepenuhnya menggunakan kekuatan Label Selector. Hal ini sangat selaras dengan sifat dinamis Kubernetes di mana alamat IP Pod terus berubah (ephemeral).

Manifes NetworkPolicy mendefinisikan aturan menggunakan tiga filter seleksi utama:

  • podSelector: Memilih Pod mana di dalam namespace lokal yang akan dikenai kebijakan (target pods).
  • ingress: Menentukan aturan lalu lintas masuk yang diizinkan (whitelisted incoming traffic).
  • egress: Menentukan aturan lalu lintas keluar yang diizinkan (whitelisted outgoing traffic).

Mari kita perhatikan arsitektur dasar penyaringan lalu lintas oleh NetworkPolicy melalui diagram berikut:

flowchart TD
    ClientNormal["Frontend Pod (app=frontend)"] -- "Port 8080 (Diizinkan)" --> TargetPod["Target Pod (app=api)"]
    ClientRisky["Staging Pod (app=staging)"] -. "Blocked (Tidak terdaftar)" .-> TargetPod
    TargetPod -- "Port 5432 (Diizinkan)" --> DB["Database Pod (app=database)"]
    TargetPod -. "Blocked (Port 22)" .-> SSH["SSH Server (Port 22)"]
    
    style ClientRisky fill:#ffcccc,stroke:#ff0000,stroke-width:2px
    style SSH fill:#ffcccc,stroke:#ff0000,stroke-width:2px

1. Sifat NetworkPolicy: Aditif (Additive) #

Satu hal yang wajib kita ingat adalah NetworkPolicy bersifat additive (penjumlahan). Di Kubernetes, tidak ada perintah “DENY” secara eksplisit pada aturan. Semua aturan bersifat “ALLOW” (whitelisting).

Jika kita membuat lebih dari satu NetworkPolicy yang menargetkan Pod yang sama, Kubernetes API Server akan menggabungkan seluruh aturan tersebut menggunakan logika OR. Tidak ada aturan yang saling meniadakan atau mengesampingkan.

2. Penegakan Kebijakan oleh CNI (CNI Enforcement) #

NetworkPolicy hanyalah objek definisi konfigurasi di database etcd kluster. Kubernetes API Server tidak melakukan pemblokiran paket secara langsung. Tugas penegakan (enforcement) aturan diserahkan sepenuhnya kepada driver CNI yang aktif di kluster kita.

[!IMPORTANT] CNI standar yang ringan seperti Flannel tidak mendukung dan mengabaikan seluruh manifes NetworkPolicy yang kita buat. Jika kita ingin menerapkan NetworkPolicy, kita wajib menggunakan CNI yang memiliki modul keamanan tingkat kernel seperti Calico (menggunakan iptables/IP sets di host) atau Cilium (menggunakan eBPF data path).


Pola Keamanan Zero-Trust: Default Deny All #

Praktik terbaik (best practice) dalam mengamankan kluster produksi adalah menerapkan pendekatan Zero-Trust. Kita berasumsi bahwa seluruh lalu lintas jaringan adalah berbahaya hingga terbukti sebaliknya.

Langkah pertama kita adalah menerapkan kebijakan Default Deny All pada namespace produksi kita. Kebijakan ini akan menutup seluruh port masuk (ingress) dan keluar (egress) untuk seluruh Pod di namespace tersebut. Setelah pintu tertutup rapat, baru kita membuat manifes NetworkPolicy spesifik secara bertahap untuk membuka akses secara selektif.

Manifes Default Deny All (Ingress & Egress) #

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all-policy
  namespace: production
spec:
  podSelector: {} # {} Kosong berarti menargetkan SEMUA Pod di namespace 'production'
  policyTypes:
    - Ingress
    - Egress
  # Karena blok ingress dan egress dikosongkan, maka seluruh lalu lintas masuk dan keluar diblokir total!

Penyelaman Sintaks: Whitelisting Ingress & Egress #

Setelah semua lalu lintas diblokir, kita harus membuka akses secara presisi. Mari kita pelajari manifes NetworkPolicy untuk mengisolasi database PostgreSQL produksi agar hanya bisa dihubungi oleh backend aplikasi kita:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: postgres-isolation-policy
  namespace: production
spec:
  podSelector:
    matchLabels:
      app: postgres-database # Kebijakan ini berlaku khusus untuk Pod berlabel app=postgres-database
  policyTypes:
    - Ingress
  ingress:
    - from:
        # 1. Mengizinkan akses dari Pod berlabel app=backend-api di namespace yang sama
        - podSelector:
            matchLabels:
              app: backend-api
      ports:
        - protocol: TCP
          port: 5432 # Hanya izinkan lalu lintas masuk ke port database PostgreSQL

Logika Kritis: AND vs OR pada Selector #

Salah satu area yang paling sering memicu kesalahan konfigurasi fatal adalah penulisan sintaks filter lintas namespace. Mari kita perhatikan perbedaan penulisan tanda hubung (-) pada YAML berikut karena memiliki arti logika yang sangat berbeda:

Logika AND (Namespace DAN Pod) #

Jika kita ingin membatasi akses hanya dari Pod berlabel app=prometheus yang berada di dalam namespace berlabel kubernetes.io/metadata.name: monitoring, kita menuliskan kedua selector tanpa dipisahkan tanda hubung baru:

      ingress:
        - from:
            - namespaceSelector:
                matchLabels:
                  kubernetes.io/metadata.name: monitoring
              podSelector: # AND: Pod harus app=prometheus DI DALAM namespace monitoring
                matchLabels:
                  app: prometheus

Logika OR (Namespace ATAU Pod) #

Jika kita memisahkan kedua selector menggunakan tanda hubung - baru pada masing-masing properti, artinya kita menggunakan logika OR:

      ingress:
        - from:
            - namespaceSelector:
                matchLabels:
                  kubernetes.io/metadata.name: monitoring
            - podSelector: # OR: Seluruh Pod di namespace monitoring BISA masuk, ATAU Pod app=prometheus dari namespace MANAPUN bisa masuk
                matchLabels:
                  app: prometheus

Sintaks OR di atas sangat berbahaya karena secara tidak sengaja mengizinkan Pod berlabel app=prometheus yang berjalan di namespace staging/dev yang tidak aman untuk masuk menghubungi database produksi kita.


Pentingnya Whitelisting DNS (Port 53) untuk Egress #

Saat kita menerapkan kebijakan Default Deny Egress (memblokir seluruh lalu lintas keluar dari Pod), kita sering kali mendapati aplikasi kita tiba-tiba error dan tidak bisa terhubung ke layanan apa pun. Hal ini terjadi karena kita lupa memberikan izin keluar untuk resolusi nama domain ke CoreDNS.

Tanpa adanya izin egress ke port 53 CoreDNS, Pod backend kita tidak akan bisa mengetahui alamat IP milik Service database lokal kluster maupun nama domain API eksternal (seperti AWS RDS atau Stripe API).

Contoh Manifes Egress Teroptimasi (Termasuk DNS Whitelist) #

Berikut adalah manifes NetworkPolicy untuk Pod backend kita yang membatasi akses keluar hanya ke database PostgreSQL dan ke CoreDNS kluster untuk resolusi nama:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: backend-egress-policy
  namespace: production
spec:
  podSelector:
    matchLabels:
      app: backend-api
  policyTypes:
    - Egress
  egress:
    # 1. Whitelist akses ke database PostgreSQL
    - to:
        - podSelector:
            matchLabels:
              app: postgres-database
      ports:
        - protocol: TCP
          port: 5432
    # 2. Whitelist akses ke CoreDNS (kube-dns) di namespace kube-system
    - to:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: kube-system
          podSelector:
            matchLabels:
              k8s-app: kube-dns
      ports:
        - protocol: UDP
          port: 53
        - protocol: TCP
          port: 53

Keterbatasan NetworkPolicy Bawaan Kubernetes #

Meskipun NetworkPolicy bawaan Kubernetes sangat berguna, kita harus memahami beberapa keterbatasan teknisnya agar dapat merancang solusi mitigasi yang tepat:

Fitur Jaringan Dukungan NetworkPolicy Standar Solusi Alternatif Kluster
Filter Berbasis Nama DNS (e.g. whitelist *.stripe.com) Tidak Mendukung (hanya mendukung blok IP CIDR manual). Gunakan Ingress/Egress Gateway, Service Mesh (Istio), atau Cilium CiliumNetworkPolicy.
Penyaringan Layer 7 (HTTP Paths, Methods, Headers) Tidak Mendukung (hanya sampai level port TCP/UDP). Implementasikan Service Mesh (Envoy sidecar proxy) atau Cilium L7 Rules.
Pencatatan Log Paket (Auditing dropped packets) Tidak Mendukung (tidak ada log saat paket ditolak). Gunakan audit log Calico Enterprise atau Hubble CLI milik Cilium.
Prioritas Urutan Kebijakan (Policy Precedence) Tidak Mendukung (semua policy di-OR tanpa urutan). Gunakan objek GlobalNetworkPolicy milik Calico yang mendukung properti order.

Anti-Pattern vs Solusi Keamanan Jaringan #

Mari kita pelajari beberapa kesalahan fatal yang sering ditemui terkait konfigurasi firewall internal kluster beserta perbandingan kodenya.

Anti-Pattern 1: Mengosongkan Aturan policyTypes pada NetworkPolicy #

Kita membuat manifes NetworkPolicy dengan menuliskan aturan ingress di dalamnya, tetapi kita lupa menuliskan properti policyTypes: [Ingress] di spec atas.

Kode Manifest Salah (Tanpa Eksplisit policyTypes) #

# JANGAN LAKUKAN INI: Perilaku policy menjadi tidak dapat diprediksi
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: unsafe-policy
  namespace: production
spec:
  podSelector:
    matchLabels:
      app: backend
  # policyTypes tidak ditulis secara tertulis!
  ingress:
    - from:
        - podSelector:
            matchLabels:
              app: frontend

Konsekuensi Buruk #

Jika kita tidak menuliskan policyTypes secara eksplisit, Kubernetes CNI akan berasumsi bahwa policy ini hanya berlaku untuk hal-hal yang didefinisikan saja. Namun, perilaku ini sangat bergantung pada versi Kubernetes dan tipe CNI yang kita jalankan. Pada beberapa versi kluster, kelalaian ini dapat memicu CNI untuk tidak menerapkan pembatasan egress, sehingga Pod backend kita tetap bebas mengirimkan data keluar kluster tanpa pengawasan.

Kode Solusi (Selalu Eksplisit Mendefinisikan policyTypes) #

# SOLUSI: Tuliskan policyTypes secara deklaratif dan jelas
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: safe-policy
  namespace: production
spec:
  podSelector:
    matchLabels:
      app: backend
  policyTypes:
    - Ingress # Eksplisit menegaskan bahwa policy ini hanya mengunci masuk (ingress)
  ingress:
    - from:
        - podSelector:
            matchLabels:
              app: frontend

Anti-Pattern 2: Menggunakan ipBlock untuk Membatasi Komunikasi Antar Pod #

Kita ingin membatasi komunikasi antara Pod frontend dan Pod backend. Karena kita tahu alamat IP Pod frontend saat ini adalah 10.244.1.15, kita menggunakan properti ipBlock untuk membatasi akses ke IP tersebut.

Kode Manifest Salah (Membatasi Komunikasi Pod dengan IP Block) #

# JANGAN LAKUKAN INI: IP Pod bersifat dinamis
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: db-ipblock-failure
  namespace: production
spec:
  podSelector:
    matchLabels:
      app: database
  policyTypes:
    - Ingress
  ingress:
    - from:
        - ipBlock:
            cidr: 10.244.1.15/32 # JANGAN: Alamat IP Pod akan berubah saat restart!

Konsekuensi Buruk #

Begitu Pod frontend mengalami restart atau reschedule akibat rolling update, Pod frontend baru akan mendapatkan IP baru (misalnya 10.244.1.20). Karena IP tersebut tidak terdaftar di aturan ipBlock database, frontend baru tidak akan bisa terhubung ke database. Aturan ipBlock hanya boleh digunakan untuk membatasi lalu lintas dari/ke alamat IP fisik di luar kluster (misalnya database server on-premise atau range VPC cloud).

Kode Solusi (Menggunakan Label Selector Standar) #

# SOLUSI: Gunakan podSelector agar dinamis mengikuti perubahan IP
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: db-selector-success
  namespace: production
spec:
  podSelector:
    matchLabels:
      app: database
  policyTypes:
    - Ingress
  ingress:
    - from:
        - podSelector:
            matchLabels:
              app: frontend # Aman: CNI otomatis mendeteksi IP Pod baru yang memiliki label ini

Panduan Praktis Mendiagnosis Gangguan NetworkPolicy #

Jika setelah menerapkan NetworkPolicy aplikasi kita mendadak tidak dapat terhubung satu sama lain, berikut adalah langkah investigasi terarah:

1. Memeriksa Status Kebijakan Jaringan #

Dapatkan daftar seluruh NetworkPolicy aktif di namespace target:

kubectl get netpol -n production

Tinjau detail selector dan rules yang terpasang:

kubectl describe netpol postgres-isolation-policy -n production

2. Menguji Koneksi Secara Interaktif (Netshoot Debugging) #

Jalankan pod debug netshoot dan pasang label agar seolah-olah Pod debug tersebut adalah bagian dari Pod aplikasi kita:

# Luncurkan Pod debug dengan label frontend agar mewarisi izin NetworkPolicy frontend
kubectl run network-probe --rm -i --tty --image nicolaka/netshoot --labels="app=frontend" -n production -- /bin/bash

Dari dalam terminal network-probe, lakukan tes koneksi ke port database:

# Test handshaking port TCP database
nc -zvw3 postgres-database 5432

3. Menganalisis Gejala Drop Paket #

  • Connection Refused: Berarti paket data berhasil menembus firewall NetworkPolicy, tetapi tidak ada aplikasi yang mendengarkan (listening) di port target Pod tujuan (cek status aplikasi Pod target).
  • Connection Timeout: Menunjukkan indikasi kuat bahwa paket data kita dibuang (dropped) di tengah jalan oleh aturan NetworkPolicy CNI. Tinjau kembali arah rules ingress pada target atau egress pada pengirim.

Ringkasan #

  • Terapkan model Zero-Trust: Jangan biarkan kluster berjalan dengan model default-open. Terapkan kebijakan Default Deny All terlebih dahulu, baru kemudian buat whitelist rules secara bertahap.
  • Pastikan kompatibilitas CNI: Ingat bahwa CNI dasar seperti Flannel mengabaikan NetworkPolicy. Gunakan CNI canggih seperti Calico atau Cilium untuk penegakan firewall kluster.
  • Pahami logika AND vs OR: Perhatikan penulisan tanda hubung (-) pada manifest YAML. Jangan sampai salah menggunakan logika OR lintas namespace yang dapat membuka celah keamanan.
  • Selalu buka akses CoreDNS: Ketika menerapkan Default Deny Egress, pastikan untuk selalu menyertakan whitelisting akses keluar ke port 53 UDP/TCP di namespace kube-system.
  • Hindari ipBlock untuk komunikasi internal Pod: ipBlock hanya ditujukan untuk alamat IP di luar kluster. Untuk komunikasi antar Pod, selalu andalkan dinamisasi podSelector.
  • Investigasi dengan probe tools: Gunakan pod debugger netshoot yang dikonfigurasi dengan label khusus untuk menyimulasikan kelancaran alur paket data dan mendeteksi drop koneksi.

← Sebelumnya: Ingress   Berikutnya: Load Balancing & kube-proxy →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact