Vergemakkelijk Kubernetes Cluster-updates met automation
Door Thomas Kooi / mrt 2022 / 1 Min
Door Thomas Kooi / / 12 min
Avisi Cloud draait Kubernetes-clusters sinds het begin van 2018. Sindsdien is er veel gebeurd. In deze blog kijken we naar een aantal praktische voorbeelden voor het hardenen van Kubernetes-clusters. We behandelen de praktische voorbeelden door gebruik te maken van de OWASP Security by design-principes.
Aan het einde van de blog eindigen we met tooling dat jou kan helpen om de benodigde security policies goed te laten verlopen.
Waarom is het belangrijk om de security posture van jouw Kubernetes-clusters te verbeteren? Ten eerste is Kubernetes erg complex. Het is makkelijk om fouten te maken zonder dat je dit door hebt. Dit willen we voorkomen, omdat dit een risico kan zijn op de beschikbaar- en vertrouwelijkheid van jouw applicaties. Indien jouw applicaties niet beschikbaar en betrouwbaar zijn, kan klantendata verloren gaan. Dit kan weer voor negatieve gevolgen zorgen, zoals imagoschade. Daarom is het goed om te kijken naar de inrichting van jouw Kubernetes-omgeving aan de hand van Security Principes. Deze Security Principes geven jou een handvat om te weten welke acties jij moet ondernemen om de Security Posture te verbeteren.
Voordat je een systeem kan beveiligen, is het belangrijk om bekend te zijn met de potentiële risico's en bedreigingen van dit systeem. Voor Kubernetes helpt Microsoft ons hiermee met behulp van hun Kubernetes threat matrix, zie hieronder.
Om je cluster te beveiligen, is het nodig om mitigerende maatregelen te hebben voor elk van deze tactieken. De blogpost van Microsoft over de threat matrix is hiervoor een goed beginpunt.
Het gebruikmaken van security principes kan helpen bij het bepalen welke maatregelen er geïmplementeerd dienen te worden om je cluster veilig te houden. Om hierbij te helpen kan je gebruikmaken van de OWASP Security by design-principes:
In deze blog gaan we door elk van deze principes en kijken we welke maatregelen wij hieruit kunnen halen die je kan toepassen op je Kubernetes-cluster. Let op dat je security vanuit meerdere perspectieven kan benaderen en het is altijd belangrijk om hier vanuit meerdere hoeken naar te kijken.
Een goede manier om risico's te minimaliseren is door de aanval oppervlakte te verkleinen. Binnen Kubernetes zijn er verschillende manier om dit te doen.
Gebruik minimale container images
Draai zo min mogelijk container images, het liefst zonder een shell. Een goede suggestie hiervoor zijn distroless container images. Er zijn ook minimale versies van container base images (Linux Distributies) beschikbaar óf je kan gebruikmaken van een alpine base image.
Een linux distributie is een verzameling van software packages rond een Linux kernel. Dit is traditioneel voor virtual machines. Voor containers heb je deze smaken ook (minus de kernel die hierbij niet nodig is). Standaard zit er in een linux distributie veel meer software als wat je voor normaal gebruik nodig hebt. Een minimaal distributie heeft hier een groot deel van verwijderd, waardoor de grote van het image verkleint is. Dit heeft als gevolg dat het aanvalsoppervlak voor kwaadwillende ook kleiner is.
Terwijl ongebruikte bestanden geen groot probleem zijn, zorgt het niet hebben van deze container images voor een paar voordelen:
Binnen AME Kubernetes is de toegang naar cluster nodes beschermd door middel van een firewall. Op AWS vindt deze bescherming plaats door middel van security regels.
Een goede netwerk policy om te downloaden in elke namespace is een standaard deny all policy:
kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
name: default-deny-all
namespace: examplenamespace
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
ingress: []
egress: []
Andere voorbeelden van goede netwerk policies zijn transparant in wat een container kan doen, in plaats van alle soorten verkeer toe te laten. Dit is een policy die DNS traffic toestaat naar de Kube-system namespace:
kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
name: allow-util-traffic
namespace: examplenamespace
spec:
podSelector:
matchLabels: {}
policyTypes:
- Egress
egress:
- ports:
# Allow DNS
- port: 53
protocol: UDP
- port: 53
protocol: TCP
to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
Als je applicatie toegang tot het publieke internet nodig heeft, vermijdt dan egress verkeer met 0.0.0.0/0 en voeg except toe voor interne subnets (RFC1918).
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-egress-to-external
namespace: examplenamespace
spec:
podSelector:
matchLabels:
app.kubernetes.io/name: example
policyTypes:
- Egress
egress:
- to:
- ipBlock:
cidr: 0.0.0.0/0
except:
# deny internal RFC1918 subnets
- 10.0.0.0/8
- 172.16.0.0/12
- 192.168.0.0/16
Sinds alles in Kubernetes draait op container images, kan het opstarten van een gecomprimeerde container image binnen je cluster ervoor zorgen dat je kwaadaardige software zoals crypto-miners binnen je cluster opstart. Andere scenario's kunnen zijn dat een aanvaller een remote shell weet te bemachtigen binnen je container of scripts daarop kan downloaden en uitvoeren.
Om te waarborgen dat je geen gecomprimeerde images opstart binnen je cluster, kan je het volgende doen:
Als je jouw self hosted registry op Harbor draait, dan zijn er verschillende opties beschikbaar. Voor hosted solutions, check je provider's documentatie.
De "Establish secure defaults"-principe zegt dat de standaard optie veilig moet zijn. Om extra privileges te kunnen verkrijgen, is het belangrijk dat er meerdere stappen uitgevoerd worden. Dit is zodat een verhoogd privilege niveau niet een configuratiefout kan zijn. Dit kan je waarborgen door gebruik te maken van veilige default instellingen voor je cluster.
Pod Security Policy of Pod-Security Standards gebruiken
Kubernetes heeft een eigen oplossing om de scope van bevoegdheden die een container heeft, wanneer deze in het cluster wordt uitgevoerd, te beperken. Het nu verouderde Pod Security Policy was de manier om dit te doen. Sinds Kubernetes v1.21 is dit deprecated en dient er gebruikgemaakt te worden van de nieuwe Pod Security Standards (PSS).
Voor nieuwe clusters is aangeraden om PSS in te zetten. PSS bevat drie policies: Privileged, Baseline en Restricted:
Profiel | Beschrijving |
---|---|
Bevoorrecht | Onbeperkt beleid met het breedst mogelijke niveau van machtigingen. Dit beleid staat bekende escalaties van bevoegdheden toe. |
Basislijn | Minimaal restrictief beleid dat bekende escalaties van bevoegdheden voorkomt. Staat de standaard (minimaal gespecificeerde) Pod-configuratie toe. |
Beperkt | Zwaar beperkt beleid, volgens de huidige best practices voor het verharden van pods. |
Indien Pod Security Standards zijn ingeschakeld in je cluster (via een featureGate
), kun je dit per namespace configureren.
apiVersion: v1
kind: Namespace
metadata:
name: my-restricted-namespace
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/enforce-version: latest
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/warn-version: latest
Het cloud native-ecosysteem heeft een aantal goede projecten beschikbaar om ervoor te zorgen dat applicaties/containers met de juiste settings draaien. Ze kunnen helpen met het afdwingen van security defaults door automatisch PodSecurityContext te configureren voor nieuwe pods of door het installeren van NetworkPolicies binnen een cluster.
Kyverno is een policy management engine speciaal gebouwd voor het Kubernetes-ecosysteem. Het is heel eenvoudig om met Kyverno aan de slag te gaan.
Via Kyverno is het mogelijk om elk gewenst beleid binnen een Kubernetes-cluster af te dwingen. Zo kan je ervoor zorgen dat alle containers van de juiste registry komen, naamgeving correct is en er netwerk policies aanwezig zijn. Er zijn ook een goede set aan default policies beschikbaar (voorbeeld).
Een goed voorbeeld van secure defaults binnen een Kubernetes-cluster is het installeren van een default network policy dat al het netwerkverkeer weigert. Dit kan met Kyverno via de volgende policy:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: add-networkpolicy
annotations:
policies.kyverno.io/title: Add Network Policy
policies.kyverno.io/category: Multi-Tenancy
policies.kyverno.io/subject: NetworkPolicy
policies.kyverno.io/description: >-
By default, Kubernetes allows communications across all Pods within a cluster.
The NetworkPolicy resource and a CNI plug-in that supports NetworkPolicy must be used to restrict
communications. A default NetworkPolicy should be configured for each Namespace to
default deny all ingress and egress traffic to the Pods in the Namespace. Application
teams can then configure additional NetworkPolicy resources to allow desired traffic
to application Pods from select sources. This policy will create a new NetworkPolicy resource
named `default-deny` which will deny all traffic anytime a new Namespace is created.
spec:
rules:
- name: default-deny
match:
resources:
kinds:
- Namespace
generate:
kind: NetworkPolicy
name: default-deny
namespace: ""
synchronize: true
data:
spec:
# select all pods in the namespace
podSelector: {}
# deny all traffic
policyTypes:
- Ingress
- Egress
De meeste standaard workloads hoeven niet onder de rootgebruiker te worden uitgevoerd. Ook in een container is het geen goed idee om root te gebruiken, omdat dit een grote mate van geprivilegieerde acties in Linux mogelijk maakt. Mogelijke risico's zijn onder meer het verkrijgen van toegang tot een Kubernetes-node (container-escape).
Zorg bij het maken van een nieuwe containerimage dat deze niet als rootgebruiker wordt uitgevoerd. Je kunt dit doen met de USER
statement in je Dockerfile. Sysdig heeft een goede blogpost over de best practices voor een Dockerfile geschreven.
Een veelvoorkomende reden waarom images nog steeds root gebruiken, is om het gebruik van een poort onder 1024 toe te staan. Je moet in plaats daarvan ofwel een hogere poort voor je applicatie gebruiken,of linux-capabilities gebruiken om het gebruik van deze poort toe te staan (CAP_NET_ADMIN).
Let op: indien je de capability gebruikt moet je ervoor zorgen dat je deze ook toevoegt aan de container security context capabilities.
Role based access control (RBAC) is standaard ingeschakeld binnen Kubernetes. Zorg ervoor dat je bij het maken van een nieuw serviceaccount alleen de benodigde permissies geeft.
Je moet ook voorkomen dat het standaard serviceaccounttoken in een pod wordt gemount (automountServiceAccountToken: false). Indien je toch een service account token nodig hebt, maak dan een speciaal serviceaccount en geef hier zo min mogelijk permissies aan.
De inloggegevens van cloudproviders zijn vaak zeer gevoelige credentials, omdat ze toegang bieden tot infrastructuur zoals EC2-instanties, control plane-VM's of back-ups. Binnen een Kubernetes-context zijn er in ieder geval twee plaatsen die inloggegevens voor cloudproviders kunnen hebben:
Door het principe van least privilege te gebruiken, zorg je ervoor dat voor alle inloggegevens het minimaal benodigde aantal permissies is ingesteld. Mocht er iets misgaan, dan zal de impact beperkt zijn.
Ten tweede, zorg ervoor dat elke namespace goed geconfigureerde network policies heeft. Dit verhindert toegang tot de instantie metadata-API (bijvoorbeeld 169.254.169.254 op AWS ). Zet bijvoorbeeld al het uitgaande netwerkverkeer standaard dicht.
Zorg ervoor dat alle workloads die Kubernetes secrets met cloud-provider credentials gebruiken worden uitgevoerd op nodes die losstaan van normale applicatieworkloads. Als een aanvaller erin slaagt om uit een container te komen en toegang te krijgen tot het hostsysteem, kan die alle Kubernetes secrets op die node achterhalen of opvragen. Indien je workloads met de cloud-provider credentials op andere hosts draaien, is de impact van een node comprise veel minder.
Zorg ervoor dat de NodeRestriction admission controller is ingeschakeld. Dit zou bij de meeste providers het geval moeten zijn.
Het kubeconfig file is een manier om met de Kubernetes API-server te communiceren. Het bevat de credentials en het endpoint van de Kubernetes API. Als een aanvaller toegang krijgt tot een kubeconfig-file, dan kan die acties uitvoeren terwijl die zich voordoet als de gebruiker of het systeem waartoe de kubeconfig behoort.
Credentials die in een kubeconfig-file worden gebruikt moeten gemakkelijk kunnen worden ingetrokken. Hiervoor dien je gebruik te maken van een authenticatiemechanisme zoals bijvoorbeeld OpenID Connect voor je cluster. De meeste enterprise ready Kubernetes service providers hebben hiervoor een oplossing.
Gebruik geen authenticatie voor je API-server via client certificates. Indien dit de enigste optie is, deel dit dan niet met andere gebruikers. Momenteel is er geen mechanisme om de client certificaten in te trekken binnen Kubernetes (kubernetes/kubernetes#60917).
Als OIDC niet beschikbaar is, overweeg dan gebruik te maken van statische bearer tokens of gebruik certificaten met een zeer korte levensduur (<24h).
"Defense in depth" vertelt ons om ervoor te zorgen dat als één maatregel faalt, er op een lager niveau een andere maatregel moet zijn dat misbruik voorkomt.
Hoewel het toepassen van het principe of least privilage bij RBAC een goede maatregel is, is het verstandig om de toegang tot de API-server alsnog te beperken.
Bij het principe van least privilage hebben we het gehad over het niet draaien van containers/pods als root user. Dit beperkt al veel toegang tot de linux kernel. Aanvullend is het belangrijk om de Linux capabilities te beperken en een pod security context in te stellen. Hiermee zorg je ervoor dat de container met een specifieke Linux user ID draait.
Configureer de podSecurityContext:
securityContext:
runAsNonRoot: true
runAsGroup: 65532
runAsUser: 65532
fsGroup: 65532
Configureer de container security context door alle Linux capabilities uit te zetten en een readOnly root filesystem aan te zetten:
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: [ 'ALL' ]
Hoewel de meeste Linux-functionaliteiten niet beschikbaar zijn wanneer de juiste pod- en container security context configureert zijn, zegt het "defense in depth"-principe om dit op meerdere lagen dicht te zetten. Met seccomp kunt je de Linux-functionaliteiten waartoe je container toegang heeft beperken. Het beperkt de beschikbare system calls vanuit de container naar de kernel.
Seccomp kan eenvoudig worden geconfigureerd voor bijna alle toepassingen. Dit kan op twee manieren. Voor de versies voorafgaand aan Kubernetes v1.19 kan je de volgende annotatie op een pod gebruiken:
seccomp.security.alpha.kubernetes.io/pod: runtime/default
Sinds Kubernetes v1.19 kan je dit configureren via de pod Security context:
securityContext:
seccompProfile:
type: RuntimeDefault
Admission Controllers/Policy management engines zoals Kyverno gebruiken admission webhooks om resources te valideren tijdens admission tot de Kubernetes API-server. Zorg ervoor dat wanneer je een voor dit doel geïnstalleerde webhook gebruikt, de validatie-webhook zo geconfigureert is dat een admission wordt geweigerd als de service die het moet bereiken niet beschikbaar is. Dit geldt voor de standaardinstallatie van de webhook voor Kyverno of OPA Gatekeeper. Dit zorgt ervoor dat er geen resources worden geïnstalleerd die niet voldoen aan het beleid, mocht de admission controller niet beschikbaar zijn.
Het weigeren van admissions die niet voldoen kan gedaan worden door de failurePolicy voor een webhook te configureren:
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
...
webhooks:
- name: my-webhook.example.com
failurePolicy: Fail
...
Let op: zorg ervoor dat de admission controller wordt uitgerold voor hoge beschikbaarheid (bijvoorbeeld: >2 replicas) om te voorkomen dat de API-server geen nieuwe resources kan verwerken als een node binnen het cluster uitvalt waarop de admission controller draait. De service voor de admission controller mag nooit onbeschikbaar zijn, anders is je cluster op dat moment niet bruikbaar. Zie ook: documentatie.
Zorg ervoor dat alle componenten die binnen Kubernetes worden uitgerold zijn geconfigureerd om mTLS te gebruiken voor communicatie. Door gebruik te maken van mTLS ontstaan je twee voordelen:
Het kan ook gewenst zijn om mTLS te gebruiken tussen de workloads die binnen het cluster zijn uitgerold. Dit kan handmatig worden ingeregeld met behulp van cert-manager en self-signed certificaten of door het gebruik van een service-mesh zoals linkerd.
Beide methoden zorgen voor een zero trust-security (nooit vertrouwen, altijd verifiëren) implementatie door de bewezen identiteit van een workload. Zorg ervoor dat je dit combineert met een sterke identiteits- en authenticatieoplossing op de applicatielaag (zoals OIDC).
Een tactiek die wordt gebruikt bij het ontwijken van detectie is het verwijderen van Kubernetes events of het wissen van de container logs. Zorg ervoor dat de events en logging door wordt gestuurd naar een externe service, met extra toegangscontrole, los van Kubernetes.
Kubernetes-cluster events moeten extern worden opgeslagen. Dit kan worden bereikt door de eventstream te volgen met een custom component binnen het cluster en deze in stdout te loggen, zodat een log collection agent deze naar een ander systeem kan transporteren of door iets als een event-router te gebruiken om de events rechtstreeks door te sturen. Enkele projecten hiervoor zijn:
Hoewel kubectl logs
het prima is om logging in realtime te bekijken, is het verstandig om alle log events van alle systemen die op het cluster worden uitgevoerd op te slaan in een extern systeem. Dit kan ELK, Loki of een andere logging systeem zijn.
Traditioneel wordt voor het principe seperation of duties vaak gesproken over het gebruik van afzonderlijke gebruikers voor verschillende domeinen. Binnen Kubernetes vertaalt dit zich naar het gebruik van externe gebruikers versus serviceaccounts, maar ook de rollen van nodes binnen een cluster.
Zorg ervoor dat er afzonderlijke node-pools worden gebruikt voor het uitvoeren of draaien van gevoelige systemen, zoals databases, of secret management software zoals Hashicorp Vault. Dit geldt ook voor het draaien van systeemcomponenten binnen het cluster, zoals een cloud controller manager (moet worden uitgevoerd op control plane-node).
Hoewel je veel toegang kunt beperken door network policies en pod-security contexts te gebruiken, als iemand erin slaagt uit een container te breken en toegang te krijgen tot een node, dan heeft deze toegang tot elk secret en/of proces dat op dat node wordt uitgevoerd.
Het "Avoid security by obscurity"-principe vertelt ons dat we ervoor moeten zorgen dat er voldoende beveiligingscontroles zijn waarop kan worden vertrouwd. Vertaald naar Kubernetes betekent dit:
Kubernetes en het cloud native-ecosysteem kunnen erg complex zijn. Wanneer je een cluster beheert, moet je de ingeregelde beveiligingsmaatregelen ervan begrijpen. Het principe van "Keep Security simple" vertelt ons dat we ervoor moeten zorgen dat er geen grote complexiteit voor mensen wordt toegevoegd wanneer er nieuwe security controls worden ingeregeld.
Zorg ervoor dat je de automatiseringsopties gebruikt die beschikbaar zijn binnen Kubernetes om aan de slag te gaan met beveiliging. Het gebruik van een admission controller (Kyverno) om deployments te valideren aan een standaard security policy of hulpmiddelen om ontwikkelaars te helpen met beveiliging, is een must om dingen simpel en werkbaar te houden. Hierdoor wordt voorkomen dat er nieuwe risico's door menselijke fouten ontstaan.
Je kan ervoor zorgen dat security simpel blijft door goed inzicht te hebben in alle aanwezige security controls en hoe deze in productie zijn toegepast. Sommige admission controllers komen met een gebruikersinterface die voor dit doel kan worden gebruikt.
Verder is het mogelijk om via Prometheus metrics over security controls op te halen - bijna elk project heeft wel een export mogelijkheid hiervoor. In combinatie met Grafana is hier veel zichtbaarheid in te creëren.
Het hebben van een kwetsbaarheid in je applicatie kan leiden tot toegang tot resources binnen een cluster. Voorbeelden hiervan zijn: een nieuwe shell kunnen starten en toegang krijgen tot gegevens van een ander intern eindpunt (SSRF).
De volgende tactieken verdedigen hiertegen:Wanneer een kwetsbaarheid in een applicatie is geïdentificeerd, zorg er dan voor dat het risico wordt geminimaliseerd door middel van de bovenstaande items én zorg ervoor dat je het snel kunt patchen. Hiervoor hebt je een geautomatiseerde deployment pipeline nodig.
Gebruik een vulnerability scanner om CVE's in je container images te identificeren. Eenmaal geïdentificeerd, patch ze naar een nieuwe versie. Gebruik de vulnerability scanner om ervoor te zorgen dat de CVE's niet langer aanwezig zijn in je container image. Een goede scanner hiervoor is Trivy (https://github.com/aquasecurity/trivy).
Idealiter vindt dit proces plaats binnen de CI pipelines / builtstraat, met additionele scans binnen het cluster.
Het beheren van Kubernetes kan een uitdaging zijn; ervoor zorgen dat je beveiligingsniveau voldoende is, dat je gebruikmaakt van best practices en meer. Gelukkig zijn er tal van tools en producten beschikbaar om je te helpen bij dit proces. Enkele van onze favoriete tools zijn:
Wanneer je gebruikmaakt van Avisi Managed Environments, helpt Avisi Cloud je met het implementeren van best practices zodat je zekerheid hebt over hoe je beveiligingsniveau is. Avisi Cloud doet dit via de Customer onboarding. Met behulp van onze production hardening guide helpen onze engineers jou om het meeste uit een AME Kubernetes-cluster te halen.
| Avisi Managed Environments
Door Thomas Kooi / jun 2023
Dan denken we dat dit ook wat voor jou is.