Kom igång med Network Policy i Kubernetes
Enligt rådande branschrekommendationer för Kubernetes bör klusteradministratörer använda Network Policy-definitioner för att reglera trafik till och från klustrets pod:ar, eftersom en standardkonfiguration annars är att jämföra med en brandvägg som konfigurerats med så kallade ”any-any-regler” (Regler som tillåter all trafik från alla källor mot alla destinationer).
Vår erfarenhet av att granska klusterkonfigurationer säger att det är långt ifrån alla administratörer som känner till denna branschrekommendation, eller känner sig helt bekväma med att använda Network Policy trots att det är nuvarande best practice.
Den här bloggartikeln kommer att demonstrera hur man kan skapa en enkel testbädd för att utvärdera Network Policy och hur man kan inhämta loggar från överträdelser av Network Policy.
Testbädden kommer att utgå ifrån en standardinstallation av K3s, som är en lättviktsimplementation av Kubernetes men CNCF-certifierad och garanterad kapabel att göra det som ett fullt Kuberneteskluster kan. Testbädden kommer att använda Elastic-stack för att ta in loggar från Network Policy-överträdelserna och göra dem sökbara.
Skillnader i implementationer av Network Policy-stöd
Det är viktigt som Kubernetes-administratör att förstå att alla implementationer av Network Policy inte är likvärdiga. Kubernetes i sig har inget stöd för att påtvinga Network Policy-definitioner, utan överlåter detta till det CNI (Container Network Interface) klustret använder.
Kubernetes kommer som bekant inte med en förkonfigurerad CNI utan överlåter detta till administratören att ombesörja. Detta är främst en fråga för on-prem-kluster då molnleverantörerna i regel förväntar sig att klustret använder en CNI specifik för molnmiljön ifråga, men för de av oss som föredrar en mer lokal smak av molnteknologin finns flera populära CNI att välja på. De största och populäraste i skrivande stund är Flannel, Calico, Weave och Canal.
Det går även att implementera en service mesh i stället, vilket kan ge kraftfullare alternativ och komplement till Network Policy men detta är bortom omfattningen av denna bloggartikel.
Eftersom testbädden denna artikel utgår ifrån är baserad på K3s väcker det frågan: vilken CNI använder vi då?
Flannel stödjer inte Network Policy? Inga problem.
En standardinstallation av K3s inkluderar Flannel som CNI, vilket kan upplevas som ett problem då Flannel inte bryr sig om Network Policy.
K3s har lyckligtvis valt att inkludera en komponent som heter kube-router, som har stöd för Network Policy! Kube-router skapar regler i iptables, en Linux-baserad värdbrandvägg, och detta är nyckeln både till att implementera Network Policy generera de loggar vi vill samla in.
Testbädden – virtuell maskin
Denna artikel utgår ifrån en testbädd som körs i en virtuell Debian-server, men den går likabra att köra på en fysisk enhet om det önskas. Det finns ett hinder för att köra K3s-klustret i containrar med hjälp av exempelvis k3d, vilket utvecklas kort i slutet av denna artikel.
Första steget till att skapa testbädden är att skapa den virtuella maskinen. Denna artikel utgår ifrån en lokal virtuell maskin med libvirt som typ 2-hypervisor.
För att kunna snabbt skapa och riva testbädden utgår vi ifrån Debian cloud images, som är förkonfigurerade att använda cloud init för att provisionera systemet. De filer som innehåller ”generic” är lämpliga för ändamålet, undvik de som heter ”genericcloud” då de är nedbantade och avsedda att köras hos molnleverantörerna.
# Ladda ned senaste Debian-molnavbildningen
wget https://cdimage.debian.org/images/cloud/bookworm/latest/debian-12-generic-amd64.qcow2
# Skapa en katalog för basavbildningar
mkdir /var/lib/libvirt/images/base
mv debian-12-generic-amd64.qcow2 /var/lib/libvirt/images/base
cd /var/lib/libvirt/images/base/
mkdir cloud-init
touch cloud-init/meta-data
Skapa därefter filen user-data i cloud-init-mappen och definerad önskad provisionering. Ersätt <EXEMPELHASH> med en egen hash, till exempel genom att använda mkpasswd. Ersätt <EXEMPELNYCKEL> med en publik ssh-nyckel för att smidigt kunna fjärradministrera systemet.
#cloud-config
users:
- name: testbed-user
passwd: >EXEMPELHASH>
lock_passwd: false
ssh-authorized-keys:
- <EXEMPELNYCKEL>
groups: sudo
sudo: ['ALL=(ALL) NOPASSWD:ALL']
package_update: true
package_upgrade: true
packages:
- sudo
- wget
- curl
- ulogd2
Vi kan nu kopiera basavbildningen och sedan skapa den virtuella maskinen med virt-install:
qemu-img create -f qcow2 -b base/debian-12-generic-amd64.qcow2 -F qcow2 debian-12.qcow2 15G
virt-install --name testbed --memory 4096 --vcpus 4 --os-variant debian12 --connect qemu:///system --virt-type kvm --nographics --disk size=50,backing_store=/var/lib/libvirt/images/debian-12.qcow2 --cloud-init user-data=/var/lib/libvirt/images/base/cloud-init-bootstrap/user-data,meta-data=/var/lib/libvirt/images/base/cloud-init-bootstrap/meta-data
När provisioneringen är klar kan du lämna konsolen genom CTRL + ALTGR + 9 (på ett svenskt tangentbord) om du föredrar att ansluta via SSH.
Om du inte vill eller kan använda libvirt och föredrar hypervisors som exempelvis VMware workstation, Virtualbox eller Hyper-V rekommenderas Vagrant för deklarativ provisionering.
Testbädden – Elastic-stack
Med en virtuell maskin på plats är det dags att skapa själva testbädden. Vi behöver installera Docker Engine både för att köra Elastic-stack och för K3s. Följ instruktionerna på Dockers hemsida och verifiera att installationen fungerar.
Elastic har ett mycket användbart skript för att snabbt skapa en lokal Elastic-stack i form av containrar, som heter start-local. Vi följer deras instruktion med en enkel men viktig modifikation.
# Kör aldrig skript genom att pipe:a dem till ett skal.
# Ladda ned och inspektera skriptet!
curl -fsSL https://elastic.co/start-local > start-local
# Efter inspektion kan vi göra skriptet exekverbart och köra det.
chmod +x start-local
sudo ./start-local
Skriptet kommer att skapa en katalog med några filer:
.
├── elastic-start-local
│ ├── docker-compose.yml
│ ├── start.sh
│ ├── stop.sh
│ └── uninstall.sh
├── error-start-local.log
└── start-local
Skriptet startar automatiskt kompositionen så klustret kommer snabbt upp och du bör se inloggningsuppgifterna i terminalen. Om du skulle glömma bort dem finns de under elastic-start-local-mappen i .env-filen.
Elastic-stack lyssnar nu på port 5601 och 9200 på den virtuella maskinens loopback-gränssnitt. För att kunna surfa till webbgränssnittet på 5601 behöver du antingen konfigurera om docker-compose-filen, installera en reverse proxy eller göra en tillfällig port forward. I denna artikel använder vi SSH med den behändiga port forward-växeln.
ssh -L 5601:127.0.0.1:5601 user@<TESTMASKINENS-IPADRESS)
Vi kan nu surfa till localhost på port 5601 på värddatorn och logga in med uppgifterna start-local genererade.
Port 9200 kommer vi inte att behöva nå utanför testbäddens virtuella maskin.
Testbädden – K3s
Vi installerar K3s enligt instruktionerna på deras hemsida, med samma modifiering som vi gjorde för installationen av Elastic-stack.
# Ladda ned och inspektera skriptet!
curl -sfL https://get.k3s.io > k3s.sh
# Klarar skriptet en ockulär besiktning kan vi flagga det exekverbar
# och köra det.
chmod +x k3s.sh
./k3s.sh
Skriptet skapar ett enkelnodskluster som är redo att ta emot våra kommandon och manifest. Verifiera att klustret är uppe genom att köra kubectl, som också har installerats av skriptet:
sudo kubectl get nodes
Testbädden – ulogd2
Det sista vi behöver innan vi kan börja testa Network Policy är en demon som är kapabel att lyssna på de NFLOG-event som genereras när trafik blockeras vid överträdelse av policyerna. Ulogd2 finns i Debians paketregister. Installera paketet om du inte har använd cloud-init-exemplet i denna bloggartikel.
sudo apt install ulogd2 -y
Öppna /etc/ulogd.conf med en texteditor och gör följande ändring:
[log1]
group=100
Starta därefter om ulogd2-tjänsten:
sudo systemctl restart ulogd2.service
Ulogd2 kommer att skriva sina loggar till filen /var/log/ulog/syslogemu.log (detta går att ändras i /etc/ulogd.conf om så önskas). Vi kan nu låta Filebeat övervaka filen och skicka allt som skrivs till filen till Elastic-stack.
Testbädden – Filebeat
Installera Filebeat enligt instruktion på Elastics hemsida, och justera agentens konfiguration under /etc/filebeat/filebeat.yml. De två segment som behöver ändras är filebeats.input:
filebeat.inputs:
- type: filestream
id: ulogd2-filestream-id
enabled: true
paths:
- /var/log/ulog/syslogemu.log
Och output.elasticsearch. Notera att du behöver ersätta <DITT_LÖSENORD> med lösenordet din start-local-körning skapade för elastic-användaren.
output.elasticsearch:
hosts: ["localhost:9200"]
preset: balanced
username: "elastic"
password: "<DITT_LÖSENORD"
Notera även att det inte är rekommenderat att använda användarnamn och lösenord i allmänhet, och elastic-användaren i synnerhet, för att skicka loggar till Elastic-stack. Rådande branschrekommendation är att använda API-nycklar i stället, men i syfte att hålla denna bloggartikel så kort som möjligt tar vi en genväg i testbädden.
Det sista vi behöver göra är att köra filebeats setup-kommando och aktivera filebeat.service:
sudo filebeat setup -e
sudo systemctl enable filebeat.service --now
Test av Network Policy
Testbädden är äntligen redo för våra tester. För att slippa hoppa in och ur skal i klustret rekommenderas att öppna flera terminaler in till testbädden, eller att använda en terminal multiplexer som exempelvis tmux eller Zellij.
Vi börjar med att skapa ett namespace för våra tester, till exempel:
apiVersion: v1
kind: Namespace
metadata:
name: bloggdemo
Spara exemplet till en fil kalladnamespace.yaml och applicera manifestet med kubectl:
sudo kubectl apply -f namespace.yaml
Vi börjar med att verifiera att ingenting hindrar oss från att göra godtyckliga anslutningar från en pod i klustret, till exempel med en container innehållandes verktyget curl.
sudo kubectl run curlpod --image=curlimages/curl --namespace bloggdemo -i --tty -- sh
curl https://google.com
Ingenting bör hindra anslutningen, men om så är fallet behöver detta felsökas.
Nu när anslutningen har verifierats kan vi skapa vår första Network Policy. Skapa en fil kallad default.yaml med följande innehåll:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: bloggdemo
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
Detta är en Network Policy som effektivt agerar som en default-deny-regel. Den träffar alla pod:ar som körs i vårt test-namespace och eftersom den inte anger någon trafik som tillåten blockeras allt som rör sig till eller från dessa.
Applicera manifestet:
sudo kubectl apply -f default.yaml
Verifiera att anslutningen nu blockeras. Du kommer att märka att du inte längre kommer att kunna slå upp google.com, och detta beror på att curlpod inte längre tillåts att ställa DNS-frågor till klustret!
Fortsätt att försöka ansluta med curl, gör fler än tio anslutningsförsök. Logga nu in i webbgränssnittet på Elastic-stack och titta under indexet filebeat-*. Du bör se loggar från dina misslyckade anslutningar! Så här ser ett exempel på en misslyckad anslutning till port 443 på 142.250.74.46 ut (www.google.com).
Kube-router antar om inte annat sägs att tröskelvärden för loggning är 10 överträdelser och begränsar loggning till 10 rader per minut. Syftet med tröskelvärdena är att filtrera bort oväsen och att inte dränka loggmottagaren med data. Tröskelvärdena kan skrivas över med hjälp av annotationer i Network Policy, detta är ett exempel som sätter tröskelvärdet till 25 och och max loggader per minut till 30.
- kube-router.io/netpol-nflog-limit=25
- kube-router.io/netpol-nflog-limit-burst=30
Network Policy skall enligt Kubernetes specifikation endast hantera TCP, UDP och STCP-baserade anslutningar. Övriga protokoll är odefinerade. Detta innebär att implementatören kan göra som den vill med övriga protokoll, och det är upp till administratören att kontrollera att en policy har en effekt eller ej. När det gäller K3s och kube-router är Network Policy mer effektiv än standard, och det går att begränsa ICMP också. Detta är ett exempel på en överträdelse som loggats efter att kommandot ping körts från en test-pod:
Du har nu alla verktyg som krävs för att komma igång med och testa effektiviteten i Network Policy, och förhoppningsvis har denna artikel väckt nyfikenhet och en del tankar om hur du kan övervaka och larmsätta ovälkomna aktiviteter i ditt Kuberneteskluster.
PS: Automatisera testbädden
Det är möjligt att eliminera en del handpåläggning vid uppsättningen av testbädden, genom att exempelvis utöka cloud-init-filen med runcmd och write_files. Detta lämnas som en övning för läsaren.
PPS: Misslyckat försök med k3d
Det första försöket att skapa testbädden utgick ifrån en ren container-baserad lösning där Elastic-stack och K3s skulle köras som containrar på värddatorn i stället för i en virtuell maskin. Det visade sig dock att eftersom kube-router konfigurerar iptables-regler i containrarna i stället för på värddatorn, och följdaktligen registreras inte heller några överträdelser som NFLOG på värddatorn heller. Det är möjligt att det går att anpassa K3d så att Filebeat körs inne i klustret, eventuellt som en sidecar eller liknande men detta har inte uträtts vidare och lämnas som en övning till läsaren.