Zero-trust is een term die je tegenwoordig overal tegenkomt, maar in de praktijk blijft het vaak hangen bij goede intenties. Zeker in een Kubernetes-omgeving met microservices is het verrassend makkelijk om impliciet vertrouwen te laten ontstaan: services die elkaar blind vertrouwen, plaintextverkeer binnen het cluster en nauwelijks zicht op wie nou eigenlijk met wie praat.
In deze blog neem ik je mee in hoe je zero-trust concreet maakt binnen Kubernetes met Istio. Geen abstracte principes, maar een werkende setup waarin alle service-to-service communicatie standaard versleuteld is met mTLS en waarbij je expliciet bepaalt wie met wie mag praten.
Doelstellingen
Het doel is om al het netwerkverkeer tussen services via een TLS-verbinding te laten lopen waarbij de gebruikte certificaten kortdurend zijn en automatisch geüpdatet worden. Daarnaast mogen zowel de frontend-pod als backend-pod geen verbindingen zonder TLS meer accepteren.

Zo leg je de basis voor zero-trust in Kubernetes
Een zero-trust-setup begint niet met policies, maar met zicht en controle over je verkeer. In Kubernetes betekent dat in de praktijk dat je een service mesh nodig hebt.Voor dit blog heb ik Istio gekozen. Istio is een breed bekende service mesh met goede ondersteuning. Daarnaast hebben we nog een paar componenten nodig, HashiCorp Vault als PKI (Public Key Infrastructure) en een certificaatmanager. De kubectl, Helm en HashiCorp Vault moeten lokaal geïnstalleerd zijn om de scripts uit te kunnen voeren. Uiteraard heb je toegang nodig tot een Kubernetes-cluster.
Zo installeer je Istio, Vault en cert-manager
Als eerste gaan we Istio, Vault en cert-manager in het cluster installeren. Daarna gaan we deze op de juiste manier configureren zodat het geheel goed samenwerkt om de mTLS binnen het cluster te laten werken met kortdurende certificaten.
Istio
Istio kan geïnstalleerd worden met het volgende script:
$version = "1.29.1" # check latest version op istio.io
$url = "https://github.com/istio/istio/releases/download/$version/istio-$version-win.zip"
Invoke-WebRequest $url -OutFile "istio.zip"
Expand-Archive istio.zip -DestinationPath .
$istioPath = (Get-ChildItem -Directory | Where-Object { $_.Name -like "istio-*" }).FullName
$env:PATH += ";$istioPath\bin"
[Environment]::SetEnvironmentVariable("PATH", $env:PATH + ";$istioPath\bin", [EnvironmentVariableTarget]::User)
istioctl install --set profile=demo -y
Let op: we gebruiken hier het demoprofiel van Istio wat niet geschikt is voor productie. Dit profiel is wel geschikt om Istio te leren gebruiken. --set profile=default is wel geschikt als uitgangspunt voor productie.
HashiCorp Vault
Voor de installatie van Vault nemen we een paar stappen:
Creëren van een namespace:
kubectl create namespace vault
Creëren van een Vault-pod, voer het volgende script uit:
@"
apiVersion: v1
kind: Pod
metadata:
name: vault
namespace: vault
spec:
containers:
- name: vault
image: hashicorp/vault:1.15
args:
- "server"
- "-dev"
- "-dev-root-token-id=root"
ports:
- containerPort: 8200
"@ | kubectl apply -f -
Configureren van de Vault-PKI, voer onderstaande commando's uit (PowerShell):
Port forward naar de vault pod:
kubectl port-forward -n vault pod/vault 8200:8200
Zet de volgende environment variabelen:
$env:VAULT_ADDR="http://127.0.0.1:8200"
$env:VAULT_TOKEN="root"
Voer de volgende commando's uit om de PKI te configureren:
vault secrets enable pki
vault secrets tune -max-lease-ttl=8760h pki
Creëer een root certificate authority (CA) en genereer een self-signed root certificate:
vault write pki/root/generate/internal common_name="cluster.local" ttl=8760h
Creëer een rol die het uitgeven van certificaten voor het domein "svc.cluser.local" en de subdomeinen toestaat, met een maximale TTL van 72 uur:
vault write pki/roles/istio-role allowed_domains="svc.cluster.local" allow_subdomains=true max_ttl="72h"
Cert-manager
De installatie van cert-manager doen we direct vanuit de GitHub-repository. Voor productie is het uiteraard verstandig om een specifieke versie te gebruiken.
Installeer de cert-manager:
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/latest/download/cert-manager.yaml
Controleer de installatie:
kubectl rollout status deployment/cert-manager -n cert-manager
Nu gaan we de cert-manager aan Vault koppelen:
kubectl create secret generic vault-token --from-literal=token=root -n cert-manager
Let op: we hebben nu alles opgezet met ‘root’ als token, dit is puur voor demo doeleinden. Voor productie is een degelijk token nodig. We gebruiken nu een ClusterIssuer om te zorgen dat cert-manager de certificaten kan uitgeven door het volgende script uit te voeren:
@"
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: vault-issuer
spec:
vault:
server: http://vault.vault.svc.cluster.local:8200
path: pki/sign/istio-role
auth:
tokenSecretRef:
name: vault-token
key: token
"@ | kubectl apply -f -
Na het uitvoeren van deze stappen hebben we de componenten klaarstaan om een namespace aan te maken waarbinnen TLS-verbindingen automatisch geregeld en afgedwongen worden.
Zo dwing je mTLS af binnen de namespace
Na het installeren van alle componenten kunnen we nu een namespace opzetten waarbinnen de mTLS geregeld en afgedwongen wordt. Door de namespace het label ’istio-injection=enabled’ te geven, wordt deze door de Istio-operator in de gaten gehouden. Wanneer er een nieuwe pod gecreëerd wordt, wordt deze direct voorzien van een initcontainer en een sidecar.
De init-container verzorgt het updaten van de iptables van de pod, waardoor inkomend- en uitgaand verkeer via de Istio-sidecarproxy gerouteerd wordt, waarna de Istio-sidecarproxy de certificaatafhandeling doet.
Voer de volgende commando’s uit om een namespace te maken en het label te plaatsen, waardoor Istio de namespace zal monitoren:
kubectl create namespace demo
kubectl label namespace demo istio-injection=enabled
Nadat we dit gedaan hebben, zal Istio de init-container en de sidecar bij het creëren van een pod injecteren, maar deze pods zullen nog steeds connecties accepteren zonder TLS, aangezien we Istio nog niet verteld hebben hoe strict wij willen zijn. De default setting voor Istio is “Permissive”, wat inhoudt dat zowel plaintext als TLS-verkeer toegestaan is.
Voer het volgende script uit om uitsluitend TLS-verkeer toe te laten, de Istio-strictmode:
@"
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: strict
namespace: demo
spec:
mtls:
mode: STRICT
"@ | kubectl apply -f -
De PeerAuthentication bepaalt wat voor binnenkomend verkeer er geaccepteerd wordt. Aangezien we van een frontend-pod naar een backend-pod binnen dezelfde namespace willen communiceren, moet het uitgaande verkeer dus ook aangepast worden.
Voer het volgende script uit om het uitgaande verkeer ook van mTLS-versleuteling te voorzien:
@"
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: default
namespace: demo
spec:
host: "*.demo.svc.cluster.local"
trafficPolicy:
tls:
mode: ISTIO_MUTUAL
"@ | kubectl apply -f -
Al het verkeer naar hosts binnen de ‘demo’-namespace zal gebruikmaken van mTLS. Vanaf dit moment wordt alle communicatie tussen de pods versleuteld.
Veelvoorkomende problemen en hoe je ze oplost
Er zijn veel momenten waarop je tegen uitdagingen kunt aanlopen, en de meeste hebben te maken met het tijdstip waarop acties in gang worden gezet:
- Pods die al draaien, krijgen geen sidecars geïnjecteerd.
- Voor deployments kan de rollout herstart worden: kubectl rollout restart deployment
-n
- Voor deployments kan de rollout herstart worden: kubectl rollout restart deployment
- De certificaten worden automatisch hernieuwd, maar wanneer je cluster niet actief is op het moment dat de deadline verloopt en het window gemist wordt, moet het geheel handmatig weer op gang gebracht worden.
- Istio moet herstart worden: kubectl rollout restart deployment istiod -n istio-system
- Alle pods moeten opnieuw uitgerold worden; zie hierboven.
Ingress-setup
We hebben nu het systeem zover dat alle communicatie binnen de namespace versleuteld is en dat niet-versleuteld verkeer geweerd wordt. Dit houdt in ons geval in dat als we een ingress configuratie opstellen voor het benaderen van de frontend het verkeer van de Ingress controller naar onze pod zonder aanpassingen niet mogelijk is.
Om toch te zorgen dat de frontend app toch benaderd kan worden vanuit de Ingress, moeten we nog enkele configuraties toevoegen:
Een ‘AuthorizationPolicy’
Dit is een Istio-policy die bepaalt wie een specifieke service mag benaderen. Voor deze policy zullen we het serviceaccount van de Istio-ingressgateway toestemming moeten geven om de service van onze frontend-pod te benaderen.
@"
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: allow-ingress
namespace: demo
spec:
selector:
matchLabels:
app: frontendapp
rules:
- from:
- source:
principals:
- "cluster.local/ns/istio-system/sa/istio-ingressgateway-service-account"
"@ | kubectl apply -f -
Let op: Na het aanpassen van de AuthorizationPolicy moet de Istio-gatewaydeployment herstart worden voordat de autorisaties toegepast worden.
kubectl rollout restart deployment istio-ingressgateway -n istio-system
De Istio-ingress houdt rekening met de DestinationPolicy, waardoor deze nu ook het uitgaande verkeer voorziet van mTLS-versleuteling.
De Istio-‘Gateway’ en ‘VirtualService’
Dit vertelt de Istio-ingress om een poort te openen (Gateway) en vervolgens (VirtualService) verkeer door te sturen naar onze frontend-appservice:
@"
apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
name: frontendapp-gateway
namespace: demo
spec:
selector:
istio: ingressgateway # use istio default controller
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- "*"
---
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: frontendapp
namespace: demo
spec:
hosts:
- "*"
gateways:
- frontendapp-gateway
http:
- match:
- uri:
prefix: /
route:
- destination:
host: frontendapp.demo.svc.cluster.local
port:
number: 8000
"@ | kubectl apply -f -
Conclusie
Door gebruik te maken van Istio kunnen we het verkeer tussen pods eenvoudig voorzien van mTLS met een kortlevend (72h) certificaat. Er moet expliciet aangegeven worden dat binnenkomend verkeer versleuteld moet zijn (strict) door middel van mTLS en er moet expliciet aangegeven worden dat het uitgaande verkeer versleuteld moet worden. Daarnaast moet er rekening gehouden worden met verbindingen van buiten de namespace, zoals een ingressverbinding. Als dit het geval is, moet er voor het serviceaccount van de verzendende pod een AutorisatiePolicy geconfigureerd zijn.
Tot slot
Het is naast mTLS ook een goed idee om alle pods te voorzien van een network policy oftewel een Kubernetes-firewall. Hierdoor kan het inkomende en uitgaande verkeer tussen pods of namespaces toegestaan of beperkt worden. Wanneer je verschillende namespaces hebt met afzonderlijke applicaties, is het vaak aan te raden het verkeer tussen de namespaces via de externe gateway te laten verlopen om ingewikkelde configuraties tussen namespaces te voorkomen.
Wil je dit toepassen in een productie omgeving of sparren over de juiste architectuur? Neem contact met ons op en we helpen je graag op weg.

Heb je vragen over dit onderwerp of zou je Addy willen inhuren voor een vergelijkbare opdracht?